07 — 반응형 (Responsive)
이 챕터가 답하는 질문: 반응형이란 디바이스에 적응하는 것이 아니라 컨텍스트에 적응하는 것이다. 그렇다면 컨텍스트는 어디서 오는가?
한 줄 답 (Pyramid Top)
반응형 CSS는 5개의 컨텍스트 축에 반응한다 — ① 뷰포트(
@media) ② 부모 컨테이너(@container) ③ 자식 존재(:has()) ④ DOM 범위(@scope) ⑤ 사용자 환경(prefers-*,dvh). 디바이스가 아니라 컨텍스트다. 같은 컴포넌트가 사이드바에서 좁게, 본문에서 넓게 — 뷰포트가 아니라 컨테이너가 결정하는 시대가 되었다.
Why — 왜 반응형이 필요한가
1) 역사적 출발: 2010년 Ethan Marcotte
2010년 5월 25일, A List Apart에 발표된 한 편의 글이 웹 디자인을 갈라놓았다 — Ethan Marcotte의 “Responsive Web Design”. 그 글의 핵심 주장:
“우리는 디바이스마다 다른 사이트를 만들 수 없다. 하나의 사이트가 모든 디바이스에 적응해야 한다.”
세 가지 도구를 묶었다 — Fluid Grid + Flexible Images + Media Queries. 이전까지 우리는 m.example.com 같은 별도 모바일 사이트를 만들었다.
2) 그러나 14년 동안 풀리지 않은 한계
- 뷰포트만 본다: 같은 카드를 사이드바·본문에 넣어도 뷰포트가 같으면 같은 스타일. 컴포넌트 단위 반응이 불가능했다.
- 부모를 모른다: CSS는 자식만 선택할 수 있었다. 자식의 존재 여부로 부모 스타일을 바꿀 수 없었다.
- 격리가 없다: 컴포넌트의 스타일이 트리 전체로 새어 나갔다 — BEM, CSS Modules 같은 우회로 해결했다.
- 뷰포트 단위가 거짓말: iOS Safari의
100vh는 주소창을 포함한 최대 높이 — 실제 보이는 영역보다 컸다.
3) 2022~2024년의 합류 — 4가지 신무기
| 신무기 | 표준화 | 해결한 문제 |
|---|---|---|
@container | 2023 Baseline | ”뷰포트가 아니라 부모”에 반응 |
:has() | 2023 Baseline | 부모 선택자 — 자식의 존재로 부모를 선택 |
@scope | 2024 Chrome/Safari, Firefox 미지원 | 컴포넌트 스타일 격리 |
dvh/svh/lvh | 2023 Baseline | 동적 뷰포트 높이 — iOS 주소창 문제 해결 |
14년 만에 반응형의 정의가 뷰포트 반응에서 컨텍스트 반응으로 확장되었다.
How — 어떻게 정리했나
이 챕터는 반응형의 5개 축을 작은 것에서 큰 것으로 — 부분 → 전체의 순서로 다룬다.
| # | 파일 | 다루는 것 | 핵심 키워드 |
|---|---|---|---|
| 1 | 01-media-queries | @media 전체, range syntax, prefers-* | width, hover, pointer, prefers-color-scheme |
| 2 | 02-container-queries | @container, container-type, cqi/cqw/cqb | container-type, inline-size, cqi |
| 3 | 03-has-selector | :has() 부모 선택자 패턴 | :has, parent-aware, sibling-based |
| 4 | 04-scope | @scope, donut scope, nesting과 결합 | @scope, donut, scoped |
| 5 | 05-viewport-units | dvh/svh/lvh, iOS Safari 이슈 | dvh, svh, lvh, dynamic viewport |
| 6 | 06-fluid-design | clamp(), fluid grid, intrinsic web design | clamp, minmax, fluid type, Jen Simmons |
| 7 | 07-mobile-first | 모바일/데스크탑 퍼스트, 브레이크포인트 전략 | mobile-first, RWD, breakpoint |
What — 이 챕터의 범위
- CSS Conditional Rules Module Level 3/4 —
@media,@supports, range syntax - CSS Containment Module Level 3 —
@container,container-type,container-name - Selectors Level 4 —
:has(),:is(),:where() - CSS Cascading and Inheritance Level 6 —
@scope - CSS Values and Units Level 4 —
dvh/svh/lvh,cqi/cqw/cqb,clamp() - CSS Media Queries Level 5 —
prefers-color-scheme,prefers-reduced-motion,hover,pointer
브라우저 호환성은 Baseline 2024 기준 — @scope는 Firefox 미지원(2026-05 기준 진행 중).
What-if — 잘못 쓰면
| 증상 | 원인 | 해결 |
|---|---|---|
| 컨테이너 쿼리가 동작 안 함 | container-type 미지정 — 기본 normal은 쿼리 대상 아님 | 부모에 container-type: inline-size |
@container (height > 400px) 무시됨 | inline-size는 인라인 축만 — 높이 쿼리는 size 필요 | container-type: size + layout containment 비용 인지 |
iOS Safari에서 100vh가 잘림 | vh는 최대 뷰포트(주소창 숨김 가정) | 100dvh(동적) 또는 100svh(작은쪽) |
:has() 사용 후 리렌더 jank | 매 DOM 변경마다 재평가, 큰 트리에서 비싸짐 | 범위를 좁히기 — .card:has(...) 식으로 앵커 명시 |
Firefox에서 @scope 무시 | 2026-05 기준 Firefox 미지원 | @supports (at-rule: @scope) 가드, 폴백 BEM/CSS Modules |
| 데스크탑에서 작업 후 모바일이 깨짐 | 데스크탑 퍼스트 — max-width 사슬이 길어짐 | 모바일 퍼스트로 전환, min-width만 사용 |
Insight — “디바이스 → 컨텍스트”의 14년
2010년의 RWD는 하나의 적과 싸웠다 — 디바이스 파편화. iPhone 320px, iPad 768px, Desktop 1024px.
그러나 2024년의 적은 다르다 — 컴포넌트 재사용성. 같은 카드 컴포넌트가 사이드바(280px), 본문(680px), 풀폭(1200px)에서 다르게 보여야 한다. 뷰포트는 같다.
이 변화를 3개의 키워드로 압축할 수 있다.
- From device to context (디바이스 → 컨텍스트) —
@container - From child to parent (자식 → 부모) —
:has() - From global to local (전역 → 지역) —
@scope
Jen Simmons는 2018년 강연 “Everything You Know About Web Design Just Changed” 에서 이를 Intrinsic Web Design이라고 명명했다. 그녀의 정의:
“미디어 쿼리에 의존하지 않고,
grid+minmax+clamp로 콘텐츠 자체가 공간에 적응하도록 두는 디자인.”
이 챕터의 6번 문서(06-fluid-design)가 그 철학을 코드로 옮긴다.
요약 + Mermaid
- 반응형은 디바이스가 아니라 컨텍스트에 적응한다.
- 5개 축 — 뷰포트 / 컨테이너 / 부모(
:has) / 범위(@scope) / 사용자 환경(prefers-*). - dvh/svh/lvh로 iOS Safari
100vh잘림 문제 해결. - Intrinsic Web Design —
clamp + grid + minmax로 미디어 쿼리 없이 적응. - 2010년 RWD → 2024년 컨텍스트 적응, 14년 진화.
다음: 01-media-queries부터 순서대로.