
기존 Media Query는 뷰포트(브라우저 창) 너비를 기준으로 스타일을 바꾼다.
@media (min-width: 768px) {
.card { display: grid; }
}
문제는 같은 컴포넌트가 넓은 영역에도, 좁은 사이드바에도 동시에 쓰일 때다.
┌─────────────────────────────────┐
│ 사이드바(좁음) │ 메인(넓음) │
│ [카드] │ [카드] │
└─────────────────────────────────┘
뷰포트 너비는 동일한데, 카드에게 주어진 공간은 다르다.
Media Query로는 이 두 경우를 구분할 수 없다.
Media Query의 한계를 마주하면 자연스럽게 이런 생각이 든다.
.sidebar .card { display: block; }
.main .card { display: grid; }
하지만 이 방식에도 문제가 있다.
반응형 대응이 안 된다. .sidebar 자체가 좁아지거나 레이아웃이 바뀌는 상황을 개발자가 직접 예측해서 Media Query로 커버해야 한다. 경우의 수가 늘어날수록 대응 코드도 함께 늘어난다.
레이아웃과 컴포넌트 스타일이 뒤섞인다. .card 스타일이 .sidebar, .main 같은 부모 맥락에 의존하게 되면서 CSS 파일 안에서 레이아웃 코드와 컴포넌트 코드의 경계가 흐려진다.
컴포넌트를 다른 곳에 옮기면 깨진다. .card가 .sidebar 안에 있을 때를 가정하고 작성된 스타일은, 다른 레이아웃으로 옮기는 순간 의존하던 부모 맥락이 사라지면서 스타일이 깨진다. 컴포넌트를 재사용하려면 부모 클래스까지 같이 챙겨가야 한다.
결국 부모 클래스 분기는 "부모가 자식을 제어" 하는 방식이다. 컴포넌트 스스로는 자기가 어떻게 보여야 하는지 알지 못한다.
부모 컨테이너의 너비를 기준으로 스타일을 바꾸는 CSS 기능이다. 부모 클래스 분기와 달리 "자기 환경을 스스로 감지" 한다. 외부 맥락에 대한 가정 없이, 자기에게 주어진 공간만 보고 스타일을 결정하기 때문에 어디에 놓든 독립적으로 동작한다.
스타일을 바꿀 기준이 될 부모 요소에 container-type을 선언한다.
.card-wrapper {
container-type: inline-size;
}
inline-size는 "가로 너비를 기준으로 쿼리하겠다"는 뜻이다.
/* 기본 스타일 */
.card {
display: block;
}
/* 컨테이너가 400px 이상일 때 */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
container-name으로 이름을 붙이고, container 단축 속성으로 한 줄로 쓸 수 있다.
/* 풀어서 쓰기 */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 단축 속성: container: <name> / <type> */
.card-wrapper {
container: card / inline-size;
}
/* 이름을 명시해서 특정 컨테이너 참조 */
@container card (min-width: 400px) {
.card { ... }
}
이름이 없으면 가장 가까운 컨테이너를 자동으로 참조한다. 컨테이너가 중첩된 경우 어떤 컨테이너를 기준으로 할지 명시하고 싶을 때 유용하다.
쿼리 조건으로 너비 외에도 다양한 값을 쓸 수 있다.
/* 기존 범위 문법 */
@container (min-width: 200px) and (max-width: 400px) { }
/* 최신 범위 문법 — 더 직관적 */
@container (200px <= width <= 400px) { }
/* 높이 기준 (container-type: size일 때만) */
@container (min-height: 300px) { }
/* 종횡비 */
@container (aspect-ratio > 1) { }
실무에서는 너비 기준 쿼리를 가장 많이 쓰고, 최신 범위 문법이 가독성이 좋아 점점 선호되는 추세다.
@container 스타일은 반드시 컨테이너의 자식에게만 적용된다.
.card-wrapper {
container-type: inline-size;
}
/* ❌ 컨테이너 자신에겐 적용 안 됨 */
@container (min-width: 400px) {
.card-wrapper { background: red; }
}
/* ✅ 자식에게만 적용됨 */
@container (min-width: 400px) {
.card { background: red; }
}
이 때문에 컨테이너 역할을 하는 wrapper와 실제 스타일을 받는 요소를 분리하는 패턴을 쓴다.
컴포넌트가 어디에 배치되든 스스로 적응할 수 있게 해주는 기능이다.
재사용성 높은 컴포넌트를 만들고 싶을 때 Media Query 대신 Container Query를 고려해볼 것.