Container Query 실습: 같은 카드를 본문과 사이드바에 배치하기

2026.04.17·수정 2026.09.14·약 24분·작성: 해비·블로그 소개

같은 카드 두 개를 넓은 본문과 좁은 사이드바에 놓고 media query와 container query의 기준을 비교합니다. 카드별 wrapper와 세로 기본값으로 완성합니다.

시작·완성 예제 ZIP 내려받기 — 압축을 푼 뒤 starter/index.html을 브라우저에서 엽니다. 외부 서버나 JavaScript 없이 실행할 수 있습니다. Network 확인이 필요한 경우 README의 로컬 서버 실행 방법을 따르세요.

Container Query 실습: 같은 카드를 본문과 사이드바에 배치하기 핵심 개념 도식
Container Query는 화면 전체가 아닌 카드를 담은 컨테이너의 크기를 기준으로 배치를 바꿉니다.

1. 화면 너비만으로는 알 수 없는 것

한 페이지의 본문은 넓고 사이드바는 좁을 수 있습니다. 두 카드의 viewport는 같은데 사용할 수 있는 공간은 다릅니다. 이번에는 같은 제목·이미지·설명을 가진 CSS 박스 카드를 두 곳에 넣습니다. 넓은 본문에서는 이미지와 글이 나란히, 좁은 사이드바에서는 위아래로 놓이게 만듭니다. HTML과 기본 박스 모델, Grid의 부모·자식 관계를 알고 시작하면 좋습니다.

.card-slot {
  container-type: inline-size;
  container-name: course;
}
@container course (min-width: 30rem) {
  .course-card {
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  }
}

이 코드에서 질문하는 대상은 .card-slot의 크기이고, 바꾸는 대상은 그 안의 .course-card입니다. card-slot은 각 카드마다 하나씩 있습니다. 전체 목록에 하나만 컨테이너를 지정하면 카드가 받은 실제 공간과 다른 너비를 질문하게 될 수 있습니다. 이름 course는 여러 독립 컨테이너에서 재사용할 수 있으며, 각 카드에 적용되는 규칙은 해당 카드의 조상 중 이름과 조건 종류에 맞는 컨테이너를 찾습니다.

2. 구조를 먼저 만들고 기본값을 세로로 두기

<div class="card-slot">
  <article class="course-card">
    <img src="images/checker-480.png" width="480" height="320" alt="격자와 원으로 만든 레이아웃 학습용 이미지">
    <div>카드 제목과 설명</div>
  </article>
</div>

wrapper를 별도로 두면 측정할 부모와 바꿀 자식이 명확합니다. 자기 자신의 크기를 조건으로 자기 자신의 레이아웃을 바꾸는 구조는 피하세요. 크기 조건이 바뀌며 배치가 다시 크기를 바꾸는 순환을 예방하도록 컨테이너 질의가 설계되어 있습니다. inline-size는 인라인 축의 크기 질의를 위한 설정이고, 기본 가로쓰기 문서에서는 보통 너비에 대응합니다. 이 실습의 min-width 조건도 가로쓰기를 전제로 합니다.

완성본에서는 기본 .course-card를 한 열 Grid로 둡니다. size query를 처리하지 못하는 환경이나 조건에 아직 도달하지 않은 카드에서는 이 세로 배치를 사용합니다. 콘텐츠를 숨기지 않으므로 글과 링크를 그대로 읽을 수 있습니다. 임의의 높이까지 격리하는 container-type: size를 넣을 이유는 없습니다. 컨테이너 폭은 바깥 Grid 트랙이 정해 주고 높이는 콘텐츠가 채우는 구조입니다.

3. media query와 container query의 책임 나누기

조건 질문하는 크기 바뀌는 부분
@media (min-width: 64rem) viewport 본문과 18rem 사이드바를 나란히 배치
@container course (min-width: 30rem) 각 card-slot의 콘텐츠 너비 그 안 카드의 이미지와 글을 두 열로 배치

64rem은 페이지 전체에 본문과 사이드바를 함께 두기 위한 실습 기준이고, 30rem은 카드가 두 열을 가질 기준입니다. 모두 특정 기기 이름이 아니라 이 콘텐츠에 사용할 공간을 기준으로 정한 값입니다. 가로쓰기, 기본 글꼴 16px, 1200px 창에서는 바깥 여백과 32px 간격, 288px 사이드바를 뺀 본문 공간이 480px보다 넓습니다. 본문만 가로 카드가 되고 사이드바는 세로 카드가 되는 조건을 만들어 줍니다. 폰트 설정과 확대에 따라 픽셀 대응은 달라질 수 있습니다.

뷰포트가 64rem보다 작아 사이드바가 아래로 내려가면 두 영역 모두 넓은 공간을 받을 수도 있습니다. 이 경우 아래쪽 카드도 가로로 바뀔 수 있습니다. “사이드바 카드는 항상 세로”라는 위치 규칙이 아니라 “주어진 공간이 충분한 카드만 가로”라는 컴포넌트 규칙이기 때문입니다.

4. 바뀌지 않을 때 확인할 순서

  • DevTools에서 card-slot의 계산된 container-type이 inline-size인지 확인합니다.
  • 조건 이름 course와 container-name의 철자를 비교합니다.
  • 규칙으로 바꾸려는 .course-card가 측정 컨테이너의 자손인지 확인합니다.
  • 컨테이너의 실제 너비를 확인합니다. viewport가 넓다는 사실만으로 조건이 참이 되지는 않습니다.
  • 나중에 선언한 CSS가 grid-template-columns를 덮어쓰는지 확인합니다.
  • @container 규칙을 꺼 보고 기본 세로 카드가 읽을 수 있는 상태인지 확인합니다.

5. 완료 기준과 선택 학습

  • starter의 TODO를 채우면 1200px 창에서 본문 카드는 두 열, 사이드바 카드는 한 열입니다.
  • 320px 창에서 한 열로 읽히며 이미지가 부모 밖으로 나가지 않습니다.
  • DevTools에서 card-slot 너비를 30rem 전후로 조절하면 내부 카드만 전환됩니다.
  • 페이지를 200% 확대해 모든 설명과 링크에 접근할 수 있는지 확인합니다.

이 글의 실행 범위는 크기를 기준으로 한 size query입니다. style query와 scroll-state query는 별도의 조건 종류이며 기본 실습 코드에 섞지 않습니다. 필요한 경우 아래 공식 문서의 연결된 가이드와 브라우저 호환성 표를 기능별로 확인하세요. 크기 질의 지원 여부만으로 모든 컨테이너 질의가 같은 환경에서 동작한다고 판단하지 않습니다.

공식 문서: MDN CSS container queries, W3C CSS Containment Level 3. 이전 Grid 실습과 비교하면 페이지 배치와 카드 내부 배치를 구분하기 쉽습니다.

전체 코드: 두 파일로 완성하기

아래 HTML은 complete/index.html, CSS는 complete/styles.css와 같습니다. 같은 폴더에 저장하세요. 이미지가 있는 실습은 ZIP에 포함된 images 폴더도 같은 위치에 둡니다.

index.html

실습 파일

파일 경로를 확인하고 같은 프로젝트 안에 저장하세요. 이미지 등 소스 목록에 없는 파일과 실행 안내는 실습 ZIP에 포함되어 있습니다.

뷰포트 대신 카드를 담는 컨테이너의 너비에 따라 배치를 전환합니다. 수정 전은 시작 상태, 수정 후는 비교할 완성 상태입니다.

수정 전

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>같은 카드, 다른 부모 | Container Query 실습</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <a href="#main">본문으로 바로 가기</a>
    <header><p>퍼블리싱 연습실</p></header>
    <main id="main" tabindex="-1">
      <h1>화면은 하나, 카드의 공간은 둘</h1>
      <p>넓은 창에서 본문 카드와 사이드바 카드를 동시에 비교하세요.</p>
      <div class="page-layout">
        <section>
          <h2>본문 / 넓은 공간</h2>
          <div class="card-slot">
            <article class="course-card">
              <img
                src="images/checker-480.png"
                width="480"
                height="320"
                alt="격자와 원으로 만든 레이아웃 학습용 이미지"
              />
              <div>
                <h3>CSS 박스</h3>
                <p>
                  패딩과 테두리가 차지하는 크기를 계산합니다. 같은 카드를 넓은 본문과
                  좁은 사이드바에 놓습니다.
                </p>
                <a href="#practice">확인 과제 보기</a>
              </div>
            </article>
          </div>
        </section>
        <aside>
          <h2>사이드바 / 좁은 공간</h2>
          <div class="card-slot">
            <article class="course-card">
              <img
                src="images/checker-480.png"
                width="480"
                height="320"
                alt="격자와 원으로 만든 레이아웃 학습용 이미지"
              />
              <div>
                <h3>CSS 박스</h3>
                <p>
                  패딩과 테두리가 차지하는 크기를 계산합니다. 같은 카드를 넓은 본문과
                  좁은 사이드바에 놓습니다.
                </p>
                <a href="#practice">확인 과제 보기</a>
              </div>
            </article>
          </div>
        </aside>
      </div>
      <section id="practice">
        <h2>확인 과제</h2>
        <p>
          card-slot의 너비를 확인하고 30rem 전후에서 이미지와 글의 배치가 달라지는지
          관찰하세요.
        </p>
      </section>
    </main>
  </body>
</html>

styles.css

*,
*::before,
*::after {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 1rem;
  color: #202020;
  background: #fff;
  font-family: system-ui, sans-serif;
  line-height: 1.7;
}
header,
main {
  max-width: 72rem;
  margin-inline: auto;
}
a {
  color: inherit;
  text-underline-offset: 0.2em;
}
a:focus-visible {
  outline: 2px solid #202020;
  outline-offset: 4px;
}
h1 {
  font-size: clamp(1.6rem, 4vw, 2.5rem);
  line-height: 1.3;
}
h2 {
  font-size: 1.3rem;
}
h3 {
  margin-top: 0;
  font-size: 1.1rem;
}
section {
  margin-block: 2.5rem;
}
.page-layout {
  display: grid;
  gap: 2rem;
}
.course-card {
  display: grid;
  gap: 1rem;
  padding: 1rem;
  border-top: 1px solid #777;
  background: #f3f3f3;
}
.course-card img {
  display: block;
  width: 100%;
  height: auto;
}
/* TODO 1: 64rem 이상에서 본문과 18rem 사이드바를 배치하세요. */
/* TODO 2: 각 .card-slot을 course 이름의 inline-size 컨테이너로 만드세요. */
/* TODO 3: course 너비 30rem 이상일 때 내부 .course-card만 두 열로 바꾸세요. */

수정 방법

index.html

이 파일은 변경하지 않습니다. 다른 파일을 수정하는 동안 기존 내용을 유지하세요.

styles.css

styles.css에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.

뷰포트 대신 카드를 담는 컨테이너의 너비에 따라 배치를 전환합니다.

--- 수정 전/styles.css
+++ 수정 후/styles.css
@@ -40,20 +40,42 @@
 }
 .page-layout {
   display: grid;
+  grid-template-columns: minmax(0, 1fr);
   gap: 2rem;
+}
+.page-layout > * {
+  min-width: 0;
+  margin-block: 1rem;
+}
+.card-slot {
+  container-type: inline-size;
+  container-name: course;
 }
 .course-card {
   display: grid;
+  grid-template-columns: minmax(0, 1fr);
   gap: 1rem;
   padding: 1rem;
   border-top: 1px solid #777;
   background: #f3f3f3;
+  overflow-wrap: anywhere;
+}
+.course-card > * {
+  min-width: 0;
 }
 .course-card img {
   display: block;
   width: 100%;
   height: auto;
 }
-/* TODO 1: 64rem 이상에서 본문과 18rem 사이드바를 배치하세요. */
-/* TODO 2: 각 .card-slot을 course 이름의 inline-size 컨테이너로 만드세요. */
-/* TODO 3: course 너비 30rem 이상일 때 내부 .course-card만 두 열로 바꾸세요. */
+@media (min-width: 64rem) {
+  .page-layout {
+    grid-template-columns: minmax(0, 1fr) 18rem;
+  }
+}
+@container course (min-width: 30rem) {
+  .course-card {
+    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
+    align-items: start;
+  }
+}

수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.

수정 후

index.html

<!doctype html>
<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>같은 카드, 다른 부모 | Container Query 실습</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <a href="#main">본문으로 바로 가기</a>
    <header><p>퍼블리싱 연습실</p></header>
    <main id="main" tabindex="-1">
      <h1>화면은 하나, 카드의 공간은 둘</h1>
      <p>넓은 창에서 본문 카드와 사이드바 카드를 동시에 비교하세요.</p>
      <div class="page-layout">
        <section>
          <h2>본문 / 넓은 공간</h2>
          <div class="card-slot">
            <article class="course-card">
              <img
                src="images/checker-480.png"
                width="480"
                height="320"
                alt="격자와 원으로 만든 레이아웃 학습용 이미지"
              />
              <div>
                <h3>CSS 박스</h3>
                <p>
                  패딩과 테두리가 차지하는 크기를 계산합니다. 같은 카드를 넓은 본문과
                  좁은 사이드바에 놓습니다.
                </p>
                <a href="#practice">확인 과제 보기</a>
              </div>
            </article>
          </div>
        </section>
        <aside>
          <h2>사이드바 / 좁은 공간</h2>
          <div class="card-slot">
            <article class="course-card">
              <img
                src="images/checker-480.png"
                width="480"
                height="320"
                alt="격자와 원으로 만든 레이아웃 학습용 이미지"
              />
              <div>
                <h3>CSS 박스</h3>
                <p>
                  패딩과 테두리가 차지하는 크기를 계산합니다. 같은 카드를 넓은 본문과
                  좁은 사이드바에 놓습니다.
                </p>
                <a href="#practice">확인 과제 보기</a>
              </div>
            </article>
          </div>
        </aside>
      </div>
      <section id="practice">
        <h2>확인 과제</h2>
        <p>
          card-slot의 너비를 확인하고 30rem 전후에서 이미지와 글의 배치가 달라지는지
          관찰하세요.
        </p>
      </section>
    </main>
  </body>
</html>

styles.css

*,
*::before,
*::after {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 1rem;
  color: #202020;
  background: #fff;
  font-family: system-ui, sans-serif;
  line-height: 1.7;
}
header,
main {
  max-width: 72rem;
  margin-inline: auto;
}
a {
  color: inherit;
  text-underline-offset: 0.2em;
}
a:focus-visible {
  outline: 2px solid #202020;
  outline-offset: 4px;
}
h1 {
  font-size: clamp(1.6rem, 4vw, 2.5rem);
  line-height: 1.3;
}
h2 {
  font-size: 1.3rem;
}
h3 {
  margin-top: 0;
  font-size: 1.1rem;
}
section {
  margin-block: 2.5rem;
}
.page-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
}
.page-layout > * {
  min-width: 0;
  margin-block: 1rem;
}
.card-slot {
  container-type: inline-size;
  container-name: course;
}
.course-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  padding: 1rem;
  border-top: 1px solid #777;
  background: #f3f3f3;
  overflow-wrap: anywhere;
}
.course-card > * {
  min-width: 0;
}
.course-card img {
  display: block;
  width: 100%;
  height: auto;
}
@media (min-width: 64rem) {
  .page-layout {
    grid-template-columns: minmax(0, 1fr) 18rem;
  }
}
@container course (min-width: 30rem) {
  .course-card {
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    align-items: start;
  }
}

index.html 전체 코드 보기

styles.css

styles.css 전체 코드 보기

이 글이 도움이 되었나요?

조회 중

웹 퍼블리싱 학습 순서

필수 13개 · 전체 15개

읽음 기록 관리

전체 과정 목차 (15개)
  1. 필수 학습 · HTML 입문: 시맨틱 문서와 접근 가능한 폼 만들기
  2. 필수 학습 · CSS 입문: 박스 모델·선택자·캐스케이드 직접 확인하기
  3. 필수 학습 · CSS Flex와 미디어 쿼리 입문: 반응형 카드 페이지 만들기
  4. 필수 학습 · CSS position 완전 이해: relative·absolute·fixed·sticky 실습
  5. 필수 학습 · HTML details와 summary로 접근 가능한 FAQ 만들기
  6. 필수 학습 · CSS Grid 실습: Flex 학습 카드로 열 맞추기와 auto-fit 비교
  7. 필수 학습 · CSS overflow 해결: 긴 텍스트·min-width: 0·말줄임 실습
  8. 필수 학습 · HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기
  9. 필수 선수 · HTML CSS 이미지 문제 해결 실습: 경로·비율·잘림·흐림·CLS
  10. 필수 학습 · Container Query 실습: 같은 카드를 본문과 사이드바에 배치하기 현재 글
  11. 선택 참고 · CSS Baseline 보는 법: 지원 범위와 폴백을 직접 확인하기
  12. 필수 학습 · CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅
  13. 필수 선수 · 웹 접근성 기초 점검: 키보드 순서·초점 표시·확대 화면을 직접 확인하는 체크리스트
  14. 필수 학습 · HTML CSS 종합 실습: 소개·카드·이미지·요금표·문의 폼·FAQ가 있는 반응형 서비스 페이지
  15. 선택 참고 · 프론트엔드 SEO 기초: HTML 구조와 이미지 성능 진단하기

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기