
이 글에서 만드는 것
Flex의 주축과 교차축을 이해하고, 작은 화면에서는 세로로 읽히고 넓은 화면에서는 카드가 나란히 놓이는 페이지를 만듭니다. HTML 순서를 유지하면서 줄바꿈, 긴 글자, 미디어 쿼리 경계를 확인합니다.
시작·완성 예제 ZIP 내려받기 — starter에서 과제를 연습한 뒤 complete와 비교하세요.

목차
- 전체 파일 전에 작은 배치부터 만들기
- 1. 부모의 배치 축과 자식의 크기를 나누어 보기
- 2. 읽는 순서를 유지한 HTML 작성하기
- 3. 작은 화면부터 CSS를 완성하기
- 4. 미디어 쿼리 경계와 넘치는 콘텐츠 다루기
- 5. 너비와 키보드를 바꾸며 결과 확인하기
- 완성 코드: 두 파일을 같은 폴더에 저장하기
- starter에서 직접 바꿀 내용
전체 파일 전에 작은 배치부터 만들기
목록 부모가 카드 사이 배치를 맡고, 카드 자체가 제목·설명·링크의 세로 배치를 맡습니다. 아래 두 선택자는 서로 다른 Flex 컨테이너입니다. 우선 세로 배치를 확인하고 미디어 쿼리에서 가로 배치를 추가하세요.
.course-list { display: flex; flex-direction: column; gap: 1rem; }
.course-card { display: flex; flex-direction: column; min-width: 0; }
.card-link { margin-top: auto; }
@media (min-width: 48rem) {
.course-list { flex-direction: row; flex-wrap: wrap; }
.course-card { flex: 1 1 15rem; }
}
flex-basis가 15rem이라고 해서 카드가 항상 그 너비로 고정되지는 않습니다. 줄을 나눈 다음 남는 공간을 각 행 안에서 분배합니다. 이 차이를 완성 화면과 아래 너비 계산으로 확인합니다.
1. 부모의 배치 축과 자식의 크기를 나누어 보기
HTML 구조와 CSS 박스 모델, 선택자 우선순위를 알고 시작합니다. Flex는 부모 요소에 display: flex를 지정하면 그 직접 자식을 배치하는 방식입니다. 예제에서는 헤더, 카드 목록, 카드 내부에 각각 Flex를 적용하므로 같은 속성도 어느 부모에 적었는지 먼저 확인해야 합니다.
| 속성 | 이 실습에서 하는 일 |
|---|---|
| flex-direction | row는 가로쓰기 문서의 행 방향, column은 위에서 아래 방향을 주축으로 삼습니다. |
| justify-content | 주축의 남는 공간을 기준으로 항목을 배치합니다. |
| align-items | 교차축에서 각 항목을 정렬합니다. |
| gap | 항목 또는 줄 사이의 간격을 지정합니다. |
| flex-wrap | row에서 한 줄에 들어가지 않는 항목을 다음 줄로 보낼 수 있게 합니다. |
이 예제는 기본 가로쓰기 문서입니다. column으로 바꾸면 주축도 세로로 바뀌므로 justify-content를 항상 가로 정렬이라고 외우지 마세요. Flex의 축과 배치 규칙은 W3C Flexbox 명세를 기준으로 설명합니다.
2. 읽는 순서를 유지한 HTML 작성하기
새 폴더에 index.html과 styles.css를 만듭니다. 완성 HTML은 헤더 다음에 학습 카드와 확인 과제가 이어지는 완전한 문서입니다. 외부 이미지나 JavaScript 파일이 필요하지 않습니다. 링크는 모두 같은 문서의 실제 과제 항목으로 이동합니다.
HTML, CSS, Flex 카드 순서를 작은 화면에서도 그대로 읽게 만들었습니다. 각 카드는 독립적인 학습 항목이라 article로 묶고, 세 항목을 감싸는 배치용 컨테이너는 div를 사용합니다. course-list의 Flex 아이템은 세 article이며, article 안의 제목은 그 바깥 Flex의 직접 아이템이 아닙니다.
3. 작은 화면부터 CSS를 완성하기
글 마지막의 CSS 전체를 styles.css에 저장합니다. 기본 규칙은 작은 화면용 세로 배치이고, 파일 마지막 미디어 쿼리에서 넓은 화면의 가로 배치만 추가합니다. 파일을 저장한 뒤 HTML을 브라우저에서 열고 창 너비를 바꾸세요.
page-width는 바깥 너비를 100%로 사용하되 70rem에서 멈추고 좌우 자동 margin으로 가운데에 놓입니다. 전역 border-box 덕분에 좌우 패딩도 이 너비 안에 들어갑니다. course-card 안에서는 column 배치를 사용하고 마지막 링크에 margin-top: auto를 주어 같은 행의 카드에 남는 높이가 있을 때 링크를 아래쪽으로 밀어냅니다. 카드 높이를 고정하지 않아 글자가 늘면 내용에 맞게 커집니다.
넓은 화면에서 flex: 1 1 15rem은 늘어남 1, 줄어듦 1, 기준 크기 15rem을 뜻합니다. 기준 크기는 영구적인 최소 너비가 아닙니다. 세 값의 의미는 MDN flex 속성 문서에서 확인할 수 있습니다. flex-wrap으로 여러 줄을 허용하면, 줄바꿈한 각 줄은 남은 공간을 따로 분배하므로 마지막 줄에 카드가 하나만 있으면 그 카드가 넓어집니다. 모든 행의 열 위치까지 일치시켜야 할 때는 이후 Grid 실습과 비교하면 됩니다.
4. 미디어 쿼리 경계와 넘치는 콘텐츠 다루기
@media (min-width: 48rem)은 viewport 너비가 기준 이상일 때만 안의 규칙을 적용합니다. 카드 자체의 부모 너비를 묻는 조건은 아닙니다. W3C 미디어 쿼리의 width 정의에서 기준을 확인할 수 있습니다. 기본 글꼴 설정이 16px이면 48rem은 768 CSS px에 해당하지만, 미디어 쿼리의 상대 단위는 초기 글꼴 크기를 기준으로 하므로 사용자 설정에 따라 달라질 수 있습니다.
48rem은 특정 제품 이름에 맞춘 숫자가 아니라 이 연습의 헤더와 카드가 가로 배치를 시작할 기준으로 정한 값입니다. 실제 페이지에서는 긴 메뉴와 문장을 넣고 좁혀 보면서 내용이 불편해지는 위치를 찾으세요. 브라우저의 물리 화면 해상도와 CSS viewport 너비도 구분합니다. 모바일을 위한 viewport 메타 태그는 HTML의 head에 이미 포함되어 있습니다.
긴 식별자가 있는 카드는 min-width: 0으로 Flex 아이템의 축소를 허용하고, overflow-wrap: anywhere로 공백이 없는 문자열도 줄바꿈할 수 있게 했습니다. 줄바꿈 속성의 의미는 W3C CSS Text의 overflow-wrap 정의에 따릅니다. 한 속성이 모든 가로 넘침의 원인을 해결하는 것은 아닙니다. 이미지 고정 너비, 자식의 min-width, 패딩까지 어떤 박스가 넘치는지 확인하세요. 넘친 내용을 overflow-x: hidden으로 가려 문제를 감추지 않습니다.
좁은 화면에서 핵심 메뉴를 숨기거나 CSS의 order로 순서를 뒤집지 않았습니다. 보이는 순서와 문서·키보드 순서가 달라지면 다음 조작을 예상하기 어렵습니다. 예제처럼 메뉴를 자연스럽게 줄바꿈하고 문서 순서대로 배치하는 방법부터 익히세요.
5. 너비와 키보드를 바꾸며 결과 확인하기
| 시험 조건 | 기대 결과 |
|---|---|
| 320px 또는 375px viewport | 헤더와 카드 목록은 세로 배치입니다. 페이지 전체에 가로 스크롤 없이 내용을 읽을 수 있어야 합니다. |
| 767px와 768px 부근, 기본 글꼴 16px | 미디어 쿼리 경계에서 헤더가 가로 배치로 전환됩니다. 카드 세 개가 들어갈 공간이 부족하면 줄바꿈합니다. |
| 1024px viewport, 기본 글꼴 16px | 카드 세 개가 같은 행에 들어갑니다. 같은 행의 링크는 카드 아래쪽에 배치됩니다. |
| 페이지 확대 200% | 사용 가능한 CSS viewport가 달라질 수 있습니다. 배치 전환과 관계없이 모든 글과 링크를 읽고 조작할 수 있어야 합니다. |
| 처음부터 Tab과 Enter 사용 | 본문 바로가기, 메뉴, HTML·CSS·Flex 과제 링크를 문서 순서대로 조작할 수 있어야 합니다. |
개발자 도구에서 display: flex를 잠시 해제해 어느 관계가 변하는지 보세요. 헤더 규칙을 끄면 헤더 정렬이, 카드 목록 규칙을 끄면 카드 사이 배치가 바뀝니다. 기본 글꼴 16px과 768px viewport에서는 좌우 패딩을 뺀 736px에 카드 기준 너비 240px 세 개와 gap 32px가 모두 들어가지 않아 두 줄이 됩니다. 마지막 줄이 늘어나는 것은 Flex의 정상적인 결과입니다.
완성 코드: 두 파일을 같은 폴더에 저장하기
아래 두 블록은 complete 폴더의 파일 전체입니다. 먼저 starter에서 과제를 수행하고, 동작이나 계산이 다를 때 해당 선언을 비교하세요. HTML 안에 CSS를 붙여 넣지 않고 styles.css를 연결합니다.
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>반응형 학습 카드 | Flex 실습</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<a class="skip-link" href="#main">본문으로 바로 가기</a>
<header class="site-header page-width">
<p class="brand">퍼블리싱 연습실</p>
<nav aria-label="페이지 안내">
<ul class="nav-list">
<li><a href="#courses">학습 카드</a></li>
<li><a href="#practice">확인 과제</a></li>
</ul>
</nav>
</header>
<main id="main" class="page-width" tabindex="-1">
<h1>화면 크기에 맞춰 읽는 학습 카드</h1>
<section id="courses" aria-labelledby="courses-title">
<h2 id="courses-title">차례대로 연습하기</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 id="practice" aria-labelledby="practice-title">
<h2 id="practice-title">확인 과제</h2>
<ul>
<li id="html-task">HTML: Tab 순서와 문서 순서가 자연스러운지 확인합니다.</li>
<li id="css-task">CSS: 박스가 부모의 너비를 넘는지 확인합니다.</li>
<li id="flex-task">
Flex: 좁은 화면과 넓은 화면에서 카드 줄바꿈을 확인합니다.
</li>
</ul>
</section>
</main>
<footer class="page-width">
<p>브라우저 너비를 바꾸며 확인하는 학습용 페이지입니다.</p>
</footer>
</body>
</html>
styles.css
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.6;
color: #222222;
background: #f5f5f5;
}
a {
color: #444444;
}
a:focus-visible {
outline: 3px solid #444444;
outline-offset: 4px;
}
.page-width {
width: 100%;
max-width: 70rem;
margin-inline: auto;
padding-inline: 1rem;
}
.skip-link {
display: inline-block;
margin: 0.5rem 1rem;
}
.site-header {
display: flex;
flex-direction: column;
gap: 1rem;
padding-block: 1rem;
}
.brand {
margin: 0;
font-weight: 700;
}
.nav-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1rem;
padding: 0;
margin: 0;
list-style: none;
}
.nav-list a {
display: inline-block;
padding: 0.5rem;
}
h1 {
font-size: 1.75rem;
line-height: 1.3;
}
h2 {
font-size: 1.4rem;
}
h3 {
font-size: 1.15rem;
margin: 0;
}
section {
margin-block: 2rem;
}
.course-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.course-card {
display: flex;
flex-direction: column;
min-width: 0;
padding: 1rem;
border: 1px solid #999999;
background: #ffffff;
overflow-wrap: anywhere;
}
.card-link {
margin-top: auto;
padding-top: 1rem;
}
수정 방법
index.html
이 파일은 변경하지 않습니다. 다른 파일을 수정하는 동안 기존 내용을 유지하세요.
styles.css
styles.css에서 아래 차이가 있는 위치를 수정하세요. -는 삭제할 줄, +는 추가할 줄이며 표시는 실제 코드에 넣지 않습니다.
작은 화면에서 한 열로 시작하고 넓은 화면에서 카드 배치를 확장합니다.
--- 수정 전/styles.css
+++ 수정 후/styles.css
@@ -87,3 +87,18 @@
margin-top: auto;
padding-top: 1rem;
}
+
+@media (min-width: 48rem) {
+ .site-header {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ }
+ .course-list {
+ flex-direction: row;
+ flex-wrap: wrap;
+ }
+ .course-card {
+ flex: 1 1 15rem;
+ }
+}
수정 후 탭의 전체 파일과 비교한 뒤 본문의 확인 절차를 실행하세요.
수정 후
index.html
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>반응형 학습 카드 | Flex 실습</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<a class="skip-link" href="#main">본문으로 바로 가기</a>
<header class="site-header page-width">
<p class="brand">퍼블리싱 연습실</p>
<nav aria-label="페이지 안내">
<ul class="nav-list">
<li><a href="#courses">학습 카드</a></li>
<li><a href="#practice">확인 과제</a></li>
</ul>
</nav>
</header>
<main id="main" class="page-width" tabindex="-1">
<h1>화면 크기에 맞춰 읽는 학습 카드</h1>
<section id="courses" aria-labelledby="courses-title">
<h2 id="courses-title">차례대로 연습하기</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 id="practice" aria-labelledby="practice-title">
<h2 id="practice-title">확인 과제</h2>
<ul>
<li id="html-task">HTML: Tab 순서와 문서 순서가 자연스러운지 확인합니다.</li>
<li id="css-task">CSS: 박스가 부모의 너비를 넘는지 확인합니다.</li>
<li id="flex-task">
Flex: 좁은 화면과 넓은 화면에서 카드 줄바꿈을 확인합니다.
</li>
</ul>
</section>
</main>
<footer class="page-width">
<p>브라우저 너비를 바꾸며 확인하는 학습용 페이지입니다.</p>
</footer>
</body>
</html>
styles.css
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.6;
color: #222222;
background: #f5f5f5;
}
a {
color: #444444;
}
a:focus-visible {
outline: 3px solid #444444;
outline-offset: 4px;
}
.page-width {
width: 100%;
max-width: 70rem;
margin-inline: auto;
padding-inline: 1rem;
}
.skip-link {
display: inline-block;
margin: 0.5rem 1rem;
}
.site-header {
display: flex;
flex-direction: column;
gap: 1rem;
padding-block: 1rem;
}
.brand {
margin: 0;
font-weight: 700;
}
.nav-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1rem;
padding: 0;
margin: 0;
list-style: none;
}
.nav-list a {
display: inline-block;
padding: 0.5rem;
}
h1 {
font-size: 1.75rem;
line-height: 1.3;
}
h2 {
font-size: 1.4rem;
}
h3 {
font-size: 1.15rem;
margin: 0;
}
section {
margin-block: 2rem;
}
.course-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.course-card {
display: flex;
flex-direction: column;
min-width: 0;
padding: 1rem;
border: 1px solid #999999;
background: #ffffff;
overflow-wrap: anywhere;
}
.card-link {
margin-top: auto;
padding-top: 1rem;
}
@media (min-width: 48rem) {
.site-header {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.course-list {
flex-direction: row;
flex-wrap: wrap;
}
.course-card {
flex: 1 1 15rem;
}
}
styles.css
starter에서 직접 바꿀 내용
- 48rem 이상에서 헤더를 row 방향으로 놓고 양끝 배치와 교차축 가운데 정렬을 적용하세요.
- 같은 미디어 쿼리에서 course-list를 row와 wrap으로 바꾸세요.
- course-card에 flex: 1 1 15rem을 적용하고 768px와 1024px 결과가 다른 이유를 계산하세요.
완성 파일과 비교하기 전에 위 확인 절차를 직접 수행하세요. 한 번에 한 속성 또는 한 구조만 바꾸고 화면이 달라지는 이유를 적으면 다음 문제에서도 원인을 찾기 쉽습니다.
이 글이 도움이 되었나요?
웹 퍼블리싱 학습 순서
필수 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
이 글과 함께 공부하기
무엇을 도와드릴까요?
읽다가 막힌 부분을 물어보세요. 이 글을 바탕으로 설명하고, 이어서 읽을 본문을 연결합니다.