karawaci.kode

← Semua snippet

TypeScript Pemula Utility

Tailwind v4 arbitrary + container query

Arbitrary value Tailwind v4 + container query (@container) untuk responsive component yang depend size parent, bukan viewport.

Dipublikasikan 13 Juni 2026

Tailwind v4 bawa banyak QoL: arbitrary value lebih cleaner, native container query, dan engine baru yang lebih cepat. Snippet ini contoh kartu produk yang adapt layout berdasarkan lebar parent — di sidebar narrow jadi stack, di grid 3-col jadi horizontal. Tanpa hack media query atau JS measurement.

Kode

// components/KartuProduk.tsx
import Image from "next/image";

interface Produk {
  id: number;
  nama: string;
  harga: number;
  diskon_persen?: number;
  foto: string;
  rating: number;
}

interface Props {
  produk: Produk;
}

export function KartuProduk({ produk }: Props) {
  const hargaDiskon = produk.diskon_persen
    ? produk.harga * (1 - produk.diskon_persen / 100)
    : produk.harga;

  return (
    // @container bikin element ini jadi container query root
    <article className="@container rounded-lg border border-zinc-200 bg-white p-3 dark:border-zinc-800 dark:bg-zinc-900">
      <div className="flex flex-col gap-3 @[280px]:flex-row">
        {/* Foto: full width kalau sempit, fixed kalau lebar */}
        <div className="relative aspect-square w-full overflow-hidden rounded @[280px]:w-32 @[280px]:flex-shrink-0">
          <Image
            src={produk.foto}
            alt={produk.nama}
            fill
            sizes="(max-width: 280px) 100vw, 128px"
            className="object-cover"
          />
          {produk.diskon_persen && (
            <span className="absolute left-1 top-1 rounded bg-red-500 px-1.5 py-0.5 text-[10px] font-semibold text-white">
              -{produk.diskon_persen}%
            </span>
          )}
        </div>

        <div className="flex min-w-0 flex-1 flex-col">
          <h3 className="line-clamp-2 text-sm font-medium text-zinc-900 dark:text-zinc-100">
            {produk.nama}
          </h3>

          <div className="mt-1 flex items-baseline gap-2">
            <span className="text-base font-bold text-zinc-900 dark:text-zinc-100">
              Rp {hargaDiskon.toLocaleString("id-ID")}
            </span>
            {produk.diskon_persen && (
              <span className="text-xs text-zinc-400 line-through">
                Rp {produk.harga.toLocaleString("id-ID")}
              </span>
            )}
          </div>

          <div className="mt-auto pt-2 text-xs text-zinc-500 @[280px]:text-sm">
            {/* Arbitrary value untuk warna brand */}
            <span className="text-[#f59e0b]">{"".repeat(Math.round(produk.rating))}</span>
            <span className="ml-1">{produk.rating.toFixed(1)}</span>
          </div>
        </div>
      </div>
    </article>
  );
}

Pemakaian

// Di sidebar 240px — kartu jadi stack
<aside className="w-60 space-y-3">
  <KartuProduk produk={produk1} />
  <KartuProduk produk={produk2} />
</aside>
// Di grid 4-kolom — masing-masing slot ~280px, kartu jadi horizontal
<section className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4">
  {produkList.map((p) => (
    <KartuProduk key={p.id} produk={p} />
  ))}
</section>

// Layout otomatis adapt — gak perlu prop "horizontal" / "vertical"
// Arbitrary breakpoint custom — @[420px], @[640px]
<div className="@container">
  <div className="text-sm @[420px]:text-base @[640px]:text-lg">
    Font size scale dengan lebar container, bukan viewport.
  </div>
</div>

Kapan dipakai

  • Kartu produk e-commerce yang di-reuse di sidebar, grid, dan listing.
  • Dashboard widget yang re-arrange tergantung lebar panel dia ditaruh.
  • Email template-style component yang harus jalan di iframe sempit dan layar wide.
  • Design system component library — UI consistent di mana pun ditaruh.

Catatan

  • @container syntax: prefix @container pada parent, lalu @[280px]: di child untuk respond ke 280px container. Bisa pakai named container @container/sidebar + @lg/sidebar:flex-row.
  • Arbitrary value escape — kalau ada karakter spesial (titik, bracket), pakai underscore: bg-[rgb(255,0,0)] jadi bg-[rgb(255_0_0)].
  • Performance — container query lebih murah dari ResizeObserver. Native CSS, GPU accelerated.
  • Layout shift — kalau parent berubah ukuran (sidebar collapse), child re-layout otomatis. Pakai transition-all kalau perlu animasi.
  • Sizing@container butuh container-type: inline-size atau size. Tailwind v4 set otomatis ke inline-size.

Tailwind v3 ke v4 migration breaking: @apply di tempat tertentu tidak lagi work, dan beberapa default value berubah (border 0 jadi transparent). Cek migration guide sebelum upgrade project lama.

# tags

tailwindcontainer-querycssresponsivereact

Ditulis oleh Asti Larasati · 13 Juni 2026