LabHub
배우기 러닝패스 코스

CSS — 나중에 쓴 규칙이 이긴다 · 가운데가 안 맞고 여백이 사라졌다 · 이론

300px 이 300px 이 아닌 이유

LabHub 에서 이어서 보기

한 줄 요약

width: 300px 은 기본 설정에서 내용 영역의 폭이지 상자의 폭이 아니다. 그리고 위아래 여백은 더해지는 대신 합쳐진다. 이 둘이 레이아웃이 어긋나는 사고의 대부분이다.

왜 이게 필요했나

"칸 두 개를 50% 씩 놓았는데 줄이 바뀐다." "패딩을 넣었더니 카드가 격자 밖으로 삐져나온다." "위 요소에 margin-bottom: 20px, 아래 요소에 margin-top: 30px 을 줬는데 간격이 50px 이 아니라 30px 이다."

셋 다 CSS 를 몰라서가 아니라 모형을 모르고 값을 넣어서 나는 일이다. 그리고 셋 다 눈으로는 "왜 이러지" 밖에 안 나온다. 계산 규칙을 알면 그 자리에서 답이 나온다.

어떻게 동작하나 — 상자의 네 겹

┌─────────── margin ───────────┐│ ┌───────── border ─────────┐ ││ │ ┌─────── padding ──────┐ │ ││ │ │      content         │ │ ││ │ └──────────────────────┘ │ ││ └──────────────────────────┘ │└──────────────────────────────┘

[CSS 2 명세의 상자 모형](https://www.w3.org/TR/CSS22/box.html)이 정의한 네 겹이다. 기본값 box-sizing: content-box 에서 width 는 맨 안쪽 content 만 가리킨다. 그래서

.card { width: 300px; padding: 20px; border: 1px solid; }

이 카드가 차지하는 가로는 300 + 20 + 20 + 1 + 1 = 342px 이다. 부모가 300px 이면 삐져나온다.

box-sizing: border-box 를 주면 widthborder 까지 포함한 값이 된다. 위 카드는 정확히 300px 이 되고, 내용 영역이 258px 로 줄어든다. [MDN 의 box-sizing 문서](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing)가 두 모드를 비교한다.

그래서 거의 모든 프로젝트가 맨 위에 이 세 줄을 둔다.

*, *::before, *::after { box-sizing: border-box; }

margin 은 어느 모드에서도 width 에 포함되지 않는다. 이것만은 예외가 없다.

여백 상쇄 — 더해지지 않고 합쳐진다

위아래로 맞닿은 여백은 더해지지 않고 둘 중 큰 값 하나가 된다. [MDN 의 여백 상쇄 문서](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model/Mastering_margin_collapsing)가 세 경우를 정리한다.

1. 이웃한 형제 — 위 요소의 아래 여백과 아래 요소의 위 여백이 합쳐진다
2. 부모와 첫/마지막 자식 — 사이에 경계선도 패딩도 없으면 자식의 여백이 부모 밖으로 새어 나간다
3. 빈 요소 — 자기 위아래 여백이 자기들끼리 합쳐진다

두 번째가 가장 자주 사람을 당황시킨다. 부모 상자 안쪽에 여백을 주려고 자식에 margin-top 을 줬는데, 부모가 통째로 아래로 밀려난다. 배경색이 있으면 그 색이 안 따라와서 더 이상하게 보인다.

상쇄를 멈추는 방법은 사이에 무언가를 두는 것이다. 부모에 padding-top 한 줄, border-top 한 줄, 또는 display: flow-root. 새 서식 문맥(BFC)을 만들면 여백은 안에 갇힌다.

그리고 가로 여백은 절대 상쇄되지 않는다. 좌우는 언제나 더해진다.

flex 와 grid 안에서는 상쇄가 없다

여백 상쇄는 일반 흐름(block 서식 문맥) 의 규칙이다. flex 아이템과 grid 아이템 사이에서는 일어나지 않는다.

그래서 요즘 레이아웃에서 상쇄를 만날 일이 줄었다. 그 대신 gap 이 그 자리를 대신한다. gap아이템 사이에만 넣고 바깥쪽에는 안 넣으므로, "마지막 요소만 여백을 빼는" 그 규칙이 통째로 필요 없어진다.

.list { display: flex; flex-direction: column; gap: 16px; }

이 한 줄이 .list > * + * { margin-top: 16px } 을 대신한다.

값은 어디를 기준으로 재는가

백분율은 담음 블록의 폭을 기준으로 한다. 여기서 함정이 하나 있다 — padding-topmargin-top 의 백분율도 폭을 기준으로 잰다. 높이가 아니다. 가로세로 비를 유지하는 상자를 만들 때 padding-top: 56.25% 를 쓰던 오래된 기법이 여기서 나왔다(지금은 aspect-ratio 가 있다).

그리고 min-widthmax-widthwidth 보다 나중에 적용된다. width: 100%max-width: 640px 을 함께 쓰면 640px 을 넘지 않는 유동 상자가 된다. [CSS Values](https://www.w3.org/TR/css-values-4/)가 단위와 계산 순서를 정의한다.

현장에서 만나는 모습

이 서비스에서 실제로 났던 것 둘을 적어 둔다.

첫째, 본문만 화면 밖으로 삐져나간 일. body 를 flex column 으로 잡아 두고 본문에 margin: 0 auto 를 줬는데, flex 자식에 자동 여백이 붙으면 늘어남(stretch)이 꺼진다. 넓은 코드 블록이 하나 들어오자 본문만 그 폭으로 벌어지고 상단 바와 어긋났다. width: 100% 를 명시해서 고쳤다.

둘째, 카드 격자가 한 줄에 두 개만 들어간 일. width: 33.33%padding: 16px 을 줬는데 box-sizing 이 기본값이었다. 세 개의 합이 100% 를 넘어 줄이 바뀌었다. border-box 한 줄로 끝났다.

둘 다 계산 규칙을 모르면 "CSS 가 이상하다" 로 끝나고, 알면 개발자 도구에서 계산된 값 한 번 보면 끝난다.

다음 퀴즈에서 확인할 것

box-sizing 두 모드에서 상자가 실제로 차지하는 폭, 여백 상쇄가 일어나는 세 경우와 멈추는 방법, 그리고 백분율이 무엇을 기준으로 재는지를 확인합니다.