Tailwind CSS

Tailwind Container Query 사용법: 부모 너비 기준 반응형 처리

2026.05.01·수정 2026.05.11·약 22분

Tailwind CSS Container Query를 이해하는 기준

Tailwind에서 반응형을 잡을 때 가장 먼저 익히는 방식은 보통 :, :, : 같은 breakpoint입니다. 이 방식은 페이지 전체 레이아웃을 잡을 때 여전히 필요합니다. 다만 카드, 리스트, 위젯처럼 여러 위치에서 재사용되는 컴포넌트는 브라우저 화면보다 실제로 들어간 부모 영역의 너비가 더 중요할 때가 있습니다. Container Query는 이 기준을 화면에서 컴포넌트가 놓인 박스로 옮기는 기능입니다.

기존 breakpoint만으로 부족해지는 순간

Tailwind Container Query 사용법: 부모 너비 기준 반응형 처리 핵심 개념을 설명하는 첫 번째 본문 이미지

Tailwind CSS에서 반응형을 처음 다룰 때는 보통 md:flex, lg:grid-cols-3, sm:text-sm 같은 클래스를 먼저 사용합니다. 이 클래스들은 브라우저 화면 너비를 기준으로 스타일을 바꿉니다. 페이지 전체가 1단에서 2단으로 바뀌거나, 상품 목록이 2열에서 4열로 늘어나는 구조라면 이 기준이 잘 맞습니다.

그런데 실제 화면에서는 컴포넌트가 항상 화면 전체 너비를 그대로 쓰지 않습니다. 같은 상품 카드라도 메인 목록 안에서는 넓은 영역을 차지하고, 오른쪽 추천 영역 안에서는 좁은 박스 안에 들어갑니다. 관리자 대시보드에서도 비슷합니다. 통계 카드는 상단 요약 영역에서는 넓게 보이지만, 사이드 패널이나 작은 위젯 영역에서는 한 줄 정보처럼 압축되어야 할 수 있습니다.

이때 md:만 사용하면 화면은 충분히 넓다고 판단해서 카드 내부를 가로형으로 바꿔버릴 수 있습니다. 하지만 카드가 실제로 들어간 부모 영역은 좁을 수 있습니다. 화면 기준과 컴포넌트가 차지하는 실제 공간 기준이 어긋나는 순간입니다.

<section class="grid gap-6 md:grid-cols-[1fr_320px]"> <main class="grid gap-4"> <article class="grid gap-4 md:grid-cols-[160px_1fr]"> <div class="aspect-[4/3] rounded-xl bg-zinc-100"></div> <div> <h3 class="text-lg font-semibold">메인 영역 카드</h3> <p class="text-sm text-zinc-500">넓은 영역에서는 가로형도 자연스럽습니다.</p> </div> </article> </main> <aside class="grid gap-4"> <article class="grid gap-4 md:grid-cols-[160px_1fr]"> <div class="aspect-[4/3] rounded-xl bg-zinc-100"></div> <div> <h3 class="text-lg font-semibold">사이드바 카드</h3> <p class="text-sm text-zinc-500">화면은 넓어도 실제 카드 영역은 좁을 수 있습니다.</p> </div> </article> </aside>
</section>

위 구조에서 md:grid-cols-[160px_1fr]는 화면 너비가 md 이상이면 적용됩니다. 메인 영역의 카드는 괜찮을 수 있지만, 사이드바의 카드는 실제 너비가 좁아도 같은 규칙을 따라갑니다. 그래서 이미지와 텍스트가 억지로 나뉘고, 제목이 어색하게 줄바꿈되거나 가격·버튼 영역이 답답하게 보일 수 있습니다.

이 문제는 Tailwind 클래스 자체의 문제가 아닙니다. 기준을 잘못 잡은 상태에 가깝습니다. 페이지 전체 레이아웃을 바꾸는 조건과 컴포넌트 내부 레이아웃을 바꾸는 조건이 모두 viewport 하나에 묶여 있기 때문입니다.

Container Query는 기준이 다르다

Container Query는 브라우저 화면 전체가 아니라, 특정 부모 컨테이너의 크기를 기준으로 자식 요소의 스타일을 바꿉니다. media query와 비슷하게 보이지만 판단하는 대상이 다릅니다. media query는 viewport를 보고, container query는 컴포넌트가 놓인 부모 박스를 봅니다.

Tailwind에서는 이 차이가 클래스 이름에서도 드러납니다. md:는 화면 기준 breakpoint이고, @md:는 컨테이너 기준 breakpoint입니다. 앞에 @가 붙는 순간 “화면이 이상인가?”가 아니라 “이 컴포넌트가 들어 있는 컨테이너가 이상인가?”를 묻는 조건으로 바뀝니다.

구분 기준 주로 쓰는 위치
md: 브라우저 화면 너비 페이지 전체 레이아웃, 섹션 배치, 큰 컬럼 구조
@md: 부모 컨테이너 너비 카드 내부, 위젯 내부, 재사용 컴포넌트의 세부 배치

이 차이를 놓치면 @md:를 단순히 새로 추가된 반응형 prefix처럼 외우게 됩니다. 그렇게 접근하면 적용이 안 될 때 원인을 찾기 어렵습니다. Container Query는 반응형 클래스가 하나 더 생긴 것이 아니라, 반응형을 판단하는 기준을 컴포넌트 쪽으로 옮긴 기능입니다.

container 클래스와 @container는 다릅니다

Tailwind에는 예전부터 container 클래스가 있었습니다. 이 클래스는 보통 레이아웃의 최대 너비를 breakpoint 기준으로 맞출 때 사용합니다. 페이지 전체를 가운데 정렬하고, 특정 너비 이상에서 max-width를 제한하는 용도에 가깝습니다.

반면 @container는 해당 요소를 Container Query의 기준점으로 만드는 클래스입니다. 이름은 비슷하지만 목적이 다릅니다. container는 레이아웃 너비를 잡는 유틸리티이고, @container는 자식 요소가 부모 크기를 기준으로 스타일을 바꿀 수 있게 만드는 선언입니다.

<div class="container mx-auto px-4"> <p>페이지 전체 폭을 제한하는 레이아웃 컨테이너입니다.</p>
</div> <div class="@container"> <div class="grid gap-4 @md:grid-cols-2"> <p>부모 너비가 충분할 때 내부 레이아웃이 바뀝니다.</p> </div>
</div>

두 코드는 모두 컨테이너라는 단어를 사용하지만 역할은 다릅니다. 글이나 코드를 읽을 때 이 둘을 구분하지 않으면, 왜 @md:가 적용되지 않는지 찾는 데 시간을 쓰게 됩니다. @md:를 쓰려면 먼저 기준이 되는 조상 요소에 @container가 있어야 합니다.

Tailwind에서 @container를 쓰는 기본 흐름

Tailwind CSS v4 기준으로 Container Query는 기본 기능으로 들어와 있습니다. v3 시절 자료를 보면 별도 플러그인을 설치하는 예시가 나올 수 있는데, v4 기준으로 글을 작성한다면 “부모에 @container, 자식에 @sm: 또는 @md:” 흐름을 먼저 잡는 것이 좋습니다.

기본 구조는 단순합니다. 기준으로 삼을 부모 요소에 @container를 붙입니다. 그리고 그 안의 자식 요소에서 @md:grid-cols-2, @lg:flex-row처럼 컨테이너 크기 기준 variant를 사용합니다.

<section class="@container rounded-2xl border border-zinc-200 p-4"> <article class="grid gap-4 @md:grid-cols-[160px_1fr] @lg:grid-cols-[220px_1fr]"> <div class="aspect-[4/3] rounded-xl bg-zinc-100"></div> <div class="flex flex-col gap-3"> <div> <p class="text-xs font-medium text-zinc-500">추천 상품</p> <h3 class="text-base font-semibold text-zinc-950 @md:text-lg"> 컨테이너 너비에 맞춰 변하는 상품 카드 </h3> </div> <p class="text-sm leading-6 text-zinc-600"> 좁은 영역에서는 세로형으로 쌓이고, 부모 영역이 넓어지면 이미지와 설명이 나란히 배치됩니다. </p> <div class="flex items-center justify-between gap-3"> <strong class="text-base text-zinc-950">39,000원</strong> <button class="rounded-lg bg-zinc-950 px-4 py-2 text-sm text-white">보기</button> </div> </div> </article>
</section>

이 구조에서는 브라우저 화면이 아니라 section의 실제 너비가 기준이 됩니다. 같은 상품 카드 컴포넌트를 넓은 목록 영역에 넣으면 @md: 조건을 만족해 가로형으로 바뀔 수 있고, 좁은 사이드바에 넣으면 세로형으로 남을 수 있습니다.

이 차이는 재사용 컴포넌트에서 특히 크게 드러납니다. 기존 breakpoint만 사용하면 컴포넌트가 놓인 위치와 상관없이 화면 크기 하나로만 반응합니다. Container Query를 사용하면 컴포넌트가 자기에게 주어진 공간을 기준으로 형태를 바꿉니다.

부모에 @container가 없으면 @md:는 기준을 찾지 못합니다

Container Query를 처음 사용할 때 자주 생기는 실수는 자식 요소에만 @md:를 붙이는 것입니다. @md:grid-cols-2를 썼는데 아무 변화가 없다면가장 먼저 부모나 조상 요소에 @container가 있는지 확인해야 합니다.

<div class="rounded-2xl border p-4"> <article class="grid gap-4 @md:grid-cols-2"> <p>이 코드는 기준 컨테이너가 없어서 의도대로 동작하지 않을 수 있습니다.</p> </article>
</div> <div class="@container rounded-2xl border p-4"> <article class="grid gap-4 @md:grid-cols-2"> <p>이 코드는 부모 컨테이너 너비를 기준으로 동작합니다.</p> </article>
</div>

위쪽 코드는 자식 요소에 @md:가 있지만 기준 컨테이너가 없습니다. 아래쪽 코드는 부모에 @container가 있으므로 자식의 @md:grid-cols-2가 판단할 기준을 가집니다. Container Query를 적용할 때는 클래스 하나를 추가하는 것보다 “어떤 박스의 크기를 기준으로 삼을 것인가”를 먼저 정해야 합니다.

media query와 container query를 나누는 기준

Container Query가 있다고 해서 기존 breakpoint를 버리는 것은 아닙니다. 오히려 둘을 섞어 써야 하는 경우가 많습니다. 기준을 나누지 않으면 md:@md:가 한 컴포넌트 안에서 뒤섞이고, 나중에 수정할 때 어느 조건이 실제 레이아웃을 바꾸는지 찾기 어려워집니다.

큰 기준은 단순합니다. 페이지 전체 구조는 기존 breakpoint로 잡고, 컴포넌트 내부 변화는 Container Query로 잡습니다. 예를 들어 헤더가 모바일에서 접히고 데스크톱에서 펼쳐지는 구조, 전체 페이지가 1단에서 2단으로 바뀌는 구조는 화면 크기 기준이 자연스럽습니다. 반대로 카드 안에서 이미지와 텍스트를 나눌지, 버튼을 한 줄로 둘지, 설명 문구를 얼마나 보여줄지는 부모 컨테이너 기준이 더 맞을 수 있습니다.

<section class="grid gap-6 lg:grid-cols-[1fr_360px]"> <main class="grid gap-4"> <div class="@container"> <article class="grid gap-4 @md:grid-cols-[180px_1fr]"> <div class="aspect-[4/3] rounded-xl bg-zinc-100"></div> <div class="space-y-3"> <h3 class="text-base font-semibold @md:text-xl">메인 목록 카드</h3> <p class="text-sm text-zinc-600">부모 너비가 넓을 때만 가로형으로 전환됩니다.</p> </div> </article> </div> </main> <aside class="grid gap-4"> <div class="@container"> <article class="grid gap-4 @md:grid-cols-[180px_1fr]"> <div class="aspect-[4/3] rounded-xl bg-zinc-100"></div> <div class="space-y-3"> <h3 class="text-base font-semibold @md:text-xl">사이드 추천 카드</h3> <p class="text-sm text-zinc-600">좁은 부모 안에서는 세로형을 유지합니다.</p> </div> </article> </div> </aside>
</section>

여기서 lg:grid-cols-[1fr_360px]는 페이지 전체가 넓어졌을 때 메인과 사이드바를 나누는 역할입니다. 반면 카드 내부의 @md:grid-cols-[180px_1fr]는 각 카드가 들어간 부모 너비를 기준으로 동작합니다. 같은 @md:를 쓰더라도 메인 영역과 사이드바에서 결과가 달라질 수 있습니다.

이 방식은 컴포넌트 재사용성이 높아질수록 장점이 드러납니다. 카드 컴포넌트 자체는 “내 부모가 넓으면 가로형, 좁으면 세로형”이라는 규칙만 가지면 됩니다. 페이지가 메인인지, 사이드바인지, 관리자 대시보드인지까지 컴포넌트가 알 필요가 줄어듭니다.

무조건 @container를 붙이면 생기는 문제

Container Query가 편하다고 해서 모든 wrapper에 @container를 붙이면 구조가 흐려질 수 있습니다. 기준 컨테이너가 너무 많으면 자식 요소가 어느 부모의 크기를 보고 있는지 추적하기 어려워집니다. 특히 중첩 카드, 탭 안의 카드, 모달 안의 리스트처럼 박스가 여러 겹인 화면에서는 기준점이 명확해야 합니다.

적용 전에 질문을 하나 던지는 것이 좋습니다. “이 컴포넌트는 부모 너비가 달라질 때 내부 형태가 달라져야 하는가?” 이 질문에 답이 없으면 기존 breakpoint나 일반 레이아웃 클래스로 충분할 수 있습니다.

실제 UI에서 확인할 체크포인트

Tailwind Container Query 사용법: 부모 너비 기준 반응형 처리 적용 흐름을 설명하는 두 번째 본문 이미지

Container Query는 카드 UI에서 가장 쉽게 체감됩니다. 상품 카드, 블로그 글 카드, 통계 카드, 알림 카드처럼 반복되는 UI는 한 곳에만 쓰이지 않는 경우가 많습니다. 처음에는 목록 영역에서만 보이지만, 나중에는 추천 영역, 검색 결과 관리자 미리보기, 모바일 하단 패널 같은 위치로 이동합니다.

이때 컴포넌트가 화면 크기에만 반응하면 위치가 바뀔 때마다 예외 클래스를 추가하게 됩니다. sidebar일 때는 세로형, main일 때는 가로형, compact일 때는 버튼 숨김 같은 식으로 props가 늘어날 수도 있습니다. 모든 경우를 props로 제어해야 하는 것은 아니지만, 단순히 부모 너비에 따라 형태만 달라지는 문제라면 Container Query가 더 간단한 기준이 될 수 있습니다.

상황 추천 기준 이유
페이지 전체가 1단에서 2단으로 전환 lg:, xl: 브라우저 화면 크기가 레이아웃 기준이기 때문입니다.
같은 카드가 메인과 사이드바에 함께 사용 @container, @md: 컴포넌트가 실제로 차지한 부모 너비가 더 중요하기 때문입니다.
대시보드 위젯 내부 정보 밀도 조정 @sm:, @lg: 위젯이 놓인 그리드 칸 너비에 따라 내부 구성이 달라질 수 있기 때문입니다.
헤더 메뉴를 모바일에서 숨김 md:, lg: 사용자 화면 크기 자체가 탐색 구조의 기준이기 때문입니다.

체크할 부분은 세 가지입니다. 첫째, 컴포넌트가 여러 위치에서 재사용되는지 확인합니다. 둘째, 위치가 바뀔 때 부모 너비가 달라지는지 봅니다. 셋째, 그 변화가 내부 레이아웃 변경으로 이어져야 하는지 판단합니다. 세 조건이 맞으면 Container Query를 검토할 만합니다.

반대로 한 페이지에서만 쓰이고, 화면 전체 기준으로만 바뀌면 기존 breakpoint가 더 단순합니다. Container Query는 더 세밀한 도구이지만, 항상 더 좋은 선택이라는 뜻은 아닙니다. 기준이 많아질수록 수정할 때 확인해야 할 조건도 늘어납니다.

브라우저와 버전 기준도 같이 확인해야 합니다

현재 기준으로 CSS Container Query는 주요 브라우저에서 넓게 사용할 수 있는 기능입니다. 그래도 프로젝트가 구형 브라우저나 특정 웹뷰를 지원해야 한다면 지원 범위를 확인해야 합니다. 특히 사내 관리자, 오래된 Android WebView, 폐쇄망 환경처럼 업데이트가 늦은 환경에서는 최신 CSS 기능이 예상보다 늦게 반영될 수 있습니다.

Tailwind 쪽에서는 버전 기준이 더 중요합니다. Tailwind CSS v4에서는 Container Query를 기본 기능으로 다룰 수 있지만, v3 자료에서는 @tailwindcss/container-queries 플러그인을 설치하는 방식이 나올 수 있습니다. 검색해서 나온 예시를 그대로 따라 하기 전에 현재 프로젝트의 Tailwind 버전을 먼저 확인해야 합니다.

<div class="@container rounded-2xl border p-4"> <div class="grid gap-4 @md:grid-cols-2"> <article class="rounded-xl border p-4">카드 A</article> <article class="rounded-xl border p-4">카드 B</article> </div>
</div>

문법을 외우기 전에 버전부터 맞춰두면 자료가 섞여도 덜 흔들립니다. 같은 @container라는 단어를 쓰더라도 프로젝트 환경이 v3인지 v4인지에 따라 설정 과정이 다를 수 있습니다.

정리: 컴포넌트 단위 반응형 기준으로 보기

Tailwind CSS Container Query는 반응형을 더 복잡하게 만드는 기능이 아니라이미 복잡해진 컴포넌트 배치 문제를 더 정확한 기준으로 나누는 기능입니다. 기존 breakpoint는 화면 전체를 기준으로 하고, Container Query는 컴포넌트가 들어간 부모 영역을 기준으로 합니다.

처음에는 md:@md: 차이만 기억해도 충분합니다. md:는 viewport 기준, @md:는 부모 컨테이너 기준입니다. 그리고 @md:를 쓰려면 기준이 되는 조상 요소에 @container가 있어야 합니다.

실제 작업에서는 모든 반응형을 Container Query로 바꾸려고 하기보다 역할을 나누는 것이 오래 갑니다. 페이지의 큰 틀은 sm:, md:, lg:로 잡고, 카드나 위젯처럼 재사용되는 컴포넌트 내부는 @container@md:로 조정합니다. 이 기준을 잡아두면 같은 컴포넌트를 메인 영역, 사이드바, 관리자 화면에 옮겨도 예외 처리가 줄어듭니다.

다음에 Tailwind로 반응형 UI를 잡을 때는 먼저 “화면 크기가 기준인가, 부모 박스 크기가 기준인가”를 확인하면 됩니다. 이 질문 하나만 있어도 기존 breakpoint와 Container Query를 섞는 위치가 훨씬 분명해집니다.

컨테이너 기준 반응형을 적용하기 전에 레이아웃 기본기를 다시 잡고 싶다면 Tailwind CSS border와 flex 기본 클래스 글을 먼저 확인해도 좋습니다.

참고 자료

Tailwind CSS 공식 문서의 responsive design, Tailwind CSS v4 안내, MDN의 CSS Container Query와 container-type 설명을 기준으로 개념과 버전 차이를 확인했습니다. 실제 적용 예시는 카드 UI와 대시보드 위젯처럼 반복 사용되는 컴포넌트 상황에 맞춰 재구성했습니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기