들어가며: 94%가 지원하는데, 왜 아무도 안 쓸까?

CSS 컨테이너 쿼리(Container Queries)는 현재 브라우저 지원율이 약 **94%**에 달합니다. 그런데 State of CSS 설문을 보면 상황이 묘해요. 개발자의 86%가 존재를 인지하고 있지만, 실제로 쓰는 사람은 **41.4%**뿐입니다.

왜 이런 일이 벌어졌을까요? 답은 단순해요. 컨테이너 쿼리가 미디어 쿼리랑 너무 똑같이 생겼기 때문입니다. 문법이 비슷하니까 "비슷한 용도겠지" 하고 넘어가는데, 이게 첫 번째 함정이에요. 둘은 보는 방향 자체가 반대입니다.

이 글에서는 미디어 쿼리와 컨테이너 쿼리의 근본적인 차이를 짚고, 실무에서 컴포넌트를 어떻게 설계해야 하는지 코드로 바로 확인해 볼게요. (근거자료: Smashing Magazine 원문)

Developer comparing CSS container queries and media queries on a code editor screen Coding Session Visual

핵심 차이: 미디어 쿼리는 '밖'을, 컨테이너 쿼리는 '안'을 본다

미디어 쿼리의 한계

아래 코드를 한번 볼까요?

@media (min-width: 1024px) {
  .card {
    display: flex;
  }
}

이 코드는 브라우저에게 딱 하나만 물어요. "지금 화면(뷰포트) 폭이 얼마야?"

문제는 이 .card가 1920px 데스크톱 화면 안의 300px짜리 그리드 셀에 들어갔을 때 발생합니다. 뷰포트는 여전히 1920px이니까 min-width: 1024px 조건이 발동되고, 300px 공간밖에 없는 카드에 display: flex가 적용되면서 레이아웃이 터져버리죠.

"미디어 쿼리는 멍청하다. 개념이 멍청한 게 아니라, 아는 게 너무 적다는 뜻이다." — Kevin Powell

컨테이너 쿼리로 바꾸면?

/* 부모를 컨테이너로 등록 */
.card-wrapper {
  container-name: card;
  container-type: inline-size;
}

/* 부모의 인라인(가로) 사이즈를 기준으로 판단 */
@container card (min-width: 450px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

이제 카드는 뷰포트에 관심이 없어요. 오직 "내 부모 래퍼가 450px 이상의 가로 공간을 줬나?" 만 확인합니다. 그렇다면 가로 배치, 아니면 기본 block 배치. 이게 컨테이너 쿼리의 본질이에요.

실전 예제: 뷰포트가 아니라 컴포넌트에 맞춘 유동 타이포그래피

미디어 쿼리 시절엔 vw 단위로 폰트를 조절했죠.

/* ❌ 뷰포트 기준 — 사이드바에 넣으면 폰트가 터짐 */
.card-title {
  font-size: clamp(100%, 1rem + 2vw, 24px);
}

컨테이너 쿼리는 자체 단위(cqi, cqw, cqb)를 제공합니다. clamp()와 조합하면 컴포넌트 크기에 맞춰 폰트가 유동적으로 변해요.

/* ✅ 컨테이너 인라인 사이즈 기준 */
.card-title {
  font-size: clamp(1rem, .5rem + 3cqi, 2rem);
}

이제 이 카드를 어디에 넣든, 폰트는 자기 컨테이너 안에서 알아서 조절됩니다.

Responsive card component layout adapting to container width in a browser devtools view Development Concept Image

주의사항: 컨테이너 쿼리의 3가지 부작용

도입 전에 반드시 알아야 할 함정이 있어요.

1. 컨테이너는 자기 자신을 쿼리할 수 없다

/* ❌ 무한 루프 — 동작하지 않음 */
.card {
  container-name: card;
  container-type: inline-size;
}
@container card (min-width: 400px) {
  .card { display: flex; }
}

컨테이너가 자기 자신의 사이즈를 보고 자기를 바꾸면 무한 루프가 되니까, 브라우저가 이를 막습니다. 반드시 부모 래퍼를 하나 더 두고 그 래퍼를 컨테이너로 등록해야 해요. 마크업이 한 단계 늘어나는 트레이드오프가 있습니다.

2. container-type: size는 레이아웃을 붕괴시킬 수 있다

/* ⚠️ 높이를 명시하지 않으면 0px로 붕괴 */
.hero-banner {
  container-type: size;
}

size는 가로·세로 둘 다 쿼리하겠다는 뜻인데, 브라우저는 자식 요소를 보지 않고 컨테이너 크기를 계산합니다. height나 aspect-ratio를 명시하지 않으면 높이가 0이 돼요. 특별한 이유가 없다면 inline-size만 쓰는 게 안전합니다.

3. 커스텀 프로퍼티는 쿼리 조건에 못 쓴다

:root { --breakpoint-lg: 1600px; }

/* ❌ 동작하지 않음 */
@container (min-width: var(--breakpoint-lg)) { /* ... */ }

커스텀 프로퍼티는 DOM을 따라 캐스케이드되는 값이라, 쿼리 조건 안에서 그 값이 다시 바뀔 가능성이 있어요. 브라우저가 순환 참조를 막기 위해 아예 조건부 사용을 금지합니다. 브레이크포인트를 변수로 관리하던 팀이라면 이 부분이 꽤 불편할 거예요.

언제 뭘 써야 하나? — 매크로 vs 마이크

  • 미디어 쿼리 (매크로 레이아웃): 페이지 전체 구조, 헤더/푸터, 메인 그리드, prefers-color-scheme, 터치 스크린 감지
  • 컨테이너 쿼리 (마이크 레이아웃): 카드, 위젯, 폼, 내비게이션 등 여러 컨텍스트에 재사용되는 컴포넌트

판단 기준은 간단해요. "이 컴포넌트가 한 곳에서만 쓰이나?" 그렇다면 미디어 쿼리, "여러 레이아웃에 재사용되나?" 그렇다면 컨테이너 쿼리입니다.

Frontend developer desk setup with browser responsive mode testing component layouts Technical Structure Concept

결론: 뷰포트는 '대리 지표'일 뿐이다

지금까지의 반응형 디자인은 뷰포트 폭이라는 **대리 지표(proxy)**에 의존해 왔습니다. 하지만 모던 웹에는 2,300개가 넘는 뷰포트 크기가 존재해요. 이걸 다 상정하는 건 애초에 불가능하죠.

컨테이너 쿼리는 반응형 디자인의 기준을 "화면"에서 "컴포넌트의 실제 공간"으로 옮깁니다. 이게 진짜 올바른 방향이에요.

한국 개발 생태계에서의 적용 맥락

국내 SI·에이전시 환경에서는 디자인 시스템이나 공통 컴포넌트 라이브러리를 만드는 팀에게 특히 효과적입니다. 다만 두 가지는 미리 짚어두세요.

  1. 레거시 브라우저 대응: 94% 지원율은 훌륭하지만, 공공기관·금융권 프로젝트에서 IE나 구형 엣지를 지원해야 한다면 폴리필 또는 @supports 분기가 필수입니다.
  2. 마크업 래퍼 증가: 컨테이너는 자기 자신을 쿼리할 수 없어서 래퍼 <div>가 하나씩 늘어납니다. 퍼블리셔와 협업하는 팀이라면 마크업 규칙을 사전에 합의해두는 게 좋아요.

다음 단계 학습 방향

  • Container Style Queries: 컴퓨티드 스타일에 반응하는 차세대 기능 (현재 실험 단계)
  • :has() 선택자와의 조합: 부모 상태에 따른 조건부 스타일링
  • CSS Nesting + @container: 컴포넌트 단위 CSS 캡슐화 패턴

함께 보면 좋은 글

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.