01 — Normal Flow & Block Formatting Context
한 줄 답: Normal flow는 박스를 block 방향으로 쌓고 inline 방향으로 흘리는 기본 배치 알고리즘이며, BFC(Block Formatting Context)를 형성하면 margin collapse · float 침범이 동시에 차단된다.
Why — 왜 normal flow를 이해해야 하나
Flexbox/Grid 시대에도 <body> 자체와 <div> 트리의 기본 배치는 normal flow다. 그 위에서:
- 자식
margin-top이 부모 바깥으로 새는 margin collapse - 부모 높이가 자식 float를 무시해 0으로 무너지는 float 붕괴
- 인라인 요소 사이의 공백 문자가 시각적 간격을 만드는 현상
display: inline-block의 baseline 4px 간격
이 모든 “왜 이러지” 사고는 normal flow의 정의된 동작이다 — 버그가 아니다. 알고 쓰면 도구고, 모르면 폭탄이다.
/* 가장 흔한 사고: 자식 margin이 부모 바깥으로 샌다 */
.parent { background: red; }
.child { margin-top: 50px; background: blue; }
/* → 부모는 자식 margin을 자기 margin으로 흡수한다. 빨강 배경은 50px 아래에서 시작 */How — 어떻게 동작하나
1) 두 가지 흐름 — BFC와 IFC
- BFC:
<div>,<p>같은 블록 박스들이 위→아래로 쌓이는 영역. block axis(세로) 기준. - IFC:
<span>, 텍스트 노드들이 좌→우로 흐르는 영역. inline axis(가로) 기준.
한 요소는 둘 중 하나의 컨텍스트를 형성하거나 참여한다.
2) Block 박스의 기본 배치
┌────────────────────────────┐ ← 부모 (BFC)
│ ┌────────────────────────┐ │
│ │ block 1 │ │ ← margin-bottom 20px
│ └────────────────────────┘ │
│ ↕ 20px │ ← 둘 중 큰 쪽만 적용 (collapse!)
│ ┌────────────────────────┐ │
│ │ block 2 │ │ ← margin-top 30px
│ └────────────────────────┘ │
└────────────────────────────┘규칙:
- 부모 너비 전체를 차지 (
width: auto) - 형제와 세로로 쌓임
- 인접 형제의 vertical margin은 collapse된다 — 둘 중 큰 값만 남는다.
- 부모-첫자식, 부모-마지막자식 사이도 조건이 맞으면 collapse된다.
3) Margin collapse — 왜 일어나며 언제 막히나
CSS2.1 §8.3.1이 정의한 세 가지 collapse 케이스:
| 케이스 | 예 | 결과 |
|---|---|---|
| 인접 형제 | .a{margin-bottom:20px} .b{margin-top:30px} | 30px만 적용 |
| 부모-자식 (top) | 부모에 padding/border 없음 + 첫 자식 margin-top | 자식 margin이 부모 바깥으로 |
| 빈 블록 자체 | 높이 0, padding/border 없는 빈 블록 | 자기 top/bottom margin이 collapse |
막는 방법 (= BFC 형성):
.parent {
/* 다음 중 하나면 BFC가 형성되고 collapse 차단 */
display: flow-root; /* ← 2026 권장 (정확한 의도) */
/* overflow: hidden; → 부수효과: 잘림, sticky 죽음 */
/* overflow: auto; → 부수효과: 스크롤바 가능성 */
/* display: flex/grid/inline-block; → 자식 정렬 규칙도 바뀜 */
/* float: left; → 자기가 흐름에서 빠짐 */
/* position: absolute/fixed; → containing block 변경 */
/* contain: layout; → 격리 (부수효과 적음) */
}2026 BP: 의도가 “BFC만 만들고 싶다”면 무조건
display: flow-root. 다른 방법은 모두 부수효과가 있다.
4) Inline 박스와 IFC
<p>Hello <span>world</span> here</p>- 텍스트 노드와
<span>이 한 줄에 흐른다. - 줄을 넘어가면 line box가 새로 만들어진다.
- 라인 박스의 높이는 그 줄 안의 가장 큰 inline 박스가 결정한다 (
line-height). - baseline에 정렬된다 →
inline-block옆에 텍스트가 있을 때 4px 정도 아래에 정렬되는 이유.
/* inline-block의 baseline 문제 */
.gallery img { display: inline-block; }
/* → 이미지들 사이에 4~6px 공백이 보임. 두 가지 원인:
① HTML의 공백 문자 (개행, 스페이스) = inline의 공백
② baseline 정렬 (descender 공간) */
/* 해결: */
.gallery { font-size: 0; } /* ① 공백 죽이기 */
.gallery img { vertical-align: top; } /* ② baseline 대신 top */
/* 또는 그냥: */
.gallery { display: flex; } /* IFC 떠나기 */What — 구체 사양
BFC를 형성하는 모든 방법 (CSS Display 3 / 2.1 §9.4.1)
| 방법 | 권장도 | 부수효과 |
|---|---|---|
display: flow-root | ★★★ (2026 표준) | 없음 — 의도 그대로 |
display: inline-block | ★ | 너비 auto가 아닌 shrink-to-fit |
display: flex / grid | ★★ | 자식 배치 알고리즘 변경 |
overflow: hidden / auto / scroll | ★★ | 잘림 / sticky 차단 / 스크롤바 |
float: left / right | ☆ (legacy) | 자기가 흐름에서 빠짐 |
position: absolute / fixed | ☆ | 흐름 이탈 |
column-count, column-width ≠ auto | ☆ | 다단 레이아웃 |
contain: layout / paint / strict | ★★ | 렌더링 격리 |
Root 요소 (<html>) | — | 자동으로 BFC |
Margin collapse가 일어나지 않는 경우
다음 중 하나라도 사이에 끼면 collapse가 끊긴다:
- 부모에
padding-top/bottom또는border-top/bottom이 있다 (위·아래 각각). - 부모가 BFC다 (
display: flow-root등). - 자식이 float 되거나 absolutely positioned 다.
- 부모-자식이 서로 다른 BFC에 속한다.
- 인접 형제 사이에 비어있지 않은 inline 컨텐츠가 있다.
display: flow-root 호환성
- Chrome 58 (2017), Firefox 53 (2017), Safari 13 (2019)
- 2026년 기준 모든 브라우저에서 안전.
clearfix해킹은 폐기.
/* 옛날 clearfix */
.clearfix::after { content: ""; display: table; clear: both; }
/* 2026 */
.clearfix { display: flow-root; }What-if — 잘못 이해하면
1) 부모 padding으로 margin collapse를 막으려다 디자인이 깨짐
/* 자식 margin이 새서 부모에 padding: 1px 추가했더니 ... */
.card { padding: 1px; }
/* → 모든 자식의 정렬이 1px씩 밀린다. flow-root가 정답 */2) overflow: hidden으로 BFC를 만들었더니 position: sticky가 죽음
.scroll-container { overflow: hidden; } /* BFC 형성 */
.sticky-header { position: sticky; top: 0; }
/* → sticky의 스크롤 컨테이너 후보가 .scroll-container가 되는데,
그게 hidden이라 스크롤 자체가 안 일어남 → sticky 무효 */→ 의도가 BFC뿐이면 display: flow-root로 바꿔라.
3) Float 컨테이너가 0 높이로 무너짐 (clearfix 이슈)
.parent { /* 자식이 모두 float이면 부모 높이가 0 */ }
.child { float: left; }
/* 해결 (옛날) */
.parent::after { content: ""; display: block; clear: both; }
/* 해결 (2026) */
.parent { display: flow-root; }4) inline-block 사이의 4px 갭에 4시간 날림
→ IFC의 baseline + HTML 공백 문자가 원인. font-size: 0 또는 flex로 도망.
5) Margin collapse를 유용하게 쓰는 사례
/* 섹션 사이 마진을 정의할 때 */
section { margin-block: 2rem; }
/* → 위 섹션의 margin-bottom과 아래 섹션의 margin-top이 collapse
→ 4rem이 아니라 2rem 간격이 됨. 디자이너 의도와 일치 */Collapse는 버그가 아니라 문서 흐름의 의도된 동작이다 — 책의 단락 간격이 문단 위·아래 마진의 합이 아니라 큰 쪽이 되는 것과 같다.
Insight — 흥미로운 이야기
display: flow-root는 왜 2017년에 와서야 추가됐나
Margin collapse를 차단하는 표준 방법은 원래 없었다. 개발자들은 우회로를 써왔다:
- 1999~2010:
overflow: hidden해킹 - 2010~2017:
clearfix(가짜 ::after) - 2017~:
display: flow-root(정답)
CSS Display 3 명세 제안자 fantasai(Elika Etemad)는 “의도를 표현하지 못하는 우회 패턴이 표준이 되어버린 것 자체가 명세 실패” 라고 회고했다. flow-root라는 이름은 “이 박스부터 새 flow가 시작된다” 는 정확한 의도를 표현한다.
Margin collapse는 왜 만들어졌나
CSS는 문서 스타일링에서 출발했다 — 신문, 책 같은 종이 매체의 비유. 단락 사이 간격은 위 문단의 bottom과 아래 문단의 top 중 큰 쪽이 자연스럽다 (합치면 너무 넓음). 이게 기본 동작이 되어야 한다는 직관에서 collapse가 명세에 들어갔다.
문제는 2010년대부터 CSS가 UI 디자인 도구가 되면서 이 직관이 더 이상 자연스럽지 않다는 점이다. Flex/Grid는 collapse를 적용하지 않는다 — 이는 명세 작성자들이 “UI 시대에는 collapse가 부담”이라는 걸 인정한 결과다.
왜 horizontal margin은 collapse하지 않나
CSS2.1 §8.3.1은 세로 방향 margin만 collapse한다고 못박았다. 이유는 글이 좌→우로 흐르기 때문 — 가로 margin이 collapse하면 단어 간격이 무너진다.
logical properties 시대(05-logical-properties)에는 이 규칙이 *“block-axis margin만 collapse”*로 일반화된다 — 세로쓰기 일본어에서는 가로 방향이 block axis가 되어 collapse한다.
요약 + 다이어그램
Normal flow는 block axis에 쌓고 inline axis에 흘리는 CSS의 가장 오래된 알고리즘이다. BFC를 형성하면 margin collapse와 float 침범이 동시에 차단된다 — 2026년 표준은
display: flow-root. Flexbox/Grid도 그 위에 올라타고, flex/grid 컨테이너 자체의 배치는 여전히 normal flow다.
다음 문서:
02-position.md— 흐름을 어떻게 끊고 좌표를 잡는가? containing block의 비밀.