항목 사이를 띄우는 간격은 부모의 gap으로 줍니다 — 항목 사이에만 들고 마지막 항목 뒤에는 생기지 않습니다. 남는 자리를 한 항목에 몰아주려면 늘어날 자식에 flex: 1을 줍니다.
내비게이션 링크마다 오른쪽 바깥 여백을 주면 링크 사이는 띄워지지만, 마지막 링크 뒤에도 여백이 남아 줄 끝이 들쭉날쭉해집니다. 입력 칸과 추가 단추를 한 줄에 놓을 때는 단추는 제 크기로 두고 입력 칸만 남는 폭까지 늘리고 싶습니다. 둘 다 부모와 자식이 맡을 일을 나눠 주면 풀립니다.
이 레슨에서는 간격을 맡는 gap, 남는 자리를 받는 flex: 1, 그리고 줄어드는 몫을 정하는 flex-shrink를 봅니다.
그림 준비 중
1사이는 부모의 gap — 마지막 뒤에는 없다
항목 둘을 오른쪽 끝에 모으고(flex-end), 항목마다 margin-right: 16px로 띄워 봅니다.
<div class="menu">
<span>가</span>
<span>나</span>
</div>
.menu {
display: flex;
justify-content: flex-end;
width: 200px;
}
.menu span {
margin-right: 16px;
}.menu span:last-child의 오른쪽 끝192px
상자는 208px에서 끝나는데 '나'는 192px에서 끝납니다. 마지막 항목 뒤에도 바깥 여백 16px이 붙어, 끝에 붙여도 그만큼 떨어졌습니다. 같은 간격을 부모의 gap으로 줍니다.
<div class="menu">
<span>가</span>
<span>나</span>
</div>
.menu {
display: flex;
justify-content: flex-end;
gap: 16px;
width: 200px;
}.menu span:last-child의 오른쪽 끝208px
gap은 이웃한 항목 사이에만 들어, '나'가 상자 끝(208px)에 붙었습니다.
| 쓰는 것 | 주는 요소 | 만드는 자리 |
|---|---|---|
padding | 부모 | 부모 테두리와 내용 사이 |
margin | 항목 | 그 항목의 바깥 |
gap | flex를 준 부모 | 이웃한 항목 사이 |
margin이 틀린 속성은 아닙니다. 항목 하나만 떼어 둘 때는 여전히 바깥 여백을 씁니다. 모든 항목 사이를 고르게 띄울 때 gap이 맞습니다.
2남는 자리는 늘어날 자식의 flex: 1
너비 240px 상자에 '가'와 너비 60px인 '나'를 놓고, '가'에만 flex: 1을 줍니다.
<div class="row">
<div class="a">가</div>
<div class="b">나</div>
</div>
.row {
display: flex;
width: 240px;
}
.a {
flex: 1;
}
.b {
width: 60px;
}.a의 너비180px.a의flex-grow1.a의flex-shrink1.a의flex-basis0%
'가'가 '나'를 뺀 남는 자리를 모두 받아 180px이 되었습니다. flex: 1은 짧게 적기 한 줄이라 풀어 적은 속성 셋에 한꺼번에 값을 냅니다(브라우저 규칙 4) — 여백 짧게 적기의 margin과 같은 꼴입니다.
| 풀어 적은 속성 | flex: 1이 낸 값 | 뜻 |
|---|---|---|
flex-grow | 1 | 남는 자리를 받는 몫 |
flex-shrink | 1 | 자리가 모자랄 때 줄어드는 몫 |
flex-basis | 0% | 나누기 전에 잡는 크기 |
나누기 전 크기가 0이라, 두 항목에 모두 flex: 1을 주면 남는 자리를 같은 몫으로 나눠 너비가 같아집니다. 늘어나는 것은 항목 자신의 몫이라 부모가 아니라 늘어날 자식에 씁니다(브라우저 규칙 7 — 자식은 제 몫만 정합니다).
예제
입력 칸과 추가 단추를 한 줄에 놓습니다. 부모는 줄과 간격을, 입력 칸은 늘어날 몫을 맡습니다.
<form class="add">
<input
type="text"
aria-label="할 일"
/>
<button type="submit">
추가
</button>
</form>
.add {
display: flex;
gap: 8px;
width: 280px;
}
input {
flex: 1;
}button의 오른쪽 끝288pxinput의flex-grow1
단추가 줄 끝(8 + 280 = 288px)에 닿았습니다. 입력 칸이 단추와 간격 8px을 뺀 자리를 모두 가져갔기 때문입니다. 단추 글자가 길어지면 단추가 넓어지고, 입력 칸이 그만큼 줄어듭니다 — 입력 칸 너비를 셈해 적을 필요가 없습니다.
할 일 화면의 폼 줄도 같은 꼴입니다 — #todo-form에 display: flex와 gap: 8px, 입력 칸 #todo-input에 flex: 1을 줍니다.
자주 틀리는 곳
틀린 생각: flex: 1을 준 칸은 긴 글자가 들어도 남는 자리 안에서 줄어든다.
공유 주소 칸과 복사 단추를 너비 160px 줄에 놓았습니다. 주소는 빈칸 없는 긴 글자 하나입니다.
<div class="share">
<p>
example.com/recipes/kimchi
</p>
<button type="button">
복사
</button>
</div>
.share {
display: flex;
width: 160px;
}p의min-widthauto
주소가 줄을 밀어내, 복사 단추가 상자 밖으로 튀어나갔습니다. flex 항목의 최소 너비(min-width)는 처음 값 auto라, 꺾을 수 없는 가장 긴 글자보다 작아지지 않습니다.
<div class="share">
<p>
example.com/recipes/kimchi
</p>
<button type="button">
복사
</button>
</div>
.share {
display: flex;
width: 160px;
}
p {
min-width: 0;
overflow-wrap: anywhere;
}p의min-width0pxbutton의 오른쪽 끝168px
맞는 규칙: 긴 글자가 드는 항목에는 min-width: 0으로 줄어들 수 있게 하고, overflow-wrap: anywhere로 아무 데서나 꺾이게 합니다. 이제 단추가 상자 끝(168px)에 머뭅니다. 할 일 화면의 글 .text도 같은 까닭으로 overflow-wrap: anywhere를 씁니다.
틀린 생각: 제목 옆 배지는 항목이니 늘 제 크기를 지킨다.
너비 100px 묶음에 긴 이름과 배지를 놓습니다.
<div class="recipe-card-head">
<h2>김치볶음밥</h2>
<span class="badge">한식</span>
</div>
.recipe-card-head {
display: flex;
width: 100px;
}
h2 {
margin: 0;
}.badge의flex-shrink1
자리가 모자라자 이름과 배지가 함께 줄어, 배지의 '한식'이 '한' / '식' 두 줄로 꺾였습니다. flex-shrink의 처음 값 1은 모자란 자리를 항목들이 나눠 줄어든다는 뜻입니다.
맞는 규칙: 줄어들면 안 되는 항목에는 flex-shrink: 0을 줍니다. 배지에 주면 배지는 제 너비를 지키고, 이름이 모자란 몫을 받아 꺾입니다.
틀린 생각: gap과 flex: 1은 어느 요소에 줘도 된다.
<form class="add">
<input
type="text"
aria-label="할 일"
/>
<button type="submit">
추가
</button>
</form>
.add {
display: flex;
flex: 1;
width: 280px;
}
input {
gap: 8px;
}input의flex-grow0
입력 칸은 늘어나지 않고, 단추와 사이도 붙어 있습니다. 부모에 준 flex: 1은 부모 자신의 몫(그 부모가 다른 flex의 항목일 때)이고, 입력 칸에 준 gap은 입력 칸 안의 항목 사이를 띄우는 값이라 할 일이 없습니다.
맞는 규칙: gap은 항목을 놓는 부모에, flex: 1은 늘어날 자식에 줍니다(브라우저 규칙 7).
확인 문제
1. 2절 상자에서 '나'에도 flex: 1을 주고 width: 60px 줄을 지웠습니다. '가'의 너비는 얼마가 될까요?
<div class="row">
<div class="a">가</div>
<div class="b">나</div>
</div>
.row {
display: flex;
width: 240px;
}
.a,
.b {
flex: 1;
}
- ① 120px
- ② 180px 그대로
- ③ 240px — '나'를 밀어낸다
힌트
flex: 1이 내는 값 셋 가운데 나누기 전 크기와 받는 몫을 보고, 남는 자리를 두 항목에 나눕니다.
정답
① 120px
.a의 너비120px.b의 너비120px
- 둘 다 나누기 전 크기(
flex-basis)가 0%이고 받는 몫(flex-grow)이 1이라, 240px을 1:1로 나눠 120px씩입니다. - ②는 '가'가 먼저 받은 몫을 지킨다고 본 것입니다. 몫은 늘어날 항목들이 함께 나눕니다.
- ③은
flex: 1이 남는 자리를 혼자 가진다고 본 것입니다. 같은 몫을 받은 항목이 둘이면 둘이 나눕니다.
2. 후기 폼의 이름 칸과 별점 칸을 한 줄에 놓으려 합니다. 주석은 배치를 짜는 세 걸음입니다(셋은 자주 쓰는 세 모양에서 정리합니다). 이름 칸 묶음이 남는 폭을 받게 하는 걸음을 통째로 쓰세요.
<div class="form-row">
<div>이름 칸</div>
<div>별점 칸</div>
</div>
/* 1. 부모 정하기 */
.form-row {
display: flex;
/* 2. 방향과 간격 */
gap: 16px;
}
/* 3. 남는 자리와 정렬 */
____
힌트
남는 폭을 받을 요소는 부모인지 자식인지 먼저 정하고, 그 요소를 고르는 선택자와 선언 하나를 씁니다.
정답
<div class="form-row">
<div>이름 칸</div>
<div>별점 칸</div>
</div>
/* 1. 부모 정하기 */
.form-row {
display: flex;
/* 2. 방향과 간격 */
gap: 16px;
}
/* 3. 남는 자리와 정렬 */
.form-row > div:first-child {
flex: 1;
}.form-row > div:first-child의flex-grow1.form-row의 줄 수1
남는 폭은 늘어날 자식, 곧 첫째 div의 몫이라 그 자식에 flex: 1을 씁니다. 이름 칸 묶음에 이름표를 붙여 그 이름으로 골라도 됩니다. .form-row에 쓰면 부모 자신의 몫이 되어 이름 칸은 늘어나지 않습니다.
3. 플로우 카페 메뉴판의 한 줄입니다. 메뉴 셋 사이를 12px씩 띄우려고 이렇게 썼습니다. 마지막 '녹차'의 오른쪽 끝은 몇 px일까요?
<div class="menu">
<div>커피</div>
<div>라테</div>
<div>녹차</div>
</div>
.menu {
display: flex;
gap: 12px;
}
.menu div {
width: 60px;
}
- ① 212px
- ② 224px
- ③ 188px
힌트
상자는 8px에서 시작합니다. 항목 셋의 너비와, 간격이 몇 곳에 드는지를 차례로 더합니다.
정답
① 212px
.menu div:last-child의 오른쪽 끝212px
- 8 + 60 + 12 + 60 + 12 + 60 = 212px입니다. 간격은 이웃한 항목 사이 두 곳에만 듭니다.
- ②는 마지막 항목 뒤에도 간격이 붙는다고 본 것입니다(224 = 212 + 12). 마지막 뒤에 붙는 것은
margin-right입니다. - ③은 간격을 빼고 너비만 더한 값입니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
hello.html의 body 안을 1절 둘째 코드로, style 요소 안을 그 CSS로 바꿔 저장 · 새로고침합니다. gap: 16px 줄을 지우고, 1절 첫째 코드처럼 .menu span 규칙에 margin-right: 16px을 써서 다시 저장 · 새로고침하면, '나'가 상자 오른쪽 끝에서 16px 떨어집니다. Elements 패널에서 span 줄에 마우스를 올리면 바깥 여백이 따로 칠해져, 마지막 항목 뒤에도 여백이 붙은 것이 보입니다.
파일을 만들고 여는 법은 준비: VS Code와 작업 폴더에 있습니다.