Web Development

CSS Container Queries Advanced

Eksplorasi mendalam CSS Container Queries β€” container units (cqw, cqh), style queries, containment, dan cara membangun komponen yang benar-benar responsive terhadap ukuran container, bukan viewport.

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

Diagram: Perbedaan Responsive Approaches
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       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.

CSS β€” Deklarasi Container
/* 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 */
⚠️ Perhatian

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

CSS β€” Size Container 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

CSS β€” Named Container
/* 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.

UnitRelatif terhadapContoh
cqw1% dari lebar containerfont-size: 5cqw
cqh1% dari tinggi containerheight: 50cqh
cqi1% dari inline-size containerpadding: 3cqi
cqb1% dari block-size containermargin: 2cqb
cqminNilai terkecil dari cqi/cqbwidth: 80cqmin
cqmaxNilai terbesar dari cqi/cqbmax-width: 50cqmax
CSS β€” Container Units
/* 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.

CSS β€” Style Queries
/* 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 + CSS β€” Product Card 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

CSS β€” Auto-Fit Grid dengan Container Queries
/* 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

CSS β€” Layout Pattern
/* 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

βœ… Container Queries 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
CSS β€” Progressive Enhancement
/* 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:

Pertanyaan 1: Apa perbedaan utama antara @media dan @container?

a) @media query viewport, @container query container element
b) Tidak ada perbedaan
c) @media lebih cepat
d) @container hanya untuk mobile

Pertanyaan 2: Unit apa yang relatif terhadap lebar container?

a) vw
b) cqw
c) em
d) rem

Pertanyaan 3: Property apa yang mendeklarasikan elemen sebagai container?

a) display: container
b) container-type
c) contain: inline-size
d) is-container: true

Pertanyaan 4: Apa yang bisa di-query oleh style queries?

a) Semua CSS properties
b) Hanya custom properties (CSS variables)
c) Hanya color properties
d) Hanya font properties

Pertanyaan 5: container-type apa yang paling ringan untuk performa?

a) size
b) inline-size
c) normal
d) block-size
πŸ” Zoom
100%
🎨 Tema