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-blockbaseline 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
│ └────────────────────────┘ │
└────────────────────────────┘

규칙:

  1. 부모 너비 전체를 차지 (width: auto)
  2. 형제와 세로로 쌓임
  3. 인접 형제의 vertical margin은 collapse된다 — 둘 중 큰 값만 남는다.
  4. 부모-첫자식, 부모-마지막자식 사이도 조건이 맞으면 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의 비밀.