flex로 자주 만드는 모양은 셋입니다 — 양 끝 줄, 한가운데, 입력 칸과 단추 줄. 셋 모두 같은 세 걸음(1. 부모 정하기 → 2. 방향과 간격 → 3. 남는 자리와 정렬)으로 만들고, 걸음마다 부모에 쓸 선언과 자식에 쓸 선언이 정해져 있습니다.
justify-content · align-items · gap · flex: 1을 하나씩 알아도, 화면 앞에서는 무엇을 어느 요소에 먼저 써야 할지 막히곤 합니다. 선언을 외운 묶음으로 붙이면 모양이 조금만 달라져도 깨집니다. 차례가 있으면 처음 보는 모양도 같은 물음 셋으로 풀립니다.
이 레슨에서는 세 걸음의 물음, 그 걸음으로 만드는 세 모양, 한가운데가 안 될 때 볼 곳을 봅니다.
그림 준비 중
1세 걸음 — 부모 정하기 → 방향과 간격 → 남는 자리와 정렬
| 걸음 | 묻는 것 | 쓰는 선언 |
|---|---|---|
| 1. 부모 정하기 | 나란히 놓을 요소들을 바로 담은 요소는? | 그 부모에 display: flex |
| 2. 방향과 간격 | 가로인가 세로인가, 사이는 얼마나? | 부모에 flex-direction · gap |
| 3. 남는 자리와 정렬 | 남는 자리는 어디로, 교차축은 어디에? | 부모에 justify-content · align-items, 늘어날 자식에 flex: 1 |
처음 값으로 되는 걸음은 비워 둡니다 — 가로로 놓을 때 flex-direction: row는 적지 않습니다. 세 모양에서 각 걸음이 쓰는 선언은 이렇습니다.
| 모양 | 부모 | 자식 |
|---|---|---|
| 양 끝 줄 | space-between · 세로 center | 그대로 |
| 한가운데 | 두 축 center · 높이 | 그대로 |
| 입력 칸과 단추 | gap | 입력 칸 flex: 1 |
양 끝 줄은 머리글 · 카드 제목 줄 · 할 일 화면 아랫줄에, 한가운데는 그림 칸의 로고처럼 높이를 정한 칸에, 입력 칸과 단추는 할 일 폼 · 찾기 줄에 씁니다.
2양 끝 줄 — 머리글
사이트 이름은 왼쪽 끝, 내비게이션은 오른쪽 끝, 둘의 세로 가운데를 맞춥니다. 머리글 끝이 창 폭에 따라 달라지니 폭 360px 창으로 그립니다.
<header class="site-header">
<a href="index.html">
레시피 노트
</a>
<nav>
<a href="kimchi.html">
김치볶음밥
</a>
</nav>
</header>
/* 1. 부모 정하기 */
.site-header {
display: flex;
/* 3. 남는 자리와 정렬 */
justify-content:
space-between;
align-items: center;
}360px.site-header > a의 왼쪽 자리8pxnav의 오른쪽 끝352px.site-header의align-itemscenter
1걸음은 사이트 이름과 nav를 바로 담은 header, 2걸음은 처음 값 row 그대로라 비웠고, 3걸음에서 남는 자리를 둘 사이로 보내고 교차축(세로)을 가운데에 맞췄습니다. 사이트 이름 글자를 크게 키워도 두 항목의 세로 가운데가 맞습니다.
3한가운데 — 그림 칸의 로고
카드의 그림 자리(200 × 150)에 아직 그림이 없을 때, 로고를 한가운데에 둡니다.
<div class="thumb">
<img
src="images/logo.svg"
alt="레시피 노트"
width="48"
height="48"
/>
</div>
/* 1. 부모 정하기 */
.thumb {
display: flex;
/* 3. 남는 자리와 정렬 */
justify-content: center;
align-items: center;
width: 200px;
height: 150px;
}img의 왼쪽 자리84pximg의 위 자리59px
로고가 가로로 (200 − 48) ÷ 2 = 76px, 세로로 (150 − 48) ÷ 2 = 51px 들어가, 칸이 시작하는 8px에서 그만큼 떨어진 자리에 놓였습니다. 주축(가로)은 justify-content, 교차축(세로)은 align-items — 두 축을 모두 가운데로 해야 한가운데입니다. 세로로 옮길 자리는 부모의 높이 150px이 만듭니다.
4입력 칸과 단추 줄 — 할 일 폼
라벨은 줄 밖에 두고, 입력 칸과 단추만 한 줄에 놓습니다. 남는 폭은 입력 칸이 받습니다.
<form>
<label for="task">할 일</label>
<div class="input-row">
<input
id="task"
name="task"
/>
<button type="submit">
추가
</button>
</div>
</form>
/* 1. 부모 정하기 */
.input-row {
display: flex;
/* 2. 방향과 간격 */
gap: 8px;
}
/* 3. 남는 자리와 정렬 */
.input-row input {
flex: 1;
}360pxbutton의 오른쪽 끝352px.input-row input의flex-grow1
1 · 2걸음은 부모 .input-row에, 3걸음의 flex: 1은 늘어날 자식인 입력 칸에 썼습니다. 단추가 줄 끝(352px)에 닿았고, 단추 글자가 길어지면 입력 칸이 그만큼 줄어듭니다. 할 일 화면의 폼 줄도 이 모양입니다.
예제
카드 제목 줄입니다. 양 끝 줄에 간격과 '줄지 않는 배지'까지 더해, 이름이 길어져도 배지가 오른쪽 끝을 지키게 합니다.
<div class="recipe-card-head">
<h2>김치볶음밥</h2>
<span class="badge">한식</span>
</div>
/* 1. 부모 정하기 */
.recipe-card-head {
display: flex;
/* 2. 방향과 간격 */
gap: 12px;
/* 3. 남는 자리와 정렬 */
justify-content:
space-between;
align-items: center;
width: 140px;
}
h2 {
margin: 0;
}배지에는 자식이 정하는 제 몫 하나(줄지 않기)를 씁니다.
.badge {
flex-shrink: 0;
}.badge의 오른쪽 끝148px.badge의flex-shrink0
배지는 묶음 끝(8 + 140 = 148px)에 붙었습니다. 묶음이 좁아 자리가 모자라면 이름이 꺾여 두 줄이 되고, 배지는 flex-shrink: 0이라 제 너비로 한 줄을 지킵니다. 세 걸음을 차례로 지나면, 묶음에 쓸 선언 넷과 배지에 쓸 선언 하나가 저마다 제자리에 붙습니다.
자주 틀리는 곳
틀린 생각: 두 축을 center로 하면 언제나 한가운데에 온다.
3절 그림 칸에서 높이를 빼 봅니다.
<div class="thumb">
<img
src="images/logo.svg"
alt="레시피 노트"
width="48"
height="48"
/>
</div>
.thumb {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
}.thumb의 높이48pximg의 위 자리8px
칸 높이가 로고 높이(48px)만큼으로 줄어, 로고는 가로로만 가운데이고 세로는 위 끝(8px)입니다. 세로로 남는 자리가 없습니다.
맞는 규칙: 정렬은 남는 자리를 옮길 뿐입니다. 한가운데에는 부모에 내용보다 큰 높이가 있어야 합니다(브라우저 규칙 6 — 블록 상자의 높이는 내용만큼).
틀린 생각: 부모 선언도 나란히 놓을 자식에게 쓰면 된다.
양 끝으로 보낼 것은 사이트 이름과 nav이니, 그 둘에 선언을 줘 봅니다.
<header class="site-header">
<a href="index.html">
레시피 노트
</a>
<nav>
<a href="kimchi.html">
김치볶음밥
</a>
</nav>
</header>
.site-header > a,
nav {
display: flex;
justify-content:
space-between;
}.site-header의 줄 수2
머리글은 그대로 두 줄입니다. 사이트 이름 a와 nav는 저마다 제 안을 놓는 부모가 되었을 뿐, 둘을 나란히 놓을 부모 header에는 아무 선언도 없습니다.
맞는 규칙: 1걸음에서 고른 부모에 1 · 2 · 3걸음의 부모 선언을 씁니다. 자식에게 쓰는 것은 제 몫(flex: 1 · flex-shrink)뿐입니다(브라우저 규칙 7).
틀린 생각: 세 모양의 선언 묶음을 외워 두면 어디에나 붙일 수 있다.
옆 메뉴의 두 링크를 위 끝과 아래 끝에 두려고, 머리글의 양 끝 줄 묶음을 그대로 붙였습니다.
<nav class="side">
<a href="index.html">
레시피 목록
</a>
<a href="kimchi.html">
김치볶음밥
</a>
</nav>
.side {
display: flex;
justify-content:
space-between;
height: 120px;
}.side의 줄 수1
두 링크가 위아래가 아니라 가로 양 끝으로 갔습니다. 묶음에는 2걸음(방향)이 빠져 있었습니다 — 머리글은 가로라 비워 둔 걸음입니다.
맞는 규칙: 모양마다 세 걸음을 다시 묻습니다. 위아래 양 끝이면 2걸음에서 flex-direction: column을 쓰고, 3걸음의 space-between이 세로로 나눕니다.
틀린 생각: flex 안에서도 위아래 바깥 여백은 겹쳐서 큰 쪽 하나만 남는다.
세로로 세운 두 칸 사이를 바깥 여백 12px씩으로 띄웠습니다.
<div class="col">
<div class="a">이름 칸</div>
<div class="b">별점 칸</div>
</div>
.col {
display: flex;
flex-direction: column;
}
.a {
margin-bottom: 12px;
}
.b {
margin-top: 12px;
}.b의 위 틈24px
틈이 12px이 아니라 24px입니다. 일반 흐름에서는 위아래 바깥 여백이 겹쳐 큰 쪽 하나만 남지만, flex 항목의 바깥 여백은 겹치지 않고 더해집니다.
맞는 규칙: 위아래 바깥 여백이 겹치는 것은 일반 흐름의 일입니다. flex 안의 간격은 바깥 여백이 아니라 2걸음의 gap으로 줍니다 — gap: 12px이면 틈이 꼭 12px입니다.
확인 문제
1. 3절 그림 칸에서 justify-content: center를 flex-end로 바꿨습니다. 로고의 왼쪽 자리와 위 자리는 어떻게 될까요?
<div class="thumb">
<img
src="images/logo.svg"
alt="레시피 노트"
width="48"
height="48"
/>
</div>
.thumb {
display: flex;
justify-content: flex-end;
align-items: center;
width: 200px;
height: 150px;
}
- ① 왼쪽 자리 160px · 위 자리 59px
- ② 왼쪽 자리 84px · 위 자리 110px
- ③ 왼쪽 자리 160px · 위 자리 110px
힌트
바꾼 속성이 맡는 축이 가로인지 세로인지 먼저 정하고, 그 축의 자리만 다시 셉니다.
정답
① 왼쪽 자리 160px · 위 자리 59px
img의 왼쪽 자리160pximg의 위 자리59px
justify-content는 주축(가로)을 맡습니다.flex-end면 로고가 칸 오른쪽 끝에 붙어 8 + 200 − 48 = 160px에서 시작합니다.- 교차축(세로)은
align-items: center그대로라 위 자리는 59px입니다. - ②는
justify-content를 세로로 본 것이고, ③은 바꾼 값이 두 축 모두에 닿는다고 본 것입니다.
2. 할 일 화면의 아랫줄입니다. 남은 개수는 왼쪽 끝, 지우기 단추는 오른쪽 끝에 두고 세로 가운데를 맞추려 합니다. 세 걸음 가운데 3걸음을 통째로 쓰세요.
<footer class="footer">
<p id="count" class="count">
전체 3개 중 2개 남음
</p>
<button
type="button"
id="clear-done"
class="clear"
>
완료한 일 지우기
</button>
</footer>
/* 1. 부모 정하기 */
.footer {
display: flex;
/* 3. 남는 자리와 정렬 */
____
}
힌트
양 끝 줄 모양에서 부모가 3걸음에 쓰는 선언 둘을 1절 표에서 찾습니다.
정답
/* 1. 부모 정하기 */
.footer {
display: flex;
/* 3. 남는 자리와 정렬 */
justify-content:
space-between;
align-items: center;
}남는 자리를 개수와 단추 사이로 보내는 justify-content: space-between, 높이가 다른 두 항목의 세로 가운데를 맞추는 align-items: center 둘입니다. 2걸음은 처음 값 row라 비웠습니다.
3. 여행 일정 칸에서 날짜와 장소를 한가운데에 놓으려고 이렇게 썼습니다. 결과 화면에서 '부산'은 어디에 놓일까요?
<div class="trip">
<div class="place">부산</div>
</div>
.trip {
display: flex;
height: 120px;
}
.place {
justify-content: center;
align-items: center;
width: 80px;
height: 40px;
}
- ① 칸의 한가운데
- ② 칸의 왼쪽 위 끝
- ③ 칸의 가로 가운데, 위 끝
힌트
두 정렬 선언을 받은 요소가 무엇이고, 그 요소에게 놓을 항목이 있는지 봅니다.
정답
② 칸의 왼쪽 위 끝
.place의 왼쪽 자리8px.place의 위 자리8px
- 정렬 선언 둘을 자식
.place에 썼습니다..place는 flex를 받은 부모가 아니라 놓을 항목이 없어, 두 선언이 할 일이 없습니다..trip은 처음 값대로 '부산'을 시작 쪽 위 끝에 둡니다(브라우저 규칙 7). - 고치려면 1걸음에서 고른 부모
.trip에 두 선언을 옮깁니다. - ①은 선언을 받은 요소를 따지지 않은 것이고, ③은 한 선언만 먹었다고 본 것입니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
hello.html의 body 안을 3절 그림 칸 코드로, style 요소 안을 3절 CSS로 바꿔 저장 · 새로고침합니다. height: 150px 줄을 지우고 다시 저장 · 새로고침하면, 로고가 칸의 위 끝으로 올라가 가로로만 가운데에 남습니다. 지운 높이를 되살리면 한가운데로 돌아옵니다. 그림 logo.svg가 없으면 아래에서 받아 images 폴더에 둡니다.
파일을 만들고 여는 법은 준비: VS Code와 작업 폴더에 있습니다.