본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 26. 17:32

【Frontend CSS – 파트 13】 브라우저 관점에서의 반응형 레이아웃: 왜 Media Query만으로는 불충분한가?

요약

Media Query의 한계를 지적하며, 컴포넌트 중심의 현대적 웹 디자인을 위한 Container Query와 Intrinsic Web Design의 필요성을 설명합니다. 브라우저의 렌더링 파이프라인 내에서 컨테이너 쿼리가 평가되는 과정을 통해 레이아웃 메커니즘을 분석합니다.

핵심 포인트

  • Media Query는 뷰포트 기준이라 컴포넌트 재사용성이 낮음
  • 현대적 반응형은 컴포넌트가 할당받은 공간에 맞춰 설계해야 함
  • Intrinsic Web Design은 콘텐츠와 컨텍스트 기반의 설계를 지향함
  • Container Query는 브라우저 레이아웃 패스 과정에서 평가됨

주의

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

이런 경험, 없으신가요?

케이스 1: ProductCard를 만들었다 — 메인 페이지에서는 완벽하다. 디자이너가 "sidebar 280px 안에 넣어줘"라고 말한다. 레이아웃이 넘치고, 글자가 겹치고, 이미지가 왜곡된다. DevTools를 열어 @media (max-width: 768px)를 추가한다 — OK. 모달(modal)에 넣는다 — 또 깨진다. max-width: 480px를 추가한다. 4열 그리드(grid)에 넣는다 — 또 깨진다. 동일한 컴포넌트를 무수히 많은 컨텍스트(context)에 맞춰 조정하고 있으며, media query는 CSS를 부풀릴 뿐 근본적인 원인을 해결하지 못하고 있다. -
케이스 2: 네비게이션 바(navbar) — 대화면에서는 로고 왼쪽, 메뉴 중앙, 아바타 오른쪽, 소화면에서는 햄버거 메뉴. 브레이크포인트(breakpoint) 768px를 작성하고 안심한다. 다음 날 아침 상사가 "이 navbar를 관리자 화면의 sidebar에 넣어줘 — sidebar는 300px지만"이라고 말한다. media query는 sidebar의 너비를 모른다. 알고 있는 것은 viewport뿐이다.

요점: media query는 페이지 레벨의 반응형(responsive)에 뛰어난 도구입니다 — 전체 레이아웃 전환, 사이트 전체의 패딩(padding) 조정, 마우스가 있는 장치의 hover 처리 등. 어디에나 둘 수 있는 컴포넌트 시스템을 만들 때는 재사용의 장벽이 되기 쉽습니다.

모던한 반응형은 "화면 크기에 맞춰 설계하는 것"이 아니라, "컴포넌트가 실제로 받는 공간에 맞춰 설계하는 것"입니다.

Responsive Web Design (RWD) — Ethan Marcotte가 2010년경에 제창 — 는 웹을 만드는 방식을 바꾸었습니다. media query가 기둥이었습니다. 하지만 웹은 변했습니다. 이제는 더 이상 "페이지"뿐만 아니라 sidebar, modal, grid 등 어디에나 나타나는 컴포넌트 시스템을 만들고 있습니다.

Jen Simmons는 새로운 방향성을 Intrinsic Web Design이라고 부릅니다 — 고정된 breakpoint로 외부에서 제어하는 것이 아니라, 콘텐츠와 컨텍스트(context)에 기반하여 설계하는 것입니다.

요컨대: "화면이 몇 px인가"라고 묻는 것이 아니라, "이 컴포넌트는 지금 얼마만큼의 공간이 있는가"라고 묻는 것입니다. 단순하게 들리지만, CSS 작성 방식이 근본적으로 바뀝니다.

반응형이 렌더링(rendering)의 어디에 위치하는지 — 그리고 컴포넌트의 레이아웃이 왜 순간적으로 "튀는지" — 를 이해하기 위해, 제1회부터 소개해 온 파이프라인(pipeline)과 Container Query가 평가되는 타이밍을 정리합니다.

실제 순서:

  • 브라우저가 DOM과 CSSOM을 만들고, Render Tree에 결합
  • 포매팅 컨텍스트(formatting context) (Block / Flex / Grid), 가용 공간(available space), 고유 크기(intrinsic size), 외적 제약(extrinsic constraints)을 확정
  • Layout pass 1 — container를 포함한 기본 크기를 계산
  • container를 측정 (inline-size, block-size) → Container Query를 평가 — 새로운 스타일로 인해 크기가 변하면
  • Layout pass 2 — Paint 및 Composite

Container Query는 처음부터 실행되지 않습니다 — 브라우저는 먼저 container의 너비를 알아야 합니다. 추가적인 layout pass가 발생할 수는 있지만, 파이프라인 외의 해킹(hack)은 아닙니다.

이 순서를 이해하면 디버그(debug)에 도움이 됩니다. 레이아웃이 "튀는" 것은 query가 발화하여 크기를 바꾸는 스타일이 적용되었기 때문이며, 무작위 버그인 경우가 많습니다. 브라우저에는 최적화도 있어 항상 pass 2가 필요한 것은 아닙니다.

media query가 알고 있는 것은 두 가지뿐입니다:

  • viewport의 크기
  • 브라우저의 기본 font size (:root에 설정한 font-size가 아님)
html {
font-size: 32px;
}
...

질문: 배경색이 변하는 시점의 viewport는 몇 px인가요?

1120px

(35 × 32)라고 생각했다면 — 그것은 틀렸습니다.

media query (미디어 쿼리)는 당신이 설정한 font-size를 보지 않습니다. 브라우저의 기본 font size (기본 글꼴 크기) — 보통 16px — 를 봅니다. 실제 breakpoint (중단점): 35 × 16 = 560px.

저도 이 부분을 착각해서 반나절 동안 debug (디버깅) 한 적이 있습니다.

min-width / max-width 조건 내의 rem / em은, :root의 font-size가 아니라 브라우저의 initial font-size (초기 글꼴 크기) (보통 16px)를 기반으로 합니다. breakpoint = rem 값 × 16.

이 지점이 media query가 가장 "맹목적인" 부분입니다.

Viewport (화면)
────────────────────────────────────────────────────
│ │
...

media query는 viewport = 1180px → "대화면" 스타일을 적용합니다. 하지만 sidebar (사이드바) 내의 카드는 280px밖에 되지 않습니다. 레이아웃이 깨집니다 — 새로운 breakpoint를 추가합니다 — 그러면 또 다른 컨텍스트 (문맥)에서 깨집니다. 익숙한 루프입니다.

유스케이스 (Use case)예시
페이지 레벨 레이아웃2열에서 1열로 전환
...@media (hover: hover)를 통한 마우스 사용 기기
글로벌 typography (타이포그래피)페이지 전체의 font-size 조절

여러 컨텍스트에서 재사용하는 컴포넌트 → Container Query (컨테이너 쿼리)가 더 적합한 경우가 많습니다. media query가 나쁜 것이 아니라, 사용하는 용도가 다를 뿐입니다.

Container query는 viewport가 아니라 부모 container (컨테이너)의 크기를 기반으로 요소에 스타일을 적용할 수 있습니다. 같은 컴포넌트라도 container가 다르면 레이아웃이 달라집니다. viewport를 물어볼 필요가 없습니다.

/* 단계 1: container 정의 */
.card-container {
container-type: inline-size; /* 너비만 추적 */
...

2단계: container를 선언하고, query (쿼리) 합니다. 간단합니다 — 어려운 점은 어떤 요소를 container로 만들지 선택하는 것입니다.

단위의미
cqicontainer의 inline size (인라인 크기)의 1% (보통 너비)
cqbcontainer의 block size (블록 크기)의 1% (보통 높이)
.card {
font-size: max(2cqi, 14px);
padding: 3cqi;
...

typography (타이포그래피)와 spacing (간격)이 container에 비례합니다 — 사이즈마다 breakpoint를 일일이 작성할 필요가 없습니다.

제11회에서 min-content, max-content, fit-content와 브라우저의 intrinsic size (고유 크기) 계산을 자세히 다루었습니다. 본 기사에서는 불필요한 media query를 피하기 위해 Container Query와 조합하기 쉬운 2가지 도구만을 소개합니다.

.card-title {
width: fit-content;
max-width: 100%;
...

콘텐츠에 맞춰 확장되지만, 부모 container로부터는 벗어나지 않습니다 — 좁은 sidebar 내의 카드에 유용합니다.

.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
...

auto-fit: 사용 가능한 공간에 따라 열을 자동으로 생성 -
minmax(250px, 1fr): 각 열은 최소 250px -
media query 불필요 — container가 좁으면 열이 줄어들고, 넓으면 늘어납니다.

레이아웃 레벨에서의 "공짜로 쓸 수 있는" responsive (반응형)입니다. intrinsic sizing (내재적 크기 지정)을 근본적으로 이해하고 싶다면 제11회로 돌아가세요.

/* 페이지 레벨: viewport (뷰포트)에 비례 */
.page-title {
font-size: clamp(1rem, 2.5vw, 2rem);
...

vw%페이지 전체의 typography (타이포그래피)에 적합합니다. viewport (뷰포트)나 extrinsic (외재적)인 부모 요소를 참조하기 때문입니다. sidebar (사이드바) 280px에도, main (메인) 900px에도 배치될 수 있는 컴포넌트라면, cqi가 vw보다 더 안전합니다 — 글자가 화면이 아닌 실제 container (컨테이너)에 비례하기 때문입니다.

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

글자 크기는 container (컨테이너)에 따라 변하지만, 1rem 미만으로는 내려가지 않습니다 — 어떤 크기에서도 읽기 쉬운 상태를 유지할 수 있습니다.

Container Query (컨테이너 쿼리) 이전에는 유사한 작업을 JavaScript (자바스크립트)로 수행해야 했습니다 — 종종 임시방편적인 hack (해킹)으로서 말이죠. 저도 ResizeObserver를 사용한 컴포넌트를 유지보수했던 적이 있습니다 — unmount (언마운트) 할 때마다 disconnect()를 잊지 말아야 한다는 점이나, 연속적인 resize (리사이즈) 시 발생하는 race condition (경쟁 상태) 같은 문제들 말입니다. Container Query는 이러한 번거로움에서 해방시켜 줍니다.

새로운 방법 — Container Query (컨테이너 쿼리):

@container (inline-size > 400px) {
.card {
flex-direction: row;
...

기존 방식 — ResizeObserver + class (클래스) 전환 (참고)

const observer = new ResizeObserver((entries) => {
for (let entry of entries) {
const width = entry.contentRect.width;
...
.card.wide {
flex-direction: row;
}

비교:

구분ResizeObserver + JSContainer Query
Performance (성능)오버헤드 (JS 실행)네이티브 (브라우저 최적화)
...

동일한 카드에 5번째 @media를 작성하기 전에, 이 플로우차트(flowchart)로 확인하세요:

인쇄해서 모니터 옆에 붙여두어도 좋을 정도입니다 — 감각에 의존해 breakpoint (중단점)를 추가하는 것보다 훨씬 낫습니다.

Container Query를 사용한 ProfileCard — 동일한 컴포넌트를 sidebar (사이드바), main (메인), grid (그리드)에 배치해도 자동으로 적응합니다.

import React from 'react';
import './ProfileCard.css';
interface ProfileCardProps {
...

ProfileCard.css — Container Query (클릭 시 전개)

.profile-card-container {
container-type: inline-size;
}
...
import React from 'react';
import { ProfileCard } from './ProfileCard';
import './Dashboard.css';
...
.sidebar { width: 280px; }
.main { flex: 1; }
.grid {
...

ProfileCard에는 media query (미디어 쿼리)가 단 하나도 없습니다. container (컨테이너)에 따라 자동으로 적응합니다 — sidebar (사이드바) 280px이든 main (메인)이 넓든 상관없습니다.

두 번째 그림 (선택 사항): 두 가지 컨텍스트 (context)를 크롭 (crop) — sidebar (사이드바, 세로 레이아웃, stats (통계)가 가로로 나열됨)와 main (메인, 가로 레이아웃, stats (통계)가 오른쪽에 위치함). 공개하기 전에 IMAGE_URL_LAYOUT_COMPARE를 교체해 주세요.

Container Query는 다음 환경에서 지원됩니다:

  • Chrome 105+ (2022년 8월)
  • Firefox 110+ (2023년 2월)
  • Safari 16.0+ (2022년 9월)
  • Edge 105+

Baseline "Newly available" 2023에 따르면, 많은 신규 프로젝트에서 큰 걱정 없이 사용할 수 있습니다. 레거시(Legacy) 프로젝트에는 폴백(Fallback)을 준비하세요.

실제 서비스에서 안전하게 사용하려면 @supports를 사용합니다:

/* 폴백: 오래된 브라우저를 위한 media query */
.profile-card {
  flex-direction: column;
  ...
}

점진적 향상(Progressive Enhancement): 폴백이 먼저 작동하고, 지원하는 브라우저에서는 Container Query가 이를 덮어씁니다.

media query를 통한 폴백은 근사치일 뿐입니다 — 여전히 뷰포트(viewport)를 보고 있기 때문에, 카드가 사이드바(sidebar) 280px 안에 있는지 메인(main) 900px 안에 있는지는 알 수 없습니다. 오래된 브라우저를 위해 사용하되, 모든 컨텍스트에서 Container Query의 완벽한 대체재가 될 수는 없습니다.

다시 @media를 작성하기 전에, 이 체크리스트를 확인하세요:

  • 컴포넌트의 문제인가, 페이지 레이아웃의 문제인가?
    • 컴포넌트 → Container Query
    • 페이지 레이아웃 → Media Query
  • 내재적 크기 조절 (intrinsic sizing)로 해결할 수 없는가? (fit-content — 자세한 내용은 제11회 참고)
  • clamp() / cqi로 부드럽게 스케일링할 수 없는가?
  • 부모 요소에 container-type을 선언했는가?
  • 브라우저 지원 여부를 확인하고, 폴백을 준비했는가?
  • Container Query의 성능을 고려했는가?
    • 추가적인 레이아웃 패스(layout pass)가 발생할 가능성 — 너무 깊게 중첩하지 말 것

브라우저에는 강력한 레이아웃 알고리즘이 있습니다. 현대적인 반응형(responsive) 설계는 브라우저를 자신의 의도에 억지로 맞추는 것이 아니라, 브라우저가 최적의 해답을 선택할 수 있도록 충분한 정보를 제공하는 것입니다.

  • Media Query는 뷰포트(viewport)를 보고, Container Query는 부모 컨테이너(container)를 봅니다.
  • 컴포넌트는 스스로의 반응형(responsive) 상태를 관리해야 합니다.
    • 내재적 크기 조절 (intrinsic sizing) + auto-fit grid로 브레이크포인트(breakpoint) 없는 적응이 가능합니다 (제11회 참고).
    • 부드러운 경험을 위해: 컴포넌트는 cqiclamp()를, 페이지는 vw를 우선시합니다.
  • Container Query는 추가적인 레이아웃 패스(layout pass)를 초래할 가능성이 있습니다 — 의도를 가지고 사용하며, 깊은 중첩은 피하세요.

가까운 미래의 트렌드:

  • 앵커 포지셔닝 (Anchor Positioning, 2024+): 다른 요소를 기준으로 배치 — popover나 tooltip이 position: absolute와 수동 계산에 의존하지 않게 됩니다.
  • 스타일 쿼리 (Style Query, 개발 중): 컨테이너(container)의 CSS 커스텀 프로퍼티(custom property) 값으로 쿼리 — 크기뿐만 아니라 상태에 따른 반응형 설계가 가능해집니다.
  • 미래의 CSS는 뷰포트(viewport) 의존도가 낮아지고, 실제 컨텍스트(context) 의존도가 높아질 것입니다.

뷰포트(viewport)는 여전히 중요합니다. 다만 유일한 중심은 아니게 되어가고 있습니다.

참고 자료:

  • MDN: CSS Container Queries
  • MDN: Using Container Size and Style Queries
  • web.dev: Container Queries
  • web.dev: Baseline
  • CSS-Tricks: Container Query Units cqi and cqb
  • This Dot Labs: CSS Container Queries, what are they?
  • W3C CSS Containment Module Level 3
  • CSS Anchor Positioning

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0