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 강좌 / width, height, min, max, box-sizing으로 크기 조절하기

CSS 강좌 / width, height, min, max, box-sizing으로 크기 조절하기

content-box와 border-box의 너비 계산, 최소·최대 크기, 고정 높이와 내용에 맞는 높이를 비교합니다. 화면과 글자 크기를 바꾸며 넘침을 진단하고 백분율 높이의 조건을 확인합니다.

CSS 강좌 / 유튜브 동영상을 반응형으로 삽입하는 방법

CSS 강좌 / 유튜브 동영상을 반응형으로 삽입하는 방법

유튜브 영상을 블로그나 홈페이지에 넣을 때 보통 iframe을 사용합니다. 코드는 유튜브 영상 공유에서 를 클릭하여 구할 수 있습니다. 아래와 같은 코드인데, 고정폭입니다. 즉 웹브라우저의 가로폭이 커지든 작아지든 가로폭이 일정합니다. 이를 반응형으로 만들고 싶다면 아래처럼 합니다.

CSS / Tutorial / 구분선 만드는 방법

CSS / Tutorial / 구분선 만드는 방법

글의 완급 조절, 내용 전환 등을 위해 구분선을 사용하는 경우가 많습니다. 선은 여러 가지 방식으로 만들 수 있는데, 보기 좋게 꾸미는 것은 CSS로 합니다. 아래는 div 태그에 모양을 입혀서 구분선을 만드는 몇 가지 예제입니다.

CSS 강좌 / 글꼴, 글자 크기, 굵기, 줄 간격 설정하기

CSS 강좌 / 글꼴, 글자 크기, 굵기, 줄 간격 설정하기

글꼴·글자 크기·굵기·줄 간격을 설정하고 실제 사용된 글꼴을 확인하는 방법을 설명합니다. 단위 없는 line-height와 em 값의 상속 차이를 실행 가능한 예제로 비교합니다.

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

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

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

CSS 강좌 / ul, ol 가운데 정렬하는 방법

CSS 강좌 / ul, ol 가운데 정렬하는 방법

목록 자체가 아니라 목록 안의 내용만 가운데 정렬할 때는 text-align 속성을 사용합니다. 목록 요소 자체를 가운데 정렬할 때는 margin 속성을 사용합니다. 만약 요소의 내용에 맞게 자동으로 크기가 정해지게 하고 싶으면 display 속성을 사용합니다.

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

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

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

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

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

같은 카드가 본문과 사이드바의 폭에 맞춰 배치를 바꾸도록 컨테이너 쿼리를 적용합니다. 중첩 컨테이너와 이름 선택, 조건이 적용되지 않는 원인도 살펴봅니다.

CSS 강좌 / 링크 꾸미는 방법

CSS 강좌 / 링크 꾸미는 방법

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

CSS 강좌 / position의 값이 fixed일 때 가운데 정렬하는 방법

CSS 강좌 / position의 값이 fixed일 때 가운데 정렬하는 방법

position의 fixed를 이용하여 요소의 위치를 고정시킬 수 있습니다. 예를 들어 상단에 고정되는 메뉴바를 만들 때 사용합니다. 그런데, fixed를 하면 요소가 한쪽으로 치우칩니다. 만약 가운데에 위치시키고 싶다면 어떻게 할까요? transform 속성으로 해결할 수 있습니다.