브레이크포인트 없는 반응형: 컨테이너 쿼리, clamp, 내재적 디자인

카드 컴포넌트를 하나 만들었다고 하자. 썸네일이 위, 제목과 설명이 아래로 쌓인 세로 카드다. 이 카드를 본문 그리드에 넣으면 폭이 넉넉하니 썸네일을 왼쪽, 텍스트를 오른쪽으로 눕히고 싶다. 그런데 똑같은 카드를 좁은 사이드바에 재사용하면 폭이 부족하니 다시 세로로 쌓여야 한다. 여기서 문제가 하나 생긴다. 이 두 상황을 가르는 기준이 뷰포트 폭이 아니라는 것이다. 데스크톱 뷰포트 하나에서도 카드는 넓은 본문과 좁은 사이드바에 동시에 놓인다.
이 지점에서 미디어 쿼리(media query)는 답을 내지 못한다. @media가 볼 수 있는 건 화면 전체의 폭이지, 이 카드가 지금 몇 픽셀짜리 자리에 앉아 있는지가 아니기 때문이다. 이 글은 그 한계에서 출발해, 반응형을 짜는 기본 축이 최근 몇 년 사이 어떻게 옮겨 갔는지를 정리한다. 결론부터 말하면 브레이크포인트를 없애자는 얘기가 아니다. 판단의 중심이 뷰포트에서 컴포넌트와 콘텐츠로 옮겨 갔다는 얘기다.
미디어 쿼리는 뷰포트만 본다
반응형을 처음 배울 때 익히는 패턴은 대개 이렇다. 320px, 768px, 1024px 같은 지점을 정해두고, 그 폭을 넘을 때마다 레이아웃을 갈아끼운다.
.layout {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.layout {
grid-template-columns: 2fr 1fr;
}
}
코드가 처음 나왔으니 하나 짚고 가자. 디자이너가 이 코드를 직접 짤 일은 없다. 모양만 눈으로 읽으면 된다. 그 모양이 곧 화면이 어떤 조건에서 배치를 어떻게 바꾸는지의 지도이고, 그건 디자이너가 시안에서 다루는 것과 같은 것이기 때문이다.
한 줄씩 읽어보자. display: grid는 이 영역을 격자로 배치하겠다는 선언이다. grid-template-columns: 1fr은 열을 하나만 두고 그 열이 남는 폭을 다 차지하게 한다는 뜻이다. 1fr의 fr은 남은 공간을 나눠 갖는 몫(fraction)이라고 읽으면 된다. 여기까지는 한 열, 곧 세로로 쌓인 배치다. 이어지는 @media (min-width: 768px)는 화면 폭이 768px 이상이면이라는 조건문이고, 그 조건이 맞을 때만 안쪽 규칙을 켠다. 안쪽의 grid-template-columns: 2fr 1fr은 열을 둘로 나누되 왼쪽이 오른쪽의 두 배 폭을 갖는다는 뜻이다. 정리하면 좁을 땐 한 줄로 쌓고, 768px을 넘으면 2:1 두 열로 가르는 규칙 하나다.
여기서 이 글 전체를 관통할 그림을 하나 깔자. 뷰포트, 곧 브라우저 창 전체를 집 한 채라고 하자. 그 집 안에 카드 같은 컴포넌트가 가구처럼 놓인다. 미디어 쿼리는 가구가 자기가 놓인 방이 아니라 집 전체의 크기를 보고 자세를 바꾸는 방식이다. 집이 이만큼 넓어지면 이 가구는 옆으로 눕는다는 식이다. 그런데 같은 집 안에도 넓은 거실과 좁은 골방이 함께 있다. 집 전체 폭만 봐서는 이 가구가 지금 거실에 있는지 골방에 있는지 알 수가 없다. 글 첫머리의 카드가 본문에 있는지 사이드바에 있는지 미디어 쿼리가 모른다는 게 정확히 이 얘기다.
이 방식은 15년 넘게 웹 반응형의 표준 도구였다. 이선 마콧(Ethan Marcotte)이 2010년에 반응형 웹 디자인(responsive web design)이라는 이름을 붙였을 때, 그 뼈대의 하나가 바로 미디어 쿼리였다. 여전히 잘 작동하고, 앞으로도 쓸 일이 많다.
문제는 미디어 쿼리가 대답하는 질문이 딱 하나로 고정돼 있다는 점이다. 브라우저 창이 지금 몇 픽셀인가. 그런데 컴포넌트 입장에서 정작 궁금한 건 다른 질문이다. 내가 지금 놓인 이 자리가 몇 픽셀인가. 이 둘은 자주 어긋난다.
앞의 카드가 정확히 그렇다. 뷰포트가 1280px라도, 그 카드가 본문의 800px 자리에 있는지 사이드바의 260px 자리에 있는지에 따라 가로로 눕힐지 세로로 쌓을지가 갈린다. 미디어 쿼리로 이걸 흉내 내려면 결국 부모가 누구냐에 따라 카드에 다른 클래스를 덧붙이는 수밖에 없다. .card--in-sidebar, .card--in-main 같은 변형이 늘어난다. 이렇게 되면 컴포넌트가 자기 문맥을 스스로 알아야 하고, 재사용할 때마다 그 문맥을 손으로 알려줘야 한다. 컴포넌트 기반으로 짜자던 원칙이 레이아웃 앞에서 무너지는 지점이다.
정리하면 미디어 쿼리의 한계는 성능이나 문법이 아니라 관측 대상에 있다. 뷰포트를 보는 도구로 컴포넌트의 문맥을 판단하려 하니 계속 어긋난다. 그래서 필요한 건 관측 대상을 바꾸는 것이다.
컨테이너 쿼리: 부모의 폭에 반응한다
컨테이너 쿼리(container query)는 그 관측 대상을 뷰포트에서 부모 요소로 바꾼다. 화면이 아니라 이 컴포넌트가 놓인 자리의 폭을 본다.
쓰는 법은 두 단계다. 먼저 기준이 될 부모를 컨테이너로 선언한다. 그다음 자식이 그 컨테이너의 폭에 대고 조건을 건다.
/* 1) 카드가 놓일 부모를 컨테이너로 선언 */
.card-slot {
container-type: inline-size;
container-name: card;
}
/* 2) 기본은 세로로 쌓기 */
.card {
display: grid;
gap: 12px;
}
/* 3) 컨테이너(부모) 폭이 400px 이상이면 가로로 눕히기 */
@container card (min-width: 400px) {
.card {
grid-template-columns: 160px 1fr;
align-items: center;
}
}
이 블록은 앞의 카드 문제를 실제로 푸는 코드다. 주석의 번호를 따라 세 덩어리로 읽으면 된다. 첫 덩어리 .card-slot은 카드가 앉을 자리, 곧 방이다. container-type: inline-size는 이 방을 기준자로 등록하겠다는 선언이고, container-name: card는 그 방에 card라는 이름표를 붙이는 것이다. 나중에 어느 방을 보고 반응할지 이름으로 가리키기 위해서다. 둘째 덩어리 .card의 display: grid는 카드 속을 격자로 배치한다는 뜻이고, gap: 12px는 칸 사이를 12px 띄운다는 뜻이다. 이 상태의 기본 모양은 세로로 쌓인 카드다. 셋째 덩어리 @container card (min-width: 400px)는 card라는 이름의 방 폭이 400px 이상이면이라는 조건문이다. 그 조건이 맞으면 안쪽 grid-template-columns: 160px 1fr이 켜져, 왼쪽에 160px짜리 썸네일 열과 오른쪽에 남는 폭을 다 먹는 열이 생긴다. 곧 가로로 눕힌 배치다. align-items: center는 그 두 열을 세로 가운데로 맞춘다는 뜻이다.
집과 방의 그림으로 옮기면 이렇다. 미디어 쿼리가 집 전체 크기를 봤다면, @container는 가구가 자기가 놓인 방의 크기를 본다. 같은 소파라도 넓은 거실에 두면 팔걸이까지 펴지고 좁은 골방에 두면 접히는 것과 같다. 소파는 자기가 거실에 있는지 골방에 있는지 알 필요가 없다. 그저 지금 선 방의 폭에 반응할 뿐이다.
핵심은 container-type: inline-size다. 이 선언은 해당 요소의 인라인 방향 크기, 즉 가로 쓰기에서는 폭을 자식들이 질의할 수 있는 기준으로 등록한다. 그러면 @container 블록 안의 min-width: 400px는 뷰포트가 아니라 그 컨테이너의 폭을 가리킨다. 이제 같은 .card 코드를 본문에도, 사이드바에도 그대로 던져 넣으면 된다. 본문 자리는 400px보다 넓으니 가로로 눕고, 사이드바 자리는 좁으니 세로로 쌓인다. 카드는 자기가 어디에 있는지 알 필요가 없다. 그냥 놓인 자리의 폭에 반응할 뿐이다.
이게 진짜 컴포넌트 기반 반응형이다. 미디어 쿼리 시절에는 컴포넌트의 반응 규칙이 페이지 레이아웃과 얽혀 있었다. 같은 카드라도 어느 페이지, 어느 슬롯에 들어가느냐에 따라 다른 브레이크포인트가 필요했다. 컨테이너 쿼리에서는 반응 규칙이 컴포넌트 안으로 들어온다. 카드는 자기 규칙을 통째로 들고 다니고, 어디에 떨궈도 문맥에 맞게 접힌다. 디자인 시스템에서 컴포넌트를 진짜로 문맥 독립적으로 배포하려면 이 축이 반드시 필요하다.
한 가지 실무 주의점. container-type: inline-size를 걸면 그 요소는 인라인 방향으로 크기 격리(containment)가 걸린다. 쉽게 말해, 컨테이너의 폭은 안쪽 콘텐츠가 아니라 바깥 레이아웃이 정해줘야 한다. 그래서 보통 카드 자체가 아니라 카드를 감싸는 슬롯을 컨테이너로 잡는다. 컴포넌트가 자기 자신을 질의하게 만들면 순환이 생기기 쉬워서, 한 겹 바깥을 컨테이너로 두는 편이 안전하다. 컨테이너 쿼리를 쓰려면 이렇게 감싸는 슬롯을 하나 두는 비용이 항상 따라온다는 것도 계산에 넣어야 한다.
브라우저 지원은 이제 걸림돌이 아니다. 크롬 105(2022년 8월), 사파리 16(2022년 9월)에서 먼저 안정 버전에 들어왔고, 파이어폭스 110(2023년 2월)이 뒤따랐다. 2023년에 세 엔진 모두 지원하는 Baseline 상태가 됐다. 5년 이상 된 브라우저를 반드시 받쳐야 하는 상황이 아니라면 지금 실무에 써도 된다. 다만 위 연도와 버전은 이 글 작성 시점 기준이니, 오래된 환경을 지원해야 하는 프로젝트라면 caniuse에서 대상 브라우저를 직접 확인하는 편이 낫다.
clamp: 값이 계단이 아니라 연속으로 흐른다
컨테이너 쿼리가 배치의 축을 바꿨다면, 유동 값(fluid value)은 크기의 축을 바꾼다.
미디어 쿼리로 폰트 크기를 반응형으로 만들면 값이 계단처럼 튄다. 767px에서 본문이 16px이었다가 768px이 되는 순간 18px로 툭 뛴다. 그 사이 폭, 예를 들어 900px에서는 16px과 18px 중 무엇도 그 폭에 맞춰 고른 값이 아니다. 그냥 가까운 계단에 걸쳐 있을 뿐이다. 브레이크포인트를 아무리 촘촘히 박아도 계단은 계단이다.
clamp()는 이 계단을 경사로로 바꾼다. 인자를 세 개 받는다. 최솟값, 선호값, 최댓값이다.
:root {
/* 최소 1rem, 선호 0.9rem + 0.6vw, 최대 1.5rem */
--step-0: clamp(1rem, 0.9rem + 0.6vw, 1.5rem);
--step-1: clamp(1.5rem, 1.2rem + 1.6vw, 2.75rem);
}
body { font-size: var(--step-0); }
h1 { font-size: var(--step-1); }
이 블록은 계단을 경사로로 바꾸는 실제 코드다. clamp()의 괄호 안 세 값을 왼쪽부터 읽으면 최솟값, 선호값, 최댓값이다. 벽에 붙은 온도조절기를 떠올리면 쉽다. 최저 온도와 최고 온도를 정해두고 그 사이에서 목표 온도를 향해 자동으로 맞추는 장치 말이다. clamp(1rem, 0.9rem + 0.6vw, 1.5rem)은 폰트를 아무리 작아도 1rem 밑으로 안 내리고, 아무리 커도 1.5rem 위로 안 올리며, 그 사이에서는 0.9rem + 0.6vw라는 목표값을 따라간다는 뜻이다.
단위도 풀어두자. rem은 문서의 기준 글자 크기를 1로 본 단위라, 사용자가 브라우저 글자 크기를 키우면 같이 커진다. vw는 뷰포트 폭의 1%다. 1vw는 창이 1000px이면 10px, 2000px이면 20px이 된다. 그래서 목표값 0.9rem + 0.6vw는 고정된 바닥(0.9rem)에 창 폭에 비례하는 미세 조정(0.6vw)을 더한 값이다. 맨 위 :root는 문서의 가장 바깥을 가리키고, --step-0은 이 값에 붙인 이름표다. 아래에서 font-size: var(--step-0)으로 그 이름표를 꺼내 쓴다. 값 하나에 이름을 붙여 여러 곳에서 재사용하는 것뿐이다.
선호값에 뷰포트 폭에 비례하는 항(vw)이 들어 있어서, 창이 넓어질수록 폰트가 연속적으로 커진다. 최솟값 아래로는 안 내려가고 최댓값 위로는 안 올라간다. 좁은 화면에서 본문이 너무 작아지거나 큰 화면에서 제목이 터무니없이 커지는 걸 양끝에서 막아준다. 브레이크포인트 없이 타이포그래피가 화면 폭을 따라 부드럽게 흐른다. 이걸 유동 타이포그래피(fluid typography)라고 부른다.
여기서 반드시 짚어야 할 접근성 함정이 하나 있다. 선호값을 vw만으로 짜면 안 된다. 예를 들어 font-size: 4vw처럼 순수 뷰포트 단위로 본문 크기를 잡으면, 사용자가 브라우저에서 글자 크기를 키우는 조작이 먹지 않는다. vw는 뷰포트 폭에만 반응할 뿐 사용자의 글자 크기 설정에는 반응하지 않기 때문이다. W3C는 이걸 명시적인 실패 사례로 문서화해 뒀다. 성공 기준 1.4.4(Resize text)에 대한 실패 기법 F94가 바로 폰트 크기의 주된 단위로 뷰포트 단위를 잘못 쓴 경우다. 저시력 사용자가 글자를 200%까지 키우지 못하면 이 기준에서 떨어진다.
위 코드가 이 함정을 피하는 이유는 선호값에 rem을 섞었기 때문이다. 0.9rem + 0.6vw에서 rem 항은 루트 폰트 크기를 따라간다. 사용자가 브라우저 기본 글자 크기를 키우거나 페이지를 확대하면 rem 항이 같이 커지므로, 텍스트가 사용자의 조작에 계속 반응한다. vw는 화면 폭에 맞춘 미세 조정을 맡고, rem은 사용자의 접근성 설정을 실어 나른다. 유동 타이포그래피의 선호값에는 항상 rem 성분을 남겨 둬야 하는 이유다. 최댓값을 잡을 때도 너무 낮게 눌러서 200% 확대를 가로막지 않는지 실제로 확대해 보고 확인하는 편이 안전하다.
그런데 여기엔 앞 절의 컨테이너 쿼리와 정면으로 부딪히는 지점이 있다. vw는 무슨 일이 있어도 뷰포트 폭을 읽는다. 그래서 아까 만든 카드를 넓은 화면의 좁은 사이드바에 넣으면, 슬롯은 260px인데 제목 폰트는 1280px 뷰포트 기준으로 커져 글자가 슬롯을 뚫고 나온다. 배치는 컨테이너에 반응하는데 크기는 뷰포트에 반응하니 둘이 어긋나는 것이다. 이걸 맞추려면 vw 대신 컨테이너 인라인 크기 단위인 cqi를 쓴다. 1cqi는 컨테이너 인라인 크기의 1%다.
.card h3 {
/* vw가 아니라 cqi: 뷰포트가 아니라 카드가 놓인 슬롯 폭을 따라간다 */
font-size: clamp(1rem, 0.9rem + 2cqi, 1.5rem);
}
한 줄만 바뀌었다. 앞의 clamp와 구조가 똑같은데 목표값의 0.6vw가 2cqi로 바뀌었을 뿐이다. cqi는 뷰포트가 아니라 카드가 놓인 방의 인라인 폭을 1로 보는 단위다. 온도조절기로 치면, 집 전체 온도가 아니라 이 가구가 놓인 방의 온도를 읽도록 센서를 방 안으로 옮겨 단 것이다. 배치를 방에 묶었으면 크기도 방에 묶어야 둘이 따로 놀지 않는다. rem 항을 그대로 남겨 둔 이유는 앞과 같다. 사용자가 글자를 키우는 조작을 계속 살리기 위해서다.
이제 같은 카드라도 본문 슬롯에서는 제목이 크게, 사이드바 슬롯에서는 작게 흐른다. 컨테이너 쿼리로 배치를 컴포넌트에 묶었으면, 크기도 cqi로 컴포넌트에 묶어야 둘이 따로 놀지 않는다. cqi는 컨테이너 쿼리와 같은 시기에 들어와 지원 범위도 거의 겹치니, 컨테이너 쿼리를 쓸 수 있는 환경이면 이 단위도 쓸 수 있다. 대신 rem 항은 그대로 남겨 사용자의 확대를 지켜야 한다는 원칙은 cqi에서도 똑같다.
내재적 레이아웃: 몇 열인지 선언하지 않는다
컨테이너 쿼리와 유동 값이 반응형의 축을 옮겼다면, 그 축을 하나로 꿰는 사고방식이 내재적 디자인(intrinsic web design)이다. 이 용어는 2018년 젠 시먼스(Jen Simmons)가 An Event Apart 강연 Everything You Know About Web Design Just Changed에서 처음 붙였다. 당시 모질라의 디자이너 애드보킷이던 그는, 반응형 웹 디자인의 다음 단계로 이 개념을 제시했다.
핵심 발상은 이렇다. 레이아웃에 몇 열이라고 명령하지 말고, 콘텐츠와 여유 공간이 알아서 접히게 두라는 것이다. 미디어 쿼리 시절에는 우리가 폭을 재고, 우리가 열 수를 정했다. 768px에서는 2열, 1024px에서는 3열 하는 식으로. 내재적 레이아웃에서는 그 결정을 브라우저에 넘긴다.
그리드(grid)의 auto-fit, auto-fill이 이걸 한 줄로 해낸다.
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
이 한 줄을 풀어 읽으면 이렇다. 각 열은 최소 240px, 최대 1fr(남는 공간의 균등 분배)이다. 브라우저는 이 규칙을 지키면서 한 줄에 몇 열이 들어갈 수 있는지 스스로 계산한다. 컨테이너가 넓으면 열이 늘고, 좁아지면 열이 줄고, 다 못 들어가면 다음 줄로 접힌다. 개발자는 240px이라는 최소 폭 하나만 정했고, 열 수와 접히는 지점은 한 번도 명시하지 않았다. 카드가 4개든 20개든, 컨테이너가 300px이든 1200px이든, 같은 규칙 한 줄이 다 감당한다.
낯선 함수 이름들도 뜯어보면 별게 아니다. repeat(...)은 같은 규칙의 열을 반복해서 깔라는 뜻이고, 그 안의 auto-fit은 반복 횟수, 곧 열 개수를 브라우저가 알아서 정하라는 지시다. minmax(240px, 1fr)은 한 열의 최소는 240px, 최대는 남는 폭을 나눠 갖는 몫(1fr)이라는 뜻이다. gap: 16px은 칸 사이 간격이다.
이걸 선반에 얹어 읽으면 이렇다. 칸 너비의 하한만 정해둔 선반을 하나 짜두면, 선반이 넓어질 때 칸이 늘고 좁아질 때 칸이 줄며, 한 줄에 다 못 들어가는 칸은 아랫줄로 알아서 접힌다. 몇 칸을 놓을지 손으로 세는 대신, 최소 칸 너비만 정하면 선반이 스스로 접었다 폈다 한다.
여기서 auto-fit과 auto-fill의 차이를 짚어야 한다. 자주 헷갈리는데, 콘텐츠가 열 개수를 다 못 채울 때 갈린다. auto-fill은 빈 열을 유령처럼 남겨 둔다. 카드 2개에 자리는 5개 들어갈 폭이라면, 카드 2개가 240px씩 왼쪽에 붙고 오른쪽에 빈 자리 3칸이 남는다. auto-fit은 빈 열을 0으로 접어 없앤다. 그러면 남은 공간을 카드 2개가 나눠 가지며 넓게 늘어난다. 카드를 항상 폭에 꽉 채워 늘리고 싶으면 auto-fit, 열 위치를 고정하고 싶으면 auto-fill이다. 선반으로 치면 auto-fill은 빈 칸 자리를 그대로 비워 두는 선반이고, auto-fit은 안 쓰는 칸을 접어 없애고 남은 물건을 넓게 펼치는 선반이다. 이 동작은 CSS 그리드 명세에 정의된 것이라 브라우저마다 다르지 않다.
minmax(), auto-fit, flex-wrap이 공유하는 태도는 하나다. 정확한 픽셀 지점을 손으로 지정하는 대신, 콘텐츠가 필요로 하는 최소치와 접히는 조건만 선언하고 나머지는 브라우저에 맡긴다. 젠 시먼스가 말한 대로, 디자인이 가용 공간의 변화에 맞춰 스스로 늘어나고 줄어들게(contracting and expanding) 두는 것이다.
그래도 브레이크포인트는 남는다
여기까지 읽으면 미디어 쿼리를 다 걷어내야 할 것 같지만, 그건 과장이다. 완전 유동이 항상 옳지는 않다.
특정 폭에서 레이아웃을 의도적으로 재배치해야 할 때가 있다. 예를 들어 데스크톱에서 좌우로 나란히 놓던 필터 패널과 결과 목록을, 모바일에서는 필터를 접어 상단 버튼으로 바꾸고 목록을 전면에 세우는 식이다. 이건 크기를 부드럽게 줄이는 문제가 아니라 정보 구조를 갈아엎는 문제다. 콘텐츠가 알아서 접히게 두는 걸로는 안 되고, 어느 지점에서 무엇을 숨기고 무엇을 드러낼지 사람이 결정해야 한다. 이럴 때는 브레이크포인트가 여전히 정확한 도구다.
디버깅 난이도도 트레이드오프로 정직하게 얹어야 한다. 미디어 쿼리는 최소한 계단이 어디 있는지 눈에 보인다. 767px에서 768px로 넘을 때 무엇이 바뀌는지 코드에 딱 적혀 있다. 유동 값은 모든 폭에서 조금씩 다른 값을 내놓기 때문에, 어느 폭에서 제목이 본문을 침범하는지, 어느 폭에서 카드가 어색하게 늘어지는지를 잡으려면 슬라이더를 끝까지 밀어 보며 전 구간을 훑어야 한다. clamp() 안의 계수를 손으로 튜닝하는 것도 감이 잘 안 잡힌다. 계산기를 옆에 두거나 유동 스케일 생성기를 쓰게 되는 이유다.
실무에서 이 긴장은 대략 이런 대화로 나타난다.
개발자: “이번 카드 목록, 브레이크포인트 몇 개로 짤까?” 디자이너: “열 수는 auto-fit으로 넘기고, 최소 카드 폭만 240으로 잡자. 브레이크포인트는 필터 접히는 지점 하나만 남기고.” 개발자: “폰트는?” 디자이너: “clamp로 흐르게. 대신 vw 말고 cqi로 잡아서 사이드바에 넣어도 안 터지게 해줘. rem은 섞어서 확대 되게 하고.”
브레이크포인트를 0개로 만드는 게 목표가 아니다. 5개, 6개씩 나열하던 걸 꼭 필요한 재배치 지점 한두 개로 줄이고, 나머지 연속적인 적응은 컨테이너 쿼리와 유동 값에 맡기는 것이다.
축이 바뀌었다
반응형의 기본 질문이 바뀌었다. 예전 질문은 화면이 몇 픽셀일 때 무엇을 어떻게 바꿀까였다. 지금 질문은 이 컴포넌트가 놓인 자리에 어떻게 반응하게 하고, 값이 폭을 따라 어떻게 흐르게 하고, 열 수를 어떻게 브라우저에 넘길까다. 미디어 쿼리는 이 새 질문들 사이에서 마지막 남는 도구로 자리를 옮겼다. 사라진 게 아니라, 판단의 중심에서 한 걸음 물러났다.
컨테이너 쿼리로 컴포넌트가 문맥에 반응하게 하고, clamp()로 값이 연속으로 흐르게 하고, auto-fit minmax()로 열 수를 콘텐츠에 맡긴다. 이 셋을 먼저 깔고 나면, 남는 브레이크포인트는 정말 구조를 갈아엎어야 하는 지점 몇 개뿐이다. 브레이크포인트를 나열하는 반응형에서, 콘텐츠와 컨테이너가 스스로 반응하는 반응형으로. 그게 지금의 기본 축이다.
이 글, 어떠셨어요?