1. Pengenalan Container Queries
Selama ini, responsive design bergantung pada @media queries yang merespons ukuran viewport (layar browser). Masalahnya: komponen yang sama bisa ditempatkan di sidebar (sempit) atau konten utama (lebar). Dengan media queries, komponen tidak tahu ukuran sebenarnya β hanya tahu ukuran layar.
Container Queries (@container) memecahkan masalah ini. Komponen merespons ukuran container-nya sendiri, bukan viewport. Ini memungkinkan komponen benar-benar portable β berfungsi dengan baik di mana pun ditempatkan.
Media Queries vs Container Queries
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β MEDIA QUERIES vs CONTAINER QUERIES β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β Media Queries (viewport-based): β β βββββββββββββββββββββββββββββββββββββββ β β β Viewport (1200px) β β β β ββββββββββββ ββββββββββββββ β β β β β Sidebar β β Content β β β β β β (300px) β β (900px) β β β β β β Card: π± β β Card: π» β β β Problem!β β β β (kecil) β β (besar) β β β Kedua cardβ β β ββββββββββββ β sama2 pakaiβ β β sama β β β β breakpoint β β β β β β 1200px!) β β β β β ββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββββ β β β β Container Queries (container-based): β β βββββββββββββββββββββββββββββββββββββββ β β β ββββββββββββ ββββββββββββββ β β β β β Sidebar β β Content β β β β β β (300px) β β (900px) β β β β β β Card: π± β β Card: π» β β β Benar! β β β β layout β β layout β β β Masing2 β β β β kecil β β besar β β β sesuai β β β ββββββββββββ ββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Containment & Container
Untuk menggunakan Container Queries, elemen harus dideklarasikan sebagai container menggunakan container-type.
/* Inline-size containment (paling umum) */
/* Container akan track lebarnya */
.card-wrapper {
container-type: inline-size;
/* Opsional: beri nama untuk menghindari nearest container */
container-name: card;
}
/* Size containment (track lebar DAN tinggi) */
.widget-wrapper {
container-type: size;
container-name: widget;
}
/* Normal containment (tanpa query, hanya containment) */
.section-wrapper {
container-type: normal;
}
/* Shorthand: container: name / type */
.card-wrapper {
container: card / inline-size;
}
/* Important: container TIDAK bisa meng-query dirinya sendiri */
/* Anak elemen yang meng-query container-nya */
Ketika elemen menjadi container, ia tidak bisa menjadi target query dari dirinya sendiri. Container query hanya bisa digunakan oleh anak/descendant dari container. Selain itu, container dengan inline-size tidak bisa menggunakan query berbasis height.
3. Size Queries
/* Deklarasi container */
.card-wrapper {
container: card / inline-size;
}
/* Query berdasarkan lebar container */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
gap: 16px;
}
.card__image {
aspect-ratio: 1;
}
}
@container card (min-width: 700px) {
.card {
grid-template-columns: 300px 1fr;
gap: 24px;
}
.card__title {
font-size: 1.5rem;
}
}
/* Default (mobile-first): < 400px */
.card {
display: flex;
flex-direction: column;
}
.card__image {
width: 100%;
aspect-ratio: 16/9;
}
/* Query dengan range syntax */
@container card (400px <= width <= 700px) {
.card {
grid-template-columns: 180px 1fr;
}
}
/* Container query untuk height (perlu container-type: size) */
@container widget (min-height: 300px) {
.widget__chart {
height: 200px;
}
}
/* Combining conditions */
@container card (min-width: 500px) and (orientation: landscape) {
.card {
flex-direction: row;
}
}
Named vs Anonymous Containers
/* Named container β query target spesifik */
.sidebar {
container: sidebar / inline-size;
}
.main-content {
container: main / inline-size;
}
/* Query named container spesifik */
@container sidebar (min-width: 300px) {
.nav-menu {
flex-direction: row;
}
}
@container main (min-width: 800px) {
.content-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Anonymous container β query nearest container */
.card-wrapper {
container-type: inline-size; /* Tanpa nama */
}
@container (min-width: 400px) {
/* Otomatis query container terdekat */
.card-inner {
flex-direction: row;
}
}
4. Container Units
CSS Container Queries Level 3 memperkenalkan container units β satuan yang relatif terhadap ukuran container, bukan viewport.
| Unit | Relatif terhadap | Contoh |
|---|---|---|
cqw | 1% dari lebar container | font-size: 5cqw |
cqh | 1% dari tinggi container | height: 50cqh |
cqi | 1% dari inline-size container | padding: 3cqi |
cqb | 1% dari block-size container | margin: 2cqb |
cqmin | Nilai terkecil dari cqi/cqb | width: 80cqmin |
cqmax | Nilai terbesar dari cqi/cqb | max-width: 50cqmax |
/* Container yang menggunakan container units */
.card-wrapper {
container-type: inline-size;
}
.card {
padding: 4cqi; /* Padding relatif terhadap container width */
border-radius: 2cqi;
}
.card__title {
font-size: clamp(1rem, 5cqi, 2.5rem); /* Fluid typography per container */
line-height: 1.2;
}
.card__subtitle {
font-size: clamp(0.875rem, 3.5cqi, 1.25rem);
}
.card__image {
width: 100%;
height: 50cqi; /* Tinggi gambar relatif terhadap container width */
object-fit: cover;
border-radius: 2cqi;
}
.card__badge {
font-size: 2.5cqi;
padding: 1cqi 2.5cqi;
border-radius: 1cqi;
}
/* Responsive tanpa breakpoint β otomatis scale */
.grid-item {
container-type: inline-size;
}
.grid-item__content {
padding: 3cqi;
font-size: 3.5cqi;
gap: 2cqi;
}
.grid-item__heading {
font-size: clamp(0.875rem, 6cqi, 2rem);
margin-bottom: 1.5cqi;
}
5. Style Queries
Style Queries memungkinkan Anda meng-query custom property (CSS variables) dari container. Ini memungkinkan perubahan style berdasarkan tema atau konfigurasi tanpa JavaScript.
/* Style queries dengan custom properties */
.card {
--theme: light;
--accent: blue;
--layout: vertical;
}
/* Query berdasarkan custom property */
@container style(--theme: dark) {
.card__inner {
background: #1a1a2e;
color: #e0e0e0;
border-color: #333;
}
}
@container style(--theme: light) {
.card__inner {
background: #ffffff;
color: #333;
border-color: #ddd;
}
}
@container style(--accent: blue) {
.card__badge {
background: #0066ff;
color: white;
}
}
@container style(--accent: green) {
.card__badge {
background: #00cc66;
color: white;
}
}
@container style(--accent: red) {
.card__badge {
background: #ff3333;
color: white;
}
}
@container style(--layout: horizontal) {
.card__inner {
flex-direction: row;
}
}
@container style(--layout: vertical) {
.card__inner {
flex-direction: column;
}
}
/* Penggunaan: */
.card.theme-dark {
--theme: dark;
}
.card.accent-green {
--accent: green;
}
.card.layout-horizontal {
--layout: horizontal;
}
6. Praktik Komponen Responsive
<!-- HTML Structure -->
<div class="product-card-wrapper">
<article class="product-card">
<img src="product.jpg" alt="Produk A" class="product-card__image">
<div class="product-card__body">
<span class="product-card__category">Elektronik</span>
<h3 class="product-card__title">Smartphone XYZ Pro</h3>
<p class="product-card__desc">Smartphone flagship dengan kamera 108MP</p>
<div class="product-card__footer">
<span class="product-card__price">Rp 5.999.000</span>
<button class="product-card__btn">Beli</button>
</div>
</div>
</article>
</div>
<style>
/* Container declaration */
.product-card-wrapper {
container: product-card / inline-size;
}
/* Mobile-first (default): Vertical stack */
.product-card {
display: flex;
flex-direction: column;
border: 1px solid #222;
border-radius: 3cqi;
overflow: hidden;
background: #111;
}
.product-card__image {
width: 100%;
aspect-ratio: 16/9;
object-fit: cover;
}
.product-card__body {
padding: 3cqi;
display: flex;
flex-direction: column;
gap: 2cqi;
}
.product-card__title {
font-size: clamp(1rem, 4cqi, 1.5rem);
margin: 0;
}
.product-card__desc {
font-size: clamp(0.75rem, 3cqi, 1rem);
opacity: 0.7;
display: none; /* Sembunyikan di mobile */
}
.product-card__footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
.product-card__price {
font-size: clamp(0.875rem, 3.5cqi, 1.25rem);
font-weight: bold;
color: #00cc66;
}
.product-card__btn {
padding: 2cqi 4cqi;
font-size: clamp(0.75rem, 2.5cqi, 1rem);
border: none;
border-radius: 2cqi;
background: #0066ff;
color: white;
cursor: pointer;
}
/* Tablet container: Horizontal layout */
@container product-card (min-width: 500px) {
.product-card {
flex-direction: row;
}
.product-card__image {
width: 40%;
aspect-ratio: 1;
}
.product-card__desc {
display: block; /* Tampilkan deskripsi */
}
}
/* Desktop container: Bigger, more info */
@container product-card (min-width: 800px) {
.product-card__image {
width: 35%;
}
.product-card__body {
padding: 4cqi;
}
}
</style>
7. Container Queries + CSS Grid
/* Grid layout yang responsive terhadap container */
.product-grid-wrapper {
container: product-grid / inline-size;
}
.product-grid {
display: grid;
gap: 4cqi;
padding: 3cqi;
}
/* Default: 1 kolom */
.product-grid {
grid-template-columns: 1fr;
}
/* 2 kolom saat container cukup lebar */
@container product-grid (min-width: 500px) {
.product-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* 3 kolom */
@container product-grid (min-width: 800px) {
.product-grid {
grid-template-columns: repeat(3, 1fr);
gap: 3cqi;
}
}
/* 4 kolom */
@container product-grid (min-width: 1100px) {
.product-grid {
grid-template-columns: repeat(4, 1fr);
gap: 2cqi;
}
}
/* Setiap item juga container */
.product-grid__item {
container: product-item / inline-size;
}
8. Common Patterns
Sidebar + Content Layout
/* Sidebar yang collapsible berdasarkan container width */
.app-layout {
display: grid;
grid-template-columns: auto 1fr;
}
.sidebar {
container: sidebar / inline-size;
width: 250px;
transition: width 0.3s ease;
}
/* Sidebar collapsed */
@container sidebar (max-width: 80px) {
.sidebar__label {
display: none;
}
.sidebar__icon {
margin: 0 auto;
}
}
/* Sidebar expanded */
@container sidebar (min-width: 200px) {
.sidebar__label {
display: block;
}
.sidebar__menu {
flex-direction: column;
}
}
9. Best Practices
- Mobile-first β tulis default style untuk ukuran terkecil
- container-type: inline-size β gunakan ini (bukan size) untuk performa
- Named containers β beri nama untuk menghindari query salah target
- Container units β gunakan cqw/cqi untuk scaling otomatis
- Kombinasikan dengan clamp() β untuk fluid typography yang aman
- Style queries β gunakan untuk theming tanpa JavaScript
- Progressive enhancement β fallback dengan @supports
- Hindari container nesting berlebihan β bisa membingungkan
- Browser support β tersedia di semua browser modern (2023+)
- Performance β container-type: inline-size lebih ringan dari size
/* Fallback untuk browser yang tidak mendukung */
.card {
display: flex;
flex-direction: column;
}
/* Default responsive dengan media query */
@media (min-width: 768px) {
.card {
flex-direction: row;
}
}
/* Override dengan container query jika didukung */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
.card {
flex-direction: column; /* Reset ke default */
}
@container (min-width: 500px) {
.card {
flex-direction: row;
}
}
}
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial CSS Container Queries, jawablah 5 pertanyaan berikut: