Web Dev

CSS Container Queries - Responsive Design Tanpa Media Queries

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.

Kenapa Media Queries Kadang Kurang Tepat?

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:

  • Main content area (lebar ~800px) mau tampil 3 kolom dengan gambar besar
  • Sidebar (lebar ~300px) mau tampil 1 kolom dengan gambar kecil
  • Modal popup (lebar ~500px) tampil 2 kolom

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.

Konsep Dasar: Container dan Query

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 containers

Lalu 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.

Praktik: Membuat Responsive Card Component

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.

Container Queries vs Media Queries: Kapan Pakai yang Mana?

Bukan berarti media queries jadi nggak berguna. Keduanya punya peran masing-masing:

  • Media queries buat layout halaman global. Navbar collapse di mobile, sidebar hide di tablet, grid columns berubah. Ini soal viewport.
  • Container queries buat komponen reusable. Card, widget, form, sidebar item yang harus adaptif terhadap space di sekitarnya. Ini soal context.

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;
    }
}

Container Type: Size vs Inline-Size

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.

Shorthand Syntax

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.

Use Case: Dashboard Widget System

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.

Tips dan Pitfalls

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.

Browser Support 2026

Container queries sudah didukung di semua browser mayor sejak akhir 2023:

  • Chrome/Edge 105+ (September 2022)
  • Firefox 110+ (Februari 2023)
  • Safari 16+ (September 2022)

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.

Kesimpulan

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!


You may also like


0 Comments


Leave a Reply

Comments with links or spam keywords will be rejected.
Scroll to Top