본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 26. 18:11

【Frontend CSS – 파트 14】Style Query · Container Naming · 실전 패턴까지 철저 해설

요약

Container Query를 활용하여 뷰포트가 아닌 부모 요소의 크기와 스타일에 따라 반응형 스타일을 적용하는 방법을 설명합니다. 컨테이너 선언 방법, 이름 지정(Naming)을 통한 정교한 쿼리 제어, 그리고 실전 활용 패턴을 다룹니다.

핵심 포인트

  • Container Query는 뷰포트가 아닌 부모 컨테이너의 크기에 반응함
  • container-type을 통해 inline-size 또는 size를 설정 가능
  • container-name을 지정하여 중첩 구조에서도 정확한 컨테이너 타겟팅 가능
  • 이름이 없는 경우 가장 가까운 컨테이너를 자동으로 참조함

주의사항

이 기사는 AI의 지원을 받아 작성되었습니다.

제13회에서 말씀드렸듯이, media query가 알고 있는 것은 viewport뿐입니다. 카드가 sidebar 280px 안에 있는지, 4열 grid 안에 있는지는 알지 못합니다. 그 결과로 자주 발생하는 패턴은, variant를 늘리거나, props로 레이아웃을 조정하거나, CSS가 비대해지는데 컴포넌트는 재사용할 수 없게 되는 상황입니다.

Container Query는 바로 그 문제를 해결합니다. 컴포넌트가 화면 크기를 묻는 것이 아니라, 부모의 너비는 얼마인지 — 그리고 본 기사부터는 부모의 테마는 무엇인지 — 를 스스로 묻게 합니다.

제13회를 아직 읽지 않으신 분은 렌더링 파이프라인(rendering pipeline)과

ProfileCard

의 기초를 먼저 읽어주시기 바랍니다. 본 기사는 "시즌 2"이며, 총정리 편이 아닙니다.

Container Query부모 container의 크기 또는 스타일에 기반하여 스타일을 적용합니다. viewport가 아닙니다. 딱 3단계만 기억하세요. 주문하듯이 외우시면 됩니다: container-type을 선언 → @container로 조건을 작성 → 조건이 참이면 자식의 스타일이 적용된다.

의미용도
inline-size너비(inline 축)로 query가장 일반적 — 가로 방향의 responsive
size너비와 높이 모두로 query양쪽 축이 모두 필요할 때
normalsize query를 위한 container가 아님기본값 — size query에는 사용하지 않음
/* container 선언 */
.card-container {
container-type: inline-size; /* 너비만 query */
...
/* 이름 없음 — 가장 가까운 container를 query */
@container (max-width: 400px) {
.child {
...

container가 하나라면 간단합니다. 가장 가까운 container를 query하면 됩니다. 하지만 실제 페이지는 main 안에 sidebar, sidebar 안에 grid, 그 안에 card... 이런 구조일 때 이름을 붙이면, query하고 싶은 container를 정확하게 지정할 수 있어 CSS가 "잘못된 부모"를 잡는 것을 방지할 수 있습니다.

두 가지 작성 방식이 있습니다 — 익숙한 방법을 선택하세요:

/* 방법 1: container-name을 개별적으로 */
.card-container {
container-name: card;
...

3.2. @container에서 이름 사용하기

이름이 있으면 query는 정확해집니다. 이름을 생략하면 브라우저는 가장 가까운 container를 사용합니다. 하나뿐이라면 편리하지만, 다단계 중첩(nesting) 구조에서는 리스크가 있습니다.

/* container 이름 "card"가 500px보다 넓을 때만 적용 */
@container card (min-width: 500px) {
.card-title {
...

동일한 이름을 여러 container에 붙일 수 있습니다. sidebar에도 있고 main에도 있는 모든 card에 동일한 규칙을 적용하고 싶을 때 유용합니다. 하나의 @container에 여러 장소가 반응합니다. 편리하지만, 필요하지 않을 때는 남용하지 마세요.

/* 둘 다 이름은 "card" */
.sidebar-card {
container: card / inline-size;
...

CSS Containment Module Level 3에 따르면, 이름만으로 query할 수 있습니다. 크기 조건은 필요하지 않습니다. "card라는 이름의 container로 둘러싸여 있는가?"라면 스타일을 적용한다는 개념입니다.

/* container 이름만으로 query */
@container card {
.card-content {
...

주의: Name-only query는 2026년 5월부터 Baseline Newly available(Chrome 148+, Firefox 151+, Safari 26.5+) 됩니다. production 환경에서는 반드시 동작 확인을 거치세요.

Size query는 "부모의 너비가 몇 px인가?"에 답합니다. Style query는 여기서 더 나아가 "부모가 지금 어떤 상태인가?" — 다크 모드(dark mode), 컴팩트 모드(compact mode), 디자인 토큰(design token)... 에 답합니다. 이 글에서 제가 가장 좋아하는 파트입니다. React context 없이도 컴포넌트가 컨텍스트의 "분위기"를 읽을 수 있게 되기 때문입니다.

Style Query는 container의 스타일 값 (style value) (대부분 커스텀 프로퍼티 (custom property))을 query할 수 있습니다. 크기뿐만이 아닙니다.

기본 아이디어: container를 정의하고, 그 계산된 스타일 (computed style)에 기반하여 자손 요소에 조건부 스타일을 적용합니다.

Size query는 container-type 선언이 필요합니다. Style query는... 아무것도 필요 없습니다 — 모든 요소가 기본적으로 style container입니다. 브라우저가 알아서 처리해 줍니다.

모든 요소는 style container이므로, style을 query하는 데 container-name이나 container-type은 불필요합니다.

왜 그럴까요? Style query는 레이아웃 루프 (layout loop)를 일으키지 않기 때문입니다 — 자식의 스타일이 부모에게 역류하지 않습니다. size query와는 달리 containment (봉쇄)가 필수적이지 않습니다.

CSS Containment Module Level 3 명세에 따르면 모든 computed style을 query할 수 있습니다 — 다만 현재 브라우저들이 "제대로 지원"하고 있는 것은 커스텀 프로퍼티 (custom property)뿐입니다 (4.4 참조).

/* 커스텀 프로퍼티 query — 지원됨 */
@container style(--theme: dark) {
.card {
...

명세상의 예시 — production에 복사하지 마세요 (광범위하게 미지원)

/* font-style query — 명세에는 있으나 광범위하게 미지원 */
@container style(font-style: italic) {
em,
...

명세는 모든 property-value 쌍의 query를 꿈꾸고 있습니다. 실제 브라우저가 지원하는 것은 Custom Properties — 즉 CSS 변수뿐입니다. 그 외의 것들은... 위의 <details>를 살펴보는 정도에 그칩니다.

Chrome for Developers에 따르면, 명세상으로는 font-weight: 800과 같은 query도 가능하지만, 구현은 현 시점에서 커스텀 프로퍼티 (custom property)만 가능합니다.

/* ✅ 유효 — 커스텀 프로퍼티 query (지원됨) */
@container style(--size: large) {
.text {
...

좋은 소식: 커스텀 프로퍼티를 위한 style query는 2026년 5월부터 Baseline Newly available입니다.

각 카드에 className={isDark ? 'dark' : ''}를 붙이는 대신, 부모 container에 --theme를 설정하면 — 내부의 카드가 자동으로 이를 인지합니다.

/* container에서 테마를 선언 */
.app {
--theme: dark;
...

제13회에서 렌더링 파이프라인 (rendering pipeline)을 다루었습니다. 본 글과 직결되는 부분만 복습하겠습니다:

기억해야 할 점: @containercontainer의 레이아웃 (layout) 이후에 평가됩니다 — 브라우저는 즉시 스타일을 적용하지 않고, 재계산이 필요한 자손 요소에 마크를 남깁니다. 그래서 resize 시에 컴포넌트가 순간적으로 "튀는" 현상이 발생할 수 있는데, 이는 버그가 아니라 브라우저가 올바르게 작동하고 있다는 (조금 느리다는) 증거입니다. 모든 <div>container-type을 스티커처럼 붙일 필요가 없는 이유이기도 합니다.

제13회의 ProfileCard

는 size query (크기 쿼리)의 입문이었습니다. 여기서는 DashboardCard로 업그레이드합니다 — named container (이름이 지정된 컨테이너), 의미 있는 breakpoint (중단점), 다크 모드 (dark mode)용 style query (스타일 쿼리). 컴포넌트 1개, 컨텍스트 3개, variant props 없음.

심플한 대시보드 카드 — 하지만 어디에 두어도 살아남을 수 있는 구조:

  • 표시 내용: title (제목), value (값), icon (아이콘), trend indicator (추세 표시기)
  • container (컨테이너)
    > 500px → 가로 레이아웃 (icon 왼쪽, 텍스트 오른쪽) - container
    300–500px → 세로 레이아웃, 모든 정보 표시 - container
    < 300px → trend (추세) 숨김, 폰트 축소 (좁은 sidebar (사이드바)에 성장 그래프는 불필요) - 부모에게
    --theme: dark

→ 자동 다크 모드 (dark mode)

import React from 'react';
import './DashboardCard.css';
import { TrendingUp, TrendingDown } from 'lucide-react';
...
/* ============================================
STEP 1: 기본 스타일 (구형 브라우저를 위한 폴백/fallback)
============================================ */
...

Style query (스타일 쿼리)와 상속: DarkThemeContainer가 부모에게 --theme: dark를 설정합니다. 변수는 .dashboard-card-container (container-name: dashboard-card)에 상속 (inheritance) 되므로, 카드 측에서 다시 설정하지 않아도 @container dashboard-card style(--theme: dark)가 작동합니다. CSS 상속 — 오래된 기능이지만, style query와 결합하면 매우 유용합니다.

App.tsx & App.css — 3가지 컨텍스트 데모 레이아웃

import React from 'react';
import { DashboardCard } from './DashboardCard';
import { DollarSign, Users, BarChart } from 'lucide-react';
...
.main-content {
width: 800px;
padding: 20px;
...

동일한 DashboardCard, 3가지 컨텍스트 — variant 없음, 장소별 media query (미디어 쿼리)도 없음:

컨텍스트container (컨테이너) 너비레이아웃비고
Main content~800px가로 배열, value (값) 크게Size query min-width: 501px
Sidebar~250px세로 배열, 폰트 작게trend indicator (추세 표시기) 숨김
Dark container~400px세로 배열 + 다크 테마Style query --theme: dark

자주 사용하는 패턴 몇 가지 — 복사하여 design system (디자인 시스템)에 맞춰 breakpoint (중단점)를 조정하세요.

grid (그리드)의 열 개수가 **container (컨테이너)**에 따라 변합니다 — viewport (뷰포트)가 아닙니다. sidebar (사이드바)가 좁으면 1열, main (메인)이 넓으면 4열 — 동일한 grid 컴포넌트입니다.

.product-grid {
container-type: inline-size;
container-name: grid;
...

cqi + clamp() — 폰트가 container (컨테이너)에 맞춰 스케일링되며, 화면 전체가 아닙니다. sidebar (사이드바)의 카드가 랜딩 페이지의 hero (히어로) 섹션과 동일한 글자 크기가 되는 일은 없습니다.

.article-container {
container-type: inline-size;
}
...

Container query는 중첩 (Nest) 할 수 있습니다 — outer가 전체 레이아웃을 담당하고, inner가 내부를 미세 조정합니다. 하지만 너무 깊게 중첩하면 성능도 떨어지고 머리도 아파집니다.

.outer {
container: outer / inline-size;
}
...

제13회에서 fluid layout의 cqi에 대해 다루었습니다. 여기서는 목록표를 확인하고 — 제15회에서 clamp()와 viewport units를 사용한 페이지 전체의 fluid typography를 심도 있게 다룹니다. 본 기사는 컴포넌트 수준까지 다룹니다.

단위의미유사 단위
cqicontainer의 inline-size의 1%vi (viewport inline)
cqbcontainer의 block-size의 1%vb (viewport block)
cqwcontainer의 width의 1%vw
cqhcontainer의 height의 1%vh
cqmincqi와 cqb 중 작은 값vmin
cqmaxcqi와 cqb 중 큰 값vmax

vw / vh와의 차이점: cqi / cqb는 query container 기준이며, viewport가 아닙니다. 타이포그래피와 spacing이 컴포넌트 단위로 작동하므로 — 사이드바(sidebar)가 히어로(hero) 섹션과 동일한 폰트 크기가 되는 일은 없습니다.

.card-title {
/* 폰트 크기 = container 너비의 2~4% */
font-size: clamp(1rem, 3cqi, 2.5rem);
...

Container Query를 막 시작했을 때 자주 나오는 질문. 요약하자면: 각각 별개의 질문에 답하는 것이며 — 어느 하나가 다른 하나를 완전히 대체하는 것이 아닙니다. 잘못 사용하면 나사로 못을 박는 것과 같습니다 — 할 수는 있지만, 장기적으로는 고통스럽습니다.

상황사용하는 것이유
페이지 전체의 responsive (header, footer, 메인 그리드)Media Queryviewport가 적절한 컨텍스트를 제공함
재사용 컴포넌트 (card, sidebar, widget)Container Query부모 container에 적응할 필요가 있음
컨텍스트에 따른 테마 · Design TokenStyle Query부모 container의 스타일을 query 함
React의 로직 변경 (state, props)JavaScriptCSS는 로직의 대체재가 될 수 없음
Media QueryContainer QueryStyle Query
query의 기준Viewport부모 containercontainer의 스타일
...

원칙: Container Query는 Media Query를 보완하는 것이지 — 대체하는 것이 아닙니다. 컴포넌트가 고정 레이아웃에만 배치된다면 @media가 더 심플합니다. 오버엔지니어링(over-engineer) 하지 마세요.

Container Query는 공짜가 아닙니다 — container-type마다 브라우저가 유지(maintain)해야 하는 context가 늘어납니다. container를 resize하면 → @container를 재평가합니다. 애니메이션으로 resize가 계속된다면 → 브라우저도 사용자도 힘들어집니다.

상황영향도
component boundary에 몇 개의 container가 있는 경우무시할 수 있음
모든 div에 container-type을 설정한 경우다수의 context로 인해 느려짐
container의 연속적인 resize (애니메이션)연속적인 재평가 발생
다수의 container를 가진 큰 DOMcontext 유지로 인한 CPU 소비

황금률: container-typecomponent boundary — 가장 바깥쪽 wrapper에만 설정하세요. 모든 <div>

를 query container로 만들지 마세요. DOM은 크리스마스 트리(Christmas tree)가 아닙니다.

/* 오래된 브라우저를 위한 폴백 (Fallback) */
.dashboard-card {
/* 기본 스타일 */
...

387px

라는 breakpoint(중단점)는 특정 화면에서 trial-and-error(시행착오)를 거친 결과인 경우가 많으며, 반년 후에는 아무도 그 이유를 기억하지 못합니다. 의미 있는 이름을 사용하세요: compact, medium, expanded.

/* ✅ 좋음 — 의미 있는 breakpoint */
@container (max-width: 300px) {
/* compact */
...
/* 페이지 전체 레이아웃용 Media Query */
@media (max-width: 768px) {
.dashboard-grid {
...
/* ✅ 좋음 */
.sidebar {
container-name: sidebar;
...

container-type은 대응하는 containment(봉쇄)를 자동으로 활성화합니다 — inline-sizecontain: layout inline-size, sizecontain: layout size. Container Query는 CSS Containment 생태계의 일부이며, 단독으로 떠 있는 기능이 아닙니다.

Query가 작동하지 않나요? 브라우저 탓을 하기 전에 이 체크리스트를 확인하세요 — 90%는 container-type을 작성하지 않았거나 container 이름의 실수입니다.

container-type을 선언했는가?

Size query에는 container-type: inline-size 또는 size가 필요합니다. 이를 잊으면 query는 침묵합니다 — CSS는 에러를 내지 않고, 그저 아무것도 하지 않을 뿐입니다.

container 이름이 올바른가?

@container sidebar (...)라고 썼는데 container-name: sidebar가 없다면 → 작동하지 않습니다.

Style Query가 Custom Property(사용자 정의 속성)를 사용하고 있는가?

현재는 커스텀 프로퍼티만 가능합니다. @container style(font-style: italic)을 사양(specification) 단계에서 그대로 production(운영 환경)에 복사하지 마세요.

@supports로 폴백(Fallback)을 준비했는가?

오래된 브라우저는 여전히 존재합니다 — 기본 스타일만으로도 충분히 사용할 수 있어야 합니다.

overflow: hidden의 영향은 없는가?

container의 크기가 변하면 → query의 결과가 예상과 다를 수 있습니다.

브라우저 지원(Browser Support)을 확인했는가?

Size + style query(커스텀 프로퍼티)는 2026년 5월부터 Baseline Newly available입니다 — 그럼에도 ship(배포) 전에 MDN이나 Can I Use를 확인하세요.

구문(Syntax)이 올바른가?
/* 올바름 */ @container (min-width: 400px) { } @container sidebar (min-width: 400px) { } @container style(--theme: dark) { } /* 틀림 */ @container sidebar min-width: 400px { } /* 괄호가 없음 */

layout loop(레이아웃 루프)가 발생하지 않는가?

Container query는 containment 덕분에 루프가 발생하지 않습니다. Style query도 마찬가지입니다 — 안심하세요, 이번에는 당신의 잘못이 아닙니다 (아마도).

본 기사는 제13회 내용을 production에서 자주 사용하는 3가지로 보완합니다:

Container Naming — DOM이 중첩되어 있을 때 올바른 container를 query
Style Query — 커스텀 프로퍼티를 통한 responsive(테마, 토큰)
DashboardCard — size + style query, @supports 폴백 포함

Media Query → 페이지가 넓은가 좁은가?
Container Query → 컴포넌트가 얼마만큼의 공간을 가지고 있는가?

Style Query → 컨텍스트의 테마는 무엇인가?

상세한 비교표는 섹션 9를 참조하세요.

  • MDN: CSS container queries
  • MDN: @container 어트리뷰트 (@container rule)
  • web.dev: Container queries
  • web.dev: New to the web platform in May 2026
  • web.dev: May 2026 Baseline monthly digest
  • W3C CSS Containment Module Level 3

"768px 화면에서 카드가 어떻게 보이는가"라고 묻는 것은 그만둡시다. 대신 "컨테이너 (container)가 300px일 때, 카드에 무엇이 필요한가"— 그리고 "부모가 dark 모드일 때, 카드는 스스로 이를 인지할 수 있는가"라고 물으세요.

그것이 바로 컴포넌트를 진정으로 재사용 가능한 디자인 시스템 (Design System)으로 만드는 방법입니다 — 하나의 코드베이스로 모든 컨텍스트에 대응하는 것. 이것은 마법이 아니라, 올바른 질문을 던지는 것뿐입니다.

【Frontend CSS – 파트 15】Fluid Typography 완전 가이드 — clamp() · 뷰포트 (viewport) 단위 · 확장 가능한 글자 크기 설계

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0