Dulu kalau mau bikin komponen yang responsif, jawabannya selalu sama: media queries. Tapi masalahnya, media queries itu ngeliatin viewport browser, bukan container komponennya. Jadi kalau kamu punya card yang dipake di sidebar (sempit) dan di main content (lebar), dia bakal keliatan sama aja padahal konteksnya beda banget.
Saya ingat pertama kali kena masalah ini waktu bikin dashboard. Ada komponen card yang harusnya di sidebar tampil compact, tapi karena viewport-nya lebar (desktop), media query-nya nganggap "oh ini layar besar, kasih layout 3 kolom dong." Hasilnya? Card di sidebar berantakan, overflow ke mana-mana. Harus nulis CSS khusus buat override, yang ujung-ujungnya jadi spaghetti.
Nah, sekarang ada solusi resminya: CSS Container Queries. Fitur ini sudah didukung di semua browser modern (Chrome 105+, Firefox 110+, Safari 16+), dan cara kerjanya bikin responsive design jadi jauh lebih masuk akal.
Media queries ngukur lebar viewport artinya seluruh layar browser. Ini bagus buat layout halaman secara keseluruhan. Tapi kalau kamu punya komponen yang reusable (card, widget, sidebar item), dia harusnya adaptif berdasarkan space yang tersedia, bukan ukuran layar.
Bayangkan kamu punya komponen product-card yang dipake di:
Kalau pakai media queries, kamu harus kasih class tambahan seperti .card-sidebar, .card-modal, lalu tulis CSS override buat masing-masing. Komponennya jadi nggak benar-benar reusable selalu perlu "tahu" di mana dia dipasang.
Container queries menyelesaikan ini dengan satu prinsip: komponen mengukur container-nya sendiri, bukan viewport.
Langkah pertama adalah mendeklarasikan elemen sebagai container. Ini kasih tahu browser: "Elemen ini bisa di-query ukurannya oleh anak-anaknya."
.card-wrapper {
container-type: inline-size;
container-name: card;
}
Penjelasan:
container-type: inline-size container bisa di-query berdasarkan lebar inline (horizontal). Anak-anak di dalamnya bisa nanya "berapa lebar container gue?"container-name: card kasih nama biar bisa nargetin container spesifik kalau ada nested containersLalu di dalam komponen, pakai @container buat nulis aturan berdasarkan ukuran container:
/* Default: layout compact (untuk container sempit) */
.product-card {
display: flex;
flex-direction: column;
gap: 8px;
}
.product-card img {
width: 100%;
height: 120px;
object-fit: cover;
}
.product-card .info h3 {
font-size: 14px;
}
/* Kalau container cukup lebar, switch ke horizontal layout */
@container card (min-width: 500px) {
.product-card {
flex-direction: row;
gap: 16px;
}
.product-card img {
width: 200px;
height: 150px;
}
.product-card .info h3 {
font-size: 18px;
}
}
/* Kalau container sangat lebar, tampil full-featured */
@container card (min-width: 750px) {
.product-card img {
width: 300px;
height: 200px;
}
.product-card .info h3 {
font-size: 22px;
}
.product-card .info .description {
display: block; /* hidden di mobile, muncul di desktop */
}
}
Sekarang komponen .product-card benar-benar mandiri. Taruh di sidebar 300px dia compact. Taruh di main content 800px dia expand. Tanpa perlu class tambahan, tanpa perlu tahu konteksnya.
Mari bikin contoh lengkap. Kita buat card produk yang bisa dipakai di mana saja:
HTML:
<div class="card-wrapper">
<div class="product-card">
<img src="product.jpg" alt="Produk" loading="lazy">
<div class="info">
<h3>Mechanical Keyboard TKL</h3>
<p class="price">Rp 850.000</p>
<p class="description">Keyboard mekanikal dengan switch Gateron Brown, RGB per-key, dan koneksi wireless Bluetooth 5.1.</p>
<button>Tambah ke Keranjang</button>
</div>
</div>
</div>
CSS:
/* === Container Definition === */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* === Base Styles (Compact untuk sidebar < 500px) === */
.product-card {
display: flex;
flex-direction: column;
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.2s;
}
.product-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.product-card img {
width: 100%;
height: 140px;
object-fit: cover;
}
.product-card .info {
padding: 12px;
}
.product-card .info h3 {
font-size: 14px;
margin: 0 0 4px 0;
line-height: 1.3;
}
.product-card .info .price {
font-size: 16px;
font-weight: 700;
color: #1565c0;
margin: 0 0 8px 0;
}
.product-card .info .description {
display: none; /* hidden di compact mode */
font-size: 13px;
color: #666;
margin: 0 0 12px 0;
line-height: 1.5;
}
.product-card .info button {
width: 100%;
padding: 8px;
background: #1565c0;
color: #fff;
border: none;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
}
/* === Medium Container (500px+) card di main content === */
@container card (min-width: 500px) {
.product-card {
flex-direction: row;
}
.product-card img {
width: 180px;
height: auto;
min-height: 160px;
}
.product-card .info {
padding: 16px;
display: flex;
flex-direction: column;
justify-content: center;
}
.product-card .info h3 {
font-size: 17px;
}
.product-card .info .price {
font-size: 18px;
}
.product-card .info .description {
display: block;
}
.product-card .info button {
width: auto;
align-self: flex-start;
padding: 8px 20px;
}
}
/* === Large Container (750px+) card di hero section === */
@container card (min-width: 750px) {
.product-card img {
width: 280px;
}
.product-card .info {
padding: 24px;
}
.product-card .info h3 {
font-size: 22px;
}
.product-card .info .price {
font-size: 22px;
}
.product-card .info .description {
font-size: 14px;
}
}
Sekarang coba taruh .card-wrapper ini di elemen dengan lebar berbeda-beda sidebar 280px, main content 700px, full-width 1000px. Card-nya otomatis adaptif tanpa perlu media query atau class tambahan apapun.
Bukan berarti media queries jadi nggak berguna. Keduanya punya peran masing-masing:
Dalam praktiknya, kamu pakai keduanya barengan:
/* Media query: layout halaman */
@media (max-width: 768px) {
.page-layout {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
}
/* Container query: komponen di dalam layout */
@container card (min-width: 500px) {
.product-card {
flex-direction: row;
}
}
Ada dua pilihan container-type:
inline-size query berdasarkan lebar (horizontal). Ini yang paling sering dipake dan performanya lebih baik.size query berdasarkan lebar DAN tinggi. Jarang dipake, cuma kalau kamu benar-benar perlu responsif berdasarkan tinggi container./* Paling umum: cuma perlu lebar */
.dashboard-widget {
container-type: inline-size;
}
/* Kalau perlu juga tinggi (misalnya resize panel) */
.terminal-panel {
container-type: size;
}
/* Query tinggi */
@container terminal (min-height: 400px) {
.terminal-panel .output {
font-size: 14px;
}
}
Perhatian: kalau pakai container-type: size, elemen tersebut nggak bisa di-scroll dengan overflow: auto secara normal karena containment-nya lebih ketat. Biasanya inline-size aja sudah cukup.
Kamu bisa nulis container declaration dengan shorthand:
/* Panjang */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Shorthand urutan: name / type */
.card-wrapper {
container: card / inline-size;
}
Dua-duanya sama aja. Pakai yang mana terserah, tapi konsisten di satu project.
Ini contoh yang paling kerasa manfaatnya. Dashboard biasanya punya grid yang bisa di-resize atau di-rearrange. Widget di dalamnya harus adaptif tanpa reload.
/* Grid layout dashboard */
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
padding: 16px;
}
/* Setiap widget jadi container */
.widget {
container: widget / inline-size;
background: #1a1a2e;
border-radius: 12px;
padding: 16px;
color: #eee;
}
/* Widget content responsif */
.widget-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.widget-header h3 {
font-size: 14px;
font-weight: 600;
}
/* Chart kecil kalau widget sempit */
.widget .chart-container {
height: 120px;
}
/* Chart lebih besar kalau widget lebar */
@container widget (min-width: 500px) {
.widget-header h3 {
font-size: 18px;
}
.widget .chart-container {
height: 200px;
}
.widget .chart-legend {
display: flex;
gap: 16px;
margin-top: 12px;
}
}
/* Full-width widget */
@container widget (min-width: 800px) {
.widget {
padding: 24px;
}
.widget .chart-container {
height: 300px;
}
.widget .summary-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-top: 16px;
}
}
Widget yang ditaruh di kolom 300px tampil minimalis. Widget yang span 2 kolom (600px) tampil lebih detail. Widget full-width (1000px) tampil lengkap dengan summary grid. Semua otomatis, tanpa JavaScript.
1. Container nggak bisa query dirinya sendiri
Kalau kamu deklarasi .card-wrapper sebagai container, aturan @container cuma berlaku buat anak-anaknya, bukan .card-wrapper itu sendiri. Jadi kalau mau card wrapper-nya juga berubah, pisahkan container dan content:
/* BENAR */
<div class="container"> <!-- container -->
<div class="card">...</div> <!-- konten yang di-query -->
</div>
/* SALAH nggak work */
<div class="card"> <!-- ini sekaligus container dan target -->
...
</div>
2. Hati-hati dengan nested containers
Kalau ada container di dalam container, child container-nya nge-query parent-nya, bukan grandparent. Ini biasanya yang kamu mau, tapi kadang bikin bingung kalau layout-nya kompleks.
3. container-type bikin overflow hidden
Deklarasi container-type: inline-size secara otomatis bikin elemen tersebut punya overflow: hidden (containment). Kalau kamu butuh tooltip atau dropdown yang overflow keluar container, pertimbangkan untuk nggak bikin elemen itu sebagai container.
4. Belum support di email clients
Container queries cuma work di browser modern. Untuk email HTML, tetap pakai media queries atau hybrid approach.
Container queries sudah didukung di semua browser mayor sejak akhir 2023:
Artinya di 2026, coverage-nya sudah di atas 95% user base global. Aman dipakai di production tanpa polyfill.
Cek support real-time di caniuse.com/css-container-queries.
Container queries bikin responsive design jadi lebih logis. Komponen mengukur dirinya sendiri, bukan viewport. Hasilnya: kode CSS lebih bersih, komponen benar-benar reusable, dan nggak perlu override berlapis-lapis.
Kalau kamu masih pakai media queries buat semuanya termasuk komponen-komponen kecil coba migrasi satu komponen dulu ke container queries. Card di sidebar dan main content adalah use case paling gampang buat mulai. Kamu bakal langsung ngerasain bedanya.
Pertanyaan atau pengalaman pakai container queries? Share di kolom komentar di bawah!