Files
2nd/10_Wiki/Topics/Coding/Frontend_Container_Queries.md
T
2026-05-09 21:08:02 +09:00

6.9 KiB


id: frontend-container-queries title: Container Queries — Component-level Responsive category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, css, responsive, vibe-coding] tech_stack: { language: "CSS", applicable_to: ["Frontend"] } applied_in: [] aliases: [container query, @container, container-type, cqw, cqi, intrinsic sizing]

Container Queries

Media query = viewport. Container query = parent element. Component 가 자기 container size 따라 변형. 2023+ 모든 modern browser 지원.

📖 핵심 개념

  • @container: 가장 가까운 named container 의 size.
  • container-type: size / inline-size / normal.
  • cqw / cqi / cqh / cqb: container 단위.
  • Style query (실험): variable 값 따라.

💻 코드 패턴

기본

.card-container {
  container-type: inline-size;
  container-name: card;
}

.card {
  display: flex;
  flex-direction: column;
}

@container card (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}
<div class="card-container">
  <div class="card">
    <img src="..." />
    <div>Content</div>
  </div>
</div>

→ Container 가 400px 이상 = horizontal. 미만 = vertical. Viewport 무관.

사용 예 — 같은 component, 다른 layout

<!-- Sidebar (작음) -->
<aside class="card-container" style="width: 300px">
  <ProductCard />  <!-- vertical -->
</aside>

<!-- Main (큼) -->
<main class="card-container" style="width: 800px">
  <ProductCard />  <!-- horizontal -->
</main>

→ 같은 component 가 위치별 다른 layout.

Container types

.container-1 {
  container-type: inline-size;  /* width 만 (most common) */
}

.container-2 {
  container-type: size;          /* width + height */
}

.container-3 {
  container-type: normal;        /* default — query target X */
}

Container units

.text {
  font-size: 5cqi;   /* 5% of container's inline size */
  padding: 2cqw;     /* width % */
  margin: 1cqh;      /* height % (size container 만) */
}

→ Container 따라 자동 scale.

Multiple containers

.grid { container-type: inline-size; container-name: grid; }
.card { container-type: inline-size; container-name: card; }

@container grid (min-width: 600px) {
  .card { background: blue; }
}

@container card (min-width: 300px) {
  .card { padding: 2rem; }
}

Ranges

@container card (300px <= width <= 600px) {
  .card { background: yellow; }
}

@container card (width > 600px) {
  .card { background: green; }
}

React component

function ProductCard({ product }: ...) {
  return (
    <div className="product-card-wrapper" style={{ containerType: 'inline-size', containerName: 'product' }}>
      <div className="product-card">
        <img src={product.image} />
        <div className="product-info">
          <h3>{product.name}</h3>
          <p>{product.price}</p>
        </div>
      </div>
    </div>
  );
}
.product-card { display: flex; flex-direction: column; gap: 8px; }
.product-card img { width: 100%; }

@container product (min-width: 400px) {
  .product-card { flex-direction: row; }
  .product-card img { width: 40%; }
}

@container product (min-width: 700px) {
  .product-card { padding: 2rem; gap: 2rem; }
}

Tailwind 4 (built-in support)

<div class="@container">
  <div class="flex flex-col @md:flex-row">
    <img class="w-full @md:w-2/5" />
    <div class="@md:p-8">...</div>
  </div>
</div>

@container 만 + Tailwind 가 처리.

vs Media query

Media query: viewport
@media (min-width: 768px) {
  // 모든 컴포넌트가 같은 breakpoint
}

Container query: parent
@container (min-width: 400px) {
  // 이 component 의 parent 가 400px+ 면
}

→ Component-driven.

사용 시나리오

- Sidebar 안 card 가 다르게 보임
- Modal 안 vs page 안 같은 form 다르게
- Dashboard widget — 어디 둬도 OK
- Email-style nested layout
- 큰 화면에 multi-column page

Style queries (실험)

.parent {
  container-name: theme-container;
  --theme: dark;
}

@container theme-container style(--theme: dark) {
  .button { background: white; color: black; }
}

→ CSS variable 따라 styling. 매우 새. Limited support.

Browser support

Chrome 105+ (2022)
Safari 16.0+ (2022)
Firefox 110+ (2023)

→ 2024+ 거의 안전.
Polyfill: container-query-polyfill (legacy)

Performance

Container query = element 의 layout 변경.
Layout invalidation 트리거.
큰 nested = 비싸 수 있음.

→ 매번 측정 — 보통 OK, 큰 페이지는 주의.

Anti-pattern: 모든 곳 container

/* ❌ */
* { container-type: inline-size; }

/* layout cost — 의미 없음 */
/* ✅ — 명시적 */
.card-container { container-type: inline-size; }

Inheritance

/* Container 가 nested */
@container outer (min-width: 800px) {
  @container inner (min-width: 300px) {
    .item { ... }
  }
}

→ 두 조건 모두 OK.

JS 통합

// 동적 변경
element.style.containerType = 'inline-size';

// MutationObserver / ResizeObserver 와 같이 안 필요 — CSS 자동.

→ JS 보다 CSS 가 빠름.

Modal / popover

.modal {
  container-type: inline-size;
  width: clamp(300px, 80vw, 800px);
}

.modal-content {
  display: flex;
  flex-direction: column;
}

@container (min-width: 600px) {
  .modal-content {
    flex-direction: row;
  }
}

→ Modal size 변경 시 자동 layout 변경.

Card grid + container query

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
}

.card-grid > * {
  container-type: inline-size;
}

@container (min-width: 350px) {
  .card { ... }  /* 큰 column 안 card 만 */
}

→ Grid 의 column width 따라 card 다름.

Subgrid + container query (modern)

.layout {
  display: grid;
  grid-template-columns: 1fr 3fr;
  container-type: inline-size;
}

@container (max-width: 600px) {
  .layout { grid-template-columns: 1fr; }
}

🤔 의사결정 기준

상황 추천
Component-driven UI Container query
Page-level layout Media query
Reusable card / widget Container query
Print @media print
Theme switch Style query (실험)
Old browser support Polyfill 또는 media query fallback

안티패턴

  • 모든 element container: layout cost.
  • Container query + media query 혼용 같은 결과: media 만으로 충분 자주.
  • container-type: size + inline 만 필요: inline-size 가 충분.
  • Tailwind 3 가정: @container 는 4+.
  • Polyfill 무 modern: 거의 모든 browser 지원.
  • Nested container 깊음: layout 비싸.

🤖 LLM 활용 힌트

  • container-type: inline-size + @container 표준.
  • Component-level responsive.
  • cqw / cqi 단위 활용.
  • Tailwind 4 의 @container 친화.

🔗 관련 문서