
Flex에서 만든 학습 카드로 Grid의 행·열, minmax, auto-fit과 auto-fill을 비교합니다. 작은 코드에서 시작해 두 파일로 완성하고 긴 내용의 넘침까지 확인합니다.
시작·완성 예제 ZIP 내려받기 — 압축을 푼 뒤 starter/index.html을 브라우저에서 엽니다. 외부 서버나 JavaScript 없이 실행할 수 있습니다. Network 확인이 필요한 경우 README의 로컬 서버 실행 방법을 따르세요.

1. Flex 다음에 Grid를 사용하는 이유
앞선 Flex 실습의 HTML 문서, CSS 박스, Flex 배치 카드를 그대로 사용합니다. 바꾸는 핵심은 목록 부모의 배치입니다. Flex는 각 줄에서 공간을 나누므로 마지막 줄의 카드가 넓어질 수 있습니다. Grid는 같은 열 트랙을 여러 행이 공유하게 만들 수 있습니다. 카드 내부에서 링크를 아래로 미는 Flex는 계속 써도 됩니다. 두 방식은 한 페이지에서 함께 사용할 수 있습니다.
.course-list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
}
이 코드를 적용하면 목록의 직접 자식 세 개가 같은 너비의 열에 놓입니다. repeat(3, …)은 트랙 정의를 세 번 반복합니다. fr은 남는 공간을 비율로 나누는 단위라서 1fr 세 개는 같은 비율이고 2fr 1fr은 남는 공간을 2:1로 나눕니다. gap은 트랙 사이 공간이며 바깥 여백을 추가하지 않습니다. 완성 CSS는 좁은 화면에 한 열을 먼저 제공하고 48rem 이상에서만 세 열로 전환합니다.
2. 컨테이너, 아이템, 트랙과 축 구분하기
| 개념 | 이 예제에서 확인할 곳 |
|---|---|
| Grid 컨테이너 | display: grid가 있는 .course-list |
| Grid 아이템 | 목록의 직접 자식 article 세 개 |
| 열 트랙 | grid-template-columns로 정한 공간 |
| 행 트랙 | 카드가 늘어나면 자동 배치로 추가되는 행 |
| 그리드 선 | 트랙을 나누는 경계이며, 세 열에는 네 개의 열 선이 생깁니다. |
이 문서는 기본 writing-mode: horizontal-tb를 사용합니다. 이때 열은 좌우로 나열되고 행은 위아래로 쌓입니다. justify-items는 각 셀의 인라인 축, align-items는 블록 축에서 아이템을 정렬합니다. 세로쓰기에서는 물리적인 가로·세로 대응이 달라지므로 justify를 무조건 가로로 외우지 마세요. justify-content와 align-content는 아이템 하나가 아니라 전체 트랙 묶음과 컨테이너 사이의 남는 공간을 다룹니다.
3. auto-fit과 auto-fill을 실제로 비교하기
.fit { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
.fill { grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr)); }
비교 영역에는 각각 카드 두 개만 넣었습니다. 창을 충분히 넓히면 최소 14rem짜리 열이 세 개 이상 들어갈 수 있습니다. auto-fill은 들어갈 수 있는 반복 열을 유지하여 오른쪽에 비어 있는 열 공간이 보입니다. auto-fit은 배치 후 비어 있는 반복 트랙을 접으므로 두 카드가 남은 너비를 나눠 가집니다. 빈 열이 생기지 않는 좁은 폭에서는 두 예제가 같아 보이는 것이 정상입니다.
minmax의 최소값에 min(100%, 14rem)을 넣은 이유는 가용 너비가 14rem보다 작은 경우 한 카드가 부모보다 커지는 것을 막기 위해서입니다. 이는 카드에 무조건 14rem을 보장하는 규칙이 아닙니다. 기본 글꼴 16px이라면 비교 부모 1000px에는 224px짜리 열 네 개와 세 gap이 들어가므로 차이를 관찰하기 쉽습니다. DevTools의 Grid overlay를 켜서 카드의 실제 너비와 빈 트랙을 함께 확인하세요.
4. minmax(0, 1fr)과 긴 내용의 넘침
단순한 1fr 트랙은 자동 최소 크기의 영향을 받아 긴 내용 때문에 예상보다 덜 줄어들 수 있습니다. minmax(0, 1fr)은 트랙의 최소값을 0으로 명시합니다. 하지만 트랙이 줄어드는 것과 글자가 줄바꿈하는 것은 다른 문제입니다. 아이템의 min-width: 0과 overflow-wrap: anywhere도 함께 확인해야 합니다.
.course-card {
min-width: 0;
overflow-wrap: anywhere;
}
긴 식별자가 있는 세 번째 카드에서 overflow-wrap만 잠시 해제하면 트랙은 줄어들어도 글자가 바깥으로 나올 수 있습니다. 이후 min-width와 트랙 최소값도 한 번에 하나씩 바꿔 어떤 제약이 넘침을 만드는지 살펴보세요. overflow-x: hidden으로 잘라 버리면 원인은 남고 내용만 사라집니다. 고정 너비 이미지나 자식의 min-width가 있으면 별도로 점검해야 합니다.
5. 완료 기준과 다음 실습
- 320px에서는 첫 영역이 한 열이고 긴 식별자도 읽힙니다.
- 기본 글꼴 16px일 때 767px·768px 부근에서 첫 영역의 열 전환을 비교합니다.
- 1000px 이상에서 auto-fit의 카드가 auto-fill보다 넓어지는지 확인합니다.
- 카드를 하나 복제하면 네 번째 카드가 다음 행의 첫 열에 놓입니다. 첫 영역의 앞 행과 열 위치가 맞아야 합니다.
- Tab 순서는 HTML에 적은 링크 순서입니다. grid-row나 order로 보이는 순서만 바꾸지 않습니다.
grid-template-areas는 이름으로 영역을 배치하는 선택 학습입니다. 이름이 있는 영역은 직사각형을 이루어야 하고 DOM 읽기 순서를 바꾸지 않는 범위에서 사용합니다. 이번 실습의 목표인 카드 열 맞추기에는 필요하지 않습니다. 다음 Container Query 실습에서는 같은 카드가 놓인 부모 너비에 따라 내부 배치를 바꿉니다.
공식 규칙 확인: W3C CSS Grid 명세, MDN repeat().
전체 코드: 두 파일로 완성하기
아래 HTML은 complete/index.html, CSS는 complete/styles.css와 같습니다. 같은 폴더에 저장하세요. 이미지가 있는 실습은 ZIP에 포함된 images 폴더도 같은 위치에 둡니다.
index.html
실습 파일
파일 경로를 확인하고 같은 프로젝트 안에 저장하세요. 이미지 등 소스 목록에 없는 파일과 실행 안내는 실습 ZIP에 포함되어 있습니다.
카드의 행과 열을 함께 맞추도록 Grid 배치를 구성합니다. 수정 전은 시작 상태, 수정 후는 비교할 완성 상태입니다.
수정 전
index.html
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>학습 카드 | Grid 실습</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>Flex 실습의 HTML·CSS·Flex 카드를 Grid로 배치합니다.</p>
<section>
<h2>01 / 세 카드의 열 맞추기</h2>
<div class="course-list">
<article class="course-card">
<h3>HTML 문서</h3>
<p>내용의 역할에 맞게 제목, 목록, 폼을 작성합니다.</p>
<a class="card-link" href="#html-task">HTML 문서 과제 보기</a>
</article>
<article class="course-card">
<h3>CSS 박스</h3>
<p>패딩과 테두리가 차지하는 크기를 계산합니다. 선택자 충돌도 확인합니다.</p>
<a class="card-link" href="#css-task">CSS 박스 과제 보기</a>
</article>
<article class="course-card">
<h3>Flex 배치</h3>
<p>긴 식별자: publishingpracticewithoutspaces1234567890</p>
<a class="card-link" href="#flex-task">Flex 배치 과제 보기</a>
</article>
</div>
</section>
<section>
<h2>02 / auto-fit: 비어 있는 반복 트랙 접기</h2>
<div class="demo fit">
<article class="course-card">
<h3>HTML 문서</h3>
<p>내용의 역할에 맞게 제목, 목록, 폼을 작성합니다.</p>
<a class="card-link" href="#html-task">HTML 문서 과제 보기</a>
</article>
<article class="course-card">
<h3>CSS 박스</h3>
<p>패딩과 테두리가 차지하는 크기를 계산합니다. 선택자 충돌도 확인합니다.</p>
<a class="card-link" href="#css-task">CSS 박스 과제 보기</a>
</article>
</div>
</section>
<section>
<h2>03 / auto-fill: 비어 있는 반복 트랙 유지하기</h2>
<div class="demo fill">
<article class="course-card">
<h3>HTML 문서</h3>
<p>내용의 역할에 맞게 제목, 목록, 폼을 작성합니다.</p>
<a class="card-link" href="#html-task">HTML 문서 과제 보기</a>
</article>
<article class="course-card">
<h3>CSS 박스</h3>
<p>패딩과 테두리가 차지하는 크기를 계산합니다. 선택자 충돌도 확인합니다.</p>
<a class="card-link" href="#css-task">CSS 박스 과제 보기</a>
</article>
</div>
</section>
<section>
<h2>확인 과제</h2>
<ul>
<li id="html-task">문서 순서와 Tab 순서를 비교합니다.</li>
<li id="css-task">DevTools에서 긴 식별자의 줄바꿈을 껐다 켭니다.</li>
<li id="flex-task">
두 카드만 있는 비교 영역을 1000px 이상의 창에서 확인합니다.
</li>
</ul>
</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;
}
/* TODO 1: 목록에 Grid와 gap을 적용하세요. */
.course-list,
.demo {
display: block;
}
.course-card {
padding: 1rem;
border-top: 1px solid #777;
background: #f3f3f3;
}
/* TODO 2: 48rem 이상에서 세 열을 만드세요. */
/* TODO 3: .fit과 .fill에 자동 반복 열을 각각 적용하세요. */
/* TODO 4: 긴 식별자의 넘침과 카드 링크 위치를 처리하세요. */
수정 방법
index.html
이 파일은 변경하지 않습니다. 다른 파일을 수정하는 동안 기존 내용을 유지하세요.
styles.css
styles.css에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.
카드의 행과 열을 함께 맞추도록 Grid 배치를 구성합니다.
--- 수정 전/styles.css
+++ 수정 후/styles.css
@@ -38,16 +38,38 @@
section {
margin-block: 2.5rem;
}
-/* TODO 1: 목록에 Grid와 gap을 적용하세요. */
.course-list,
.demo {
- display: block;
+ display: grid;
+ gap: 1rem;
+}
+.course-list {
+ grid-template-columns: minmax(0, 1fr);
}
.course-card {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
padding: 1rem;
border-top: 1px solid #777;
background: #f3f3f3;
+ overflow-wrap: anywhere;
}
-/* TODO 2: 48rem 이상에서 세 열을 만드세요. */
-/* TODO 3: .fit과 .fill에 자동 반복 열을 각각 적용하세요. */
-/* TODO 4: 긴 식별자의 넘침과 카드 링크 위치를 처리하세요. */
+.card-link {
+ margin-top: auto;
+ padding-top: 1rem;
+}
+.demo {
+ background: #ddd;
+}
+.fit {
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
+}
+.fill {
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
+}
+@media (min-width: 48rem) {
+ .course-list {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.
수정 후
index.html
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>학습 카드 | Grid 실습</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>Flex 실습의 HTML·CSS·Flex 카드를 Grid로 배치합니다.</p>
<section>
<h2>01 / 세 카드의 열 맞추기</h2>
<div class="course-list">
<article class="course-card">
<h3>HTML 문서</h3>
<p>내용의 역할에 맞게 제목, 목록, 폼을 작성합니다.</p>
<a class="card-link" href="#html-task">HTML 문서 과제 보기</a>
</article>
<article class="course-card">
<h3>CSS 박스</h3>
<p>패딩과 테두리가 차지하는 크기를 계산합니다. 선택자 충돌도 확인합니다.</p>
<a class="card-link" href="#css-task">CSS 박스 과제 보기</a>
</article>
<article class="course-card">
<h3>Flex 배치</h3>
<p>긴 식별자: publishingpracticewithoutspaces1234567890</p>
<a class="card-link" href="#flex-task">Flex 배치 과제 보기</a>
</article>
</div>
</section>
<section>
<h2>02 / auto-fit: 비어 있는 반복 트랙 접기</h2>
<div class="demo fit">
<article class="course-card">
<h3>HTML 문서</h3>
<p>내용의 역할에 맞게 제목, 목록, 폼을 작성합니다.</p>
<a class="card-link" href="#html-task">HTML 문서 과제 보기</a>
</article>
<article class="course-card">
<h3>CSS 박스</h3>
<p>패딩과 테두리가 차지하는 크기를 계산합니다. 선택자 충돌도 확인합니다.</p>
<a class="card-link" href="#css-task">CSS 박스 과제 보기</a>
</article>
</div>
</section>
<section>
<h2>03 / auto-fill: 비어 있는 반복 트랙 유지하기</h2>
<div class="demo fill">
<article class="course-card">
<h3>HTML 문서</h3>
<p>내용의 역할에 맞게 제목, 목록, 폼을 작성합니다.</p>
<a class="card-link" href="#html-task">HTML 문서 과제 보기</a>
</article>
<article class="course-card">
<h3>CSS 박스</h3>
<p>패딩과 테두리가 차지하는 크기를 계산합니다. 선택자 충돌도 확인합니다.</p>
<a class="card-link" href="#css-task">CSS 박스 과제 보기</a>
</article>
</div>
</section>
<section>
<h2>확인 과제</h2>
<ul>
<li id="html-task">문서 순서와 Tab 순서를 비교합니다.</li>
<li id="css-task">DevTools에서 긴 식별자의 줄바꿈을 껐다 켭니다.</li>
<li id="flex-task">
두 카드만 있는 비교 영역을 1000px 이상의 창에서 확인합니다.
</li>
</ul>
</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;
}
.course-list,
.demo {
display: grid;
gap: 1rem;
}
.course-list {
grid-template-columns: minmax(0, 1fr);
}
.course-card {
min-width: 0;
display: flex;
flex-direction: column;
padding: 1rem;
border-top: 1px solid #777;
background: #f3f3f3;
overflow-wrap: anywhere;
}
.card-link {
margin-top: auto;
padding-top: 1rem;
}
.demo {
background: #ddd;
}
.fit {
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
}
.fill {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
}
@media (min-width: 48rem) {
.course-list {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
styles.css
이 글이 도움이 되었나요?
웹 퍼블리싱 학습 순서
필수 13개 · 전체 15개
읽음 기록 관리
전체 과정 목차 (15개)
- 필수 학습 · HTML 입문: 시맨틱 문서와 접근 가능한 폼 만들기
- 필수 학습 · CSS 입문: 박스 모델·선택자·캐스케이드 직접 확인하기
- 필수 학습 · CSS Flex와 미디어 쿼리 입문: 반응형 카드 페이지 만들기
- 필수 학습 · CSS position 완전 이해: relative·absolute·fixed·sticky 실습
- 필수 학습 · HTML details와 summary로 접근 가능한 FAQ 만들기
- 필수 학습 · CSS Grid 실습: Flex 학습 카드로 열 맞추기와 auto-fit 비교 현재 글
- 필수 학습 · CSS overflow 해결: 긴 텍스트·min-width: 0·말줄임 실습
- 필수 학습 · HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기
- 필수 선수 · HTML CSS 이미지 문제 해결 실습: 경로·비율·잘림·흐림·CLS
- 필수 학습 · Container Query 실습: 같은 카드를 본문과 사이드바에 배치하기
- 선택 참고 · CSS Baseline 보는 법: 지원 범위와 폴백을 직접 확인하기
- 필수 학습 · CSS z-index가 안 먹힐 때: stacking context와 부모 층 디버깅
- 필수 선수 · 웹 접근성 기초 점검: 키보드 순서·초점 표시·확대 화면을 직접 확인하는 체크리스트
- 필수 학습 · HTML CSS 종합 실습: 소개·카드·이미지·요금표·문의 폼·FAQ가 있는 반응형 서비스 페이지
- 선택 참고 · 프론트엔드 SEO 기초: HTML 구조와 이미지 성능 진단하기
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.
BlogFlow AI
이 글과 함께 공부하기
무엇을 도와드릴까요?
읽다가 막힌 부분을 물어보세요. 이 글을 바탕으로 설명하고, 이어서 읽을 본문을 연결합니다.