AD JB Toolbox 01

CSS 강좌 / 컨테이너 쿼리로 구성 요소 너비에 맞춰 꾸미기

컨테이너 쿼리(container query)는 부품을 감싸는 컨테이너의 크기 등을 조건으로 자손의 스타일을 바꾸는 기능입니다. 같은 카드라도 본문에서는 가로 배치, 좁은 사이드바에서는 세로 배치가 되게 만들 수 있습니다. 화면 전체의 폭을 보는 미디어 쿼리와 달리, 부품이 실제로 들어간 부모의 공간을 기준으로 판단합니다.

뷰포트 조건과 컨테이너 조건의 차이

브라우저 폭이 1200px이어도 카드가 본문 700px 영역에 있는지 사이드바 300px 영역에 있는지에 따라 적절한 배치는 다릅니다. 두 카드가 같은 뷰포트 조건을 공유하는 미디어 쿼리만으로는 이 차이를 표현하기 어렵습니다. 부품을 재사용할 때는 부모 폭에 따라 내부 구성을 바꾸면 배치별 예외 규칙을 줄일 수 있습니다.

구분 판단 기준 알맞은 역할
@media 뷰포트 폭 등 환경 페이지 전체의 열 수, 사이드바 위치
@container 선택한 조상 컨테이너의 크기 등 카드 내부의 그림·글 배치, 부품 간격

컨테이너 쿼리도 “가로 화면이면 가로 배치”가 아니라 조건식의 폭을 비교합니다. 사이드바가 30rem보다 커지면 그 안의 카드도 가로 배치될 수 있습니다. 항상 좁은 사이드바를 원한다면 부모 레이아웃에도 최대 폭을 정해야 합니다.

크기 조회용 부모 만들기

.slot { container: panel / inline-size; }
@container panel (min-width: 30rem) {
  .card { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 1rem; }
}

container: panel / inline-size는 이름을 panel로, 크기 조회 유형을 inline-size로 지정하는 단축 속성입니다. 일반적인 가로쓰기에서는 inline 방향이 가로이므로 부모의 가로 크기를 조회하는 예제에 적합합니다. 세로쓰기에서는 방향의 의미가 달라지므로 항상 화면의 가로 축과 같다고 일반화하지 않습니다.

조건을 평가할 부모는 .slot이고 조건부 스타일을 받을 자손은 .card입니다. 카드 자신의 크기를 조회해 카드 자신을 바꾸는 구조로 작성하지 마세요. 크기가 바뀌면서 조건이 반복해서 뒤집히는 문제를 피하기 위해 크기 쿼리는 적합한 조상을 기준으로 동작합니다.

inline-size 격리는 부모의 해당 방향 크기를 자식 내용과 독립적으로 계산하게 합니다. 따라서 컨테이너의 폭을 자식 내용만으로 결정하려는 shrink-to-fit 배치에서는 예상과 다르게 줄어들 수 있습니다. 이 강의에서는 블록의 이용 가능한 폭이나 Grid 트랙이 부모 폭을 제공하게 합니다. 높이까지 조회해야 한다는 이유로 size를 무작정 사용하면 자식만으로 높이를 정하던 상자가 줄어들 수도 있습니다. MDN의 container-type 설명에서 크기 격리의 범위를 확인할 수 있습니다.

전체 예제: 같은 카드, 다른 부모 폭

container-card.html로 저장해 브라우저에서 여세요. 색 상자는 그림 영역을 표현하는 예제 요소이므로 별도 이미지 파일은 없습니다. 큰 화면에서는 본문과 최대 20rem 사이드바를 나누고, 44rem 이하에서는 두 영역을 한 열로 배치합니다.

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>container-card.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); gap: 1rem; width: min(100%, 75rem); margin-inline: auto; padding: 1rem; }
.slot { min-width: 0; container: panel / inline-size; }
.card { min-width: 0; padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.media { min-height: 7rem; background: #287699; }
.card h2 { font-size: 1.25rem; } .card p { margin-bottom: 0; }
@container panel (min-width: 30rem) {
  .card { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 1rem; align-items: start; }
}
@media (max-width: 44rem) { .layout { grid-template-columns: minmax(0, 1fr); } }
</style>
</head>
<body>
<div class="layout">
<main class="slot"><article class="card"><div class="media" aria-hidden="true"></div><div><h2>본문 영역에서 쓰는 카드</h2><p>부모 폭이 30rem 이상이면 그림과 글을 나란히 놓습니다.</p></div></article></main>
<aside class="slot"><article class="card"><div class="media" aria-hidden="true"></div><div><h2>사이드바에서 쓰는 같은 카드</h2><p>이 예제의 큰 화면 사이드바는 20rem을 넘지 않으므로 세로 배치를 유지합니다.</p></div></article></aside>
</div>
</body>
</html>

초기 글꼴 크기가 16px인 기본 환경에서 30rem은 480px입니다. 큰 화면의 본문 부모가 480px 이상이면 카드 내부가 2열이 되고, 최대 320px인 사이드바의 카드 내부는 세로 배치됩니다. 좁은 화면에서는 사이드바가 아래로 내려가면서 그 부모가 오히려 넓어질 수도 있습니다. 따라서 600px 화면에서 두 카드가 모두 가로 배치되는 것도 부모 폭이 조건을 만족하면 정상입니다.

44rem의 바깥 조건과 30rem의 안쪽 조건은 서로 다른 문제를 해결합니다. 앞의 조건은 페이지 전체에서 영역을 어떻게 놓을지 정하고, 뒤의 조건은 각 카드가 받은 공간 안에서 그림과 글을 어떻게 놓을지 정합니다. 부모 이름이 같아도 각 카드는 자신의 조상 중 조건에 적합한 panel을 찾습니다.

중첩된 컨테이너와 이름 선택하기

이름 없는 크기 쿼리는 해당 조건을 조회할 수 있는 가장 가까운 조상 컨테이너를 선택합니다. 바깥 영역이 넓어도 중간에 더 좁은 컨테이너가 추가되면 결과가 달라질 수 있습니다. 다음 예제를 container-nested.html로 저장하면 이 차이를 따로 확인할 수 있습니다.

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>container-nested.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
main { width: min(100%, 42rem); padding: 1rem; }
.outer { width: 100%; container: outer / inline-size; border: 2px solid #789; padding: 1rem; }
.inner { width: min(100%, 17rem); container-type: inline-size; }
.card { padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.media { min-height: 5rem; background: #287699; }
@container (min-width: 30rem) {
  .card { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; }
}
</style>
</head>
<body>
<main><h1>어느 부모의 폭을 조회할까요?</h1><div class="outer"><div class="inner"><article class="card"><div class="media" aria-hidden="true"></div><p>가까운 안쪽 컨테이너는 최대 17rem입니다.</p></article></div></div></main>
</body>
</html>

넓은 화면에서 바깥 컨테이너는 30rem보다 크지만 안쪽은 최대 17rem입니다. 이름 없는 조건은 가까운 안쪽을 선택하므로 카드가 세로 배치됩니다. 다른 코드는 유지하고 @container (min-width: 30rem)만 다음처럼 바꿔 보세요.

@container outer (min-width: 30rem) {
  .card { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; }
}

이제 이름이 outer인 바깥 조상을 선택하므로 넓은 화면에서 카드가 가로 배치됩니다. 카드 자체는 여전히 최대 17rem 안에 있으므로 본문 폭이 충분한지는 별도로 확인해야 합니다. 이름은 잘못된 기준을 자동으로 고치는 속성이 아니라 어떤 조상을 기준으로 삼을지 명시하는 기능입니다. 비교 후 이름 없는 조건으로 복구하세요.

조건이 적용되지 않을 때 점검하기

  1. 조건부 선택자의 대상이 컨테이너의 자손인지 확인합니다. 부모 자체에 같은 스타일을 적용하려고 하지 않았는지 점검합니다.
  2. 개발자 도구에서 부모의 container-type과 실제 콘텐츠 폭을 확인합니다. padding과 border를 포함한 바깥 폭과 조회할 크기를 혼동하지 않습니다.
  3. 중간에 새로운 컨테이너가 생겼는지 확인하고, 필요하면 container-name으로 기준을 명시합니다.
  4. container-type만 normal로 바꿔 보면 크기 쿼리가 적용되지 않는 차이를 볼 수 있습니다. 확인 후 inline-size로 복구합니다.

기본 카드를 세로 배치로 두면 크기 쿼리를 지원하지 않는 환경에서도 내용은 읽을 수 있습니다. 지원 환경에서는 가로 배치가 추가되는 구성으로 시작하세요. 실제 지원 범위와 필요한 브라우저는 MDN의 컨테이너 크기 쿼리 안내를 참고하고 대상 환경에서 확인합니다.

같은 카테고리의 다른 글
CSS 강좌 / 상속(inheritance)

CSS 강좌 / 상속(inheritance)

CSS 속성은 상속하는 속성과 상속하지 않는 속성이 있습니다. 상속하는 속성은 자식 요소에 영향을 미칩니다. 상속하지 않는 속성은 자식 요소에 영향을 미치지 않습니다.

CSS 강좌 / 세로 가운데 정렬하는 방법

CSS 강좌 / 세로 가운데 정렬하는 방법

CSS에서 텍스트의 가로 가운데 정렬은 text-align 속성을 이용합니다. 요소의 가로 가운데 정렬은 margin 속성을 이용합니다. 세로 정렬을 가운데로 하려면 어떻게 해야 할까요? 네 가지 방법을 소개합니다.

CSS 강좌 / 배경색과 배경 이미지 설정하기

CSS 강좌 / 배경색과 배경 이미지 설정하기

실습용 이미지를 내려받아 배경색과 배경 이미지 경로를 설정합니다. cover·contain의 잘림과 빈 공간, 반복과 위치를 비교하고 배경이 안 보일 때의 확인 순서를 설명합니다.

CSS 강좌 / 박스 모델과 margin, padding, border 이해하기

CSS 강좌 / 박스 모델과 margin, padding, border 이해하기

콘텐츠·padding·border·margin의 위치와 너비 계산을 도해와 실행 예제로 설명합니다. 여백 축약 규칙과 세로 margin 합치기를 비교하고 개발자 도구에서 실제 크기를 확인합니다.

CSS 강좌 / 문단과 목록의 간격, 정렬, 목록 표시 설정하기

CSS 강좌 / 문단과 목록의 간격, 정렬, 목록 표시 설정하기

문단 안의 줄 간격, 문단 사이 여백, 첫 줄 들여쓰기를 구분합니다. 긴 목록을 이용해 outside·inside 표시 위치와 항목 간격을 비교하고 정렬 문제를 확인할 수 있습니다.

CSS 강좌 / 링크 꾸미는 방법

CSS 강좌 / 링크 꾸미는 방법

링크의 기본 밑줄과 방문·호버·활성 상태를 설정하고 키보드 포커스를 표시합니다. 두 파일로 방문 이력과 Tab·Enter 동작을 확인하며 상태 규칙이 겹칠 때의 결과를 설명합니다.

CSS / Tutorial / 마름모 만드는 방법

CSS / Tutorial / 마름모 만드는 방법

CSS로 삼각형을 만들 수 있다면 마름모는 쉽게 만들 수 있습니다. 삼각형을 두 개 붙이면 마름모가 되기 때문입니다. 마름모는 네 변의 길이가 같은 사각형이므로, 이등변삼각형 두 개를 붙이며 됩니다.

CSS 강좌 / 테두리, 둥근 모서리, 그림자 만들기

CSS 강좌 / 테두리, 둥근 모서리, 그림자 만들기

테두리와 윤곽선의 차이, 둥근 모서리, 그림자의 이동·흐림·퍼짐을 설명합니다. 원·타원과 부모 영역에서 그림자가 잘리는 상황을 실제 예제로 비교합니다.

CSS 강좌 / 상속·우선순위·캐스케이드로 최종 스타일 결정하기

CSS 강좌 / 상속·우선순위·캐스케이드로 최종 스타일 결정하기

상속과 직접 지정의 차이부터 중요도·선택자 우선순위·작성 순서까지 단계별로 판단합니다. 같은 문단의 색상·여백·테두리를 예측하는 전체 예제와 !important·인라인 스타일의 비교 실습을 제공합니다.

CSS 강좌 / Flexbox로 요소를 한 방향에 배치하기

CSS 강좌 / Flexbox로 요소를 한 방향에 배치하기

Flexbox의 직접 자식, 주축과 교차축, row·column·reverse의 차이를 전체 예제로 익힙니다. 초기 크기와 키보드 순서를 비교하면서 배치가 달라지는 이유를 확인합니다.