02 — Container Queries

답하는 질문: 컴포넌트가 자기가 놓인 부모 컨테이너의 크기를 알고, 그에 맞춰 적응할 수 있는가?


한 줄 답

컨테이너 쿼리는 부모 요소의 크기에 반응하는 미디어 쿼리다. @media뷰포트를 보았다면 @container가장 가까운 컨테이너를 본다. 동일 컴포넌트가 사이드바(280px)와 본문(680px)에서 자동으로 다른 레이아웃이 된다 — 14년간 풀리지 않던 컴포넌트 단위 반응형의 해답.


Why — 왜 필요했나

1) 미디어 쿼리의 한계

// 같은 <Card />가 두 곳에 있다
<Sidebar><Card /></Sidebar>   {/* 280px */}
<Main><Card /></Main>          {/* 980px */}

뷰포트가 같으면 @media는 둘을 구분 못 한다. 결국 우리는 부모로부터 prop을 받아 분기했다 — <Card variant="narrow" />. CSS만으로 해결이 안 됐다.

2) 왜 표준화가 14년 걸렸나

문제는 순환 의존성. 자식이 부모 크기에 반응해서 자기 크기를 바꾸면, 부모 크기가 또 바뀌어서 자식이 또 반응한다 — 무한 루프.

해법은 CSS Containment(2019). 부모에 container-type을 선언하면 자식이 부모 크기에 영향을 주지 못하도록 강제 — 순환 끊김. 이를 깔고 2023년 Container Queries가 표준화됐다.


How — 어떻게 동작하나

3단계 셋업

/* 1. 부모를 컨테이너로 등록 */
.card-container {
  container-type: inline-size;      /* 인라인 축(=폭)만 본다 */
  container-name: card;             /* 선택적, 명명 컨테이너 */
}
 
/* 2. 자식이 부모 크기에 반응 */
.card {
  display: flex;
  flex-direction: column;
}
 
@container card (width >= 400px) {
  .card {
    flex-direction: row;            /* 부모가 400px 이상이면 가로 */
  }
}

What — 전체 사양

1) container-type

의미비용
normal컨테이너 아님(기본)없음
inline-size인라인 축(보통 width)만 컨테이너가벼움 — layout containment만
size양 축(width + height) 컨테이너무거움 — fixed 크기 강제, 자식이 부모 높이에 영향 못 줌

권장: 99%의 경우 inline-size. size는 height 쿼리 반드시 필요할 때만.

2) container-name

.outer  { container: outer / inline-size; }    /* shorthand */
.inner  { container: inner / inline-size; }
 
@container outer (width >= 600px) { ... }      /* 명시적으로 outer만 */
@container inner (width >= 300px) { ... }
@container (width >= 300px) { ... }            /* 이름 없이 — 가장 가까운 조상 컨테이너 */

3) 컨테이너 쿼리 단위 — cqi/cqw/cqb/cqh/cqmin/cqmax

뷰포트 단위(vw/vh)의 컨테이너 버전:

단위의미
cqw컨테이너 width의 1%
cqh컨테이너 height의 1%
cqi컨테이너 inline-size의 1% (가장 흔함)
cqb컨테이너 block-size의 1%
cqmincqi, cqb 중 작은 쪽
cqmaxcqi, cqb 중 큰 쪽
.card-title {
  font-size: clamp(1rem, 4cqi, 2rem);   /* 컨테이너 폭의 4%, 1~2rem 사이 */
}

4) 쿼리할 수 있는 피쳐

@container (width >= 400px) { ... }
@container (min-width: 400px) { ... }      /* legacy 문법도 OK */
@container (aspect-ratio > 1) { ... }      /* size 컨테이너에서만 */
@container (orientation: portrait) { ... } /* size 컨테이너에서만 */
@container style(--variant: hero) { ... }  /* style query (실험적, Chrome 111+) */

5) Style Queries (실험적, 2026-05 부분 지원)

부모의 CSS 변수 값으로 자식 분기:

.theme-section { --variant: hero; container-name: theme; }
@container theme style(--variant: hero) {
  .title { font-size: 4rem; }
}

상태: Chrome 안정, Safari 18+, Firefox 미지원. @supports로 가드 권장.


What-if — 잘못 쓰면

Case 1: container-type 미지정

.parent { /* container-type 없음 */ }
@container (width >= 400px) { .child { ... } }  /* 매칭 안 됨 */

@container등록된 조상만 찾는다. 등록 안 됐으면 매칭 실패. 침묵의 버그.

Case 2: height 쿼리에 inline-size 사용

.parent { container-type: inline-size; }
@container (height >= 300px) { ... }  /* 매칭 안 됨 */

inline-size는 인라인 축만 본다. height·orientation·aspect-ratio는 size 필요 → 그러나 size는 부모를 intrinsic 크기에서 격리하므로 자식의 height가 부모를 늘리지 못한다 → 콘텐츠가 잘림. 트레이드오프.

Case 3: 컴포넌트 자신을 컨테이너로 만들기

.card {
  container-type: inline-size;
}
@container (width >= 400px) {
  .card { padding: 2rem; }      /* 자기 자신을 변경 */
}

@container자기 자신은 보지 않는다 — 조상만 본다. 이를 풀려면 wrapper 한 단계 더 두기:

<div class="card-wrapper">
  <div class="card">...</div>
</div>
.card-wrapper { container-type: inline-size; }
@container (width >= 400px) { .card { padding: 2rem; } }

이는 “container query는 wrapper가 필요하다”는 흔한 불만의 원인. *Chrome 117+*는 이를 우회하기 위한 container-name self-reference를 논의 중.

Case 4: cqi 단위가 0이 되는 경우

.parent { /* container-type 미지정 */ }
.child { width: 50cqi; }   /* 컨테이너 없음 → viewport 단위로 fallback */

브라우저는 가장 가까운 컨테이너를 찾고, 없으면 small viewport로 fallback. 의도와 다를 수 있음.

Case 5: 무한 루프 — size 컨테이너에서

.parent {
  container-type: size;
}
@container (height >= 300px) {
  .child { height: 400px; }    /* 자식이 부모 height 늘림? */
}

size 컨테이너는 자식이 부모 크기에 영향 줄 수 없도록 격리 — 부모 height는 자식 콘텐츠로 자라지 않음. 대신 부모는 명시적으로 크기를 가져야 함(예: height: 50vh).


Insight — Container Query는 “CSS의 Hooks moment”

React에 Hooks가 등장(2019)한 이유는 컴포넌트가 자기 상태를 자기 안에 캡슐화하기 위함이었다. 그 전까지 컴포넌트는 부모로부터 prop을 받아야 했다.

Container Query는 CSS의 같은 전환점이다. 그 전까지 컴포넌트의 스타일은 부모의 뷰포트 미디어 쿼리 또는 부모가 넘긴 prop에 의존했다 — <Card variant="narrow" />.

@container로 컴포넌트는 자기 컨텍스트를 자기 안에서 본다. CSS가 컴포넌트 친화적 언어가 되는 분기점.

“Hooks가 React를 함수형으로 만들었듯, @container는 CSS를 컴포넌트 단위로 만들었다.” — Miriam Suzanne (CSS WG, @container 명세 공동 작성자)

Miriam은 2017년부터 Element Queries 제안서를 이어왔다. 6년의 작업이 2023년 Baseline 2023으로 결실 — 표준의 느림공고함을 동시에 보여준다.


요약 + Mermaid

  • @container부모 컨테이너 크기에 반응 — @media(뷰포트)와 짝.
  • 셋업: container-type: inline-size + @container (...) 두 줄.
  • inline-size(가벼움) vs size(양축, layout containment 비용).
  • cqi/cqw/... 단위로 컨테이너 비율 표현.
  • 자기 자신은 쿼리 못 함 — wrapper 한 단계 필요.

다음: 03-has-selector부모 선택자의 합류.