마진 병합(Margin Collapse)이 왜 발생하는지 이해했다면, 다음 단계는 그것을 언제, 어떻게 막을지 아는 것입니다. 마진 병합을 방지하는 방법은 하나가 아니라 여러 가지이며, 각각 작동 원리와 부작용이 다르기 때문에 상황에 맞는 선택이 중요합니다. 잘못된 방법을 선택하면 마진 병합은 막히지만 레이아웃이 깨지거나 콘텐츠가 잘리는 예상치 못한 문제가 생길 수 있습니다. 이 주제를 통해 각 방지 기법이 내부적으로 어떤 원리로 병합을 차단하는지 이해하면, 단순히 외워서 쓰는 것이 아니라 상황을 보고 최선의 방법을 판단하는 능력을 갖출 수 있습니다.
핵심 문제점
- 마진 병합을 막는 방법이 여러 가지이지만, 각각 어떤 원리로 작동하는지 모르면 우연에 의존해 적용하게 됨
overflow: hidden으로 마진 병합을 차단하면 내부 콘텐츠가 잘리는 부작용이 생길 수 있어 주의가 필요함border나padding을 1px 추가하는 방식은 의도가 불명확해 동료 개발자가 실수로 제거할 위험이 있음- 어떤 방법이 BFC(Block Formatting Context)를 생성하는 방식이고, 어떤 방법이 단순히 마진 사이 분리선을 만드는 방식인지 구분하지 못하면 디버깅이 어려워짐
- 컴포넌트 기반 개발에서 마진 병합 방지 로직이 컴포넌트 내부에 숨겨져 있으면 외부에서 동작을 예측하기 어려워 유지보수 비용이 올라감
왜 중요한가?
실무에서 마진 병합으로 인한 레이아웃 버그는 원인을 파악하기 어렵고, 수정 과정에서 또 다른 문제를 만들기 쉽습니다. 마진 병합 방지 기법을 원리 단위로 이해하면 버그를 재현하지 않고도 코드만 보고 원인을 추론할 수 있습니다. 특히 display: flow-root처럼 부작용 없이 BFC만 생성하는 속성을 알고 있으면, overflow: hidden처럼 의도하지 않은 부작용을 동반하는 방식 대신 더 안전한 선택을 할 수 있습니다. 컴포넌트 단위로 개발하는 환경에서는 각 컴포넌트의 마진이 외부로 새어 나가지 않도록 캡슐화하는 것이 디자인 시스템의 일관성을 유지하는 데 핵심이 됩니다. 마진 대신 gap을 사용하는 현대적 접근도 마진 병합을 원천적으로 우회하는 방식으로, 언제 마진이 적합하고 언제 gap이 적합한지 구분하는 판단력은 CSS 레이아웃 전반의 숙련도를 높여 줍니다.
핵심 개념
BFC(Block Formatting Context)란?
입문
마진 병합을 막는 방법을 이해하려면 먼저 BFC라는 개념을 알아야 해요. BFC는 마진 병합이 일어나는 ‘공간의 규칙’을 정하는 핵심 개념이에요!
🏘️ 독립된 동네를 만든다는 게 무슨 말인가요? 마을 전체가 하나의 공간이라면, 이웃집 소음이 내 집에 들려올 수 있어요. 그런데 내 집을 방음이 완벽한 독립 건물로 만들면, 밖에서 무슨 일이 생겨도 내 집 안은 영향을 받지 않아요. BFC는 CSS에서 이런 ‘독립된 공간’을 만드는 것이에요.
📦 BFC가 없으면 무슨 일이 생기나요? 모든 블록 요소들이 같은 공간에 있으면, 인접한 요소들의 마진이 서로 만나서 병합되어 버려요. 마치 이웃들이 담장 없이 붙어 살면 서로의 정원이 겹쳐 버리는 것처럼요. 이때 각 요소의 마진이 예상과 다르게 동작해요.
🛡️ BFC를 만들면 어떻게 달라지나요? 어떤 요소가 BFC를 가지면, 그 요소 안의 마진은 절대 바깥으로 새어 나가지 않아요. 마치 독립된 방 안에서 일어나는 일이 방 밖에 영향을 주지 않는 것처럼요. 이렇게 되면 마진이 예상한 대로 정확히 동작해요.
💡 BFC는 어떻게 만드나요?
CSS에는 BFC를 만드는 여러 가지 방법이 있어요. overflow: hidden, display: flow-root, display: flex 같은 속성을 요소에 주면 그 요소가 BFC를 갖게 돼요. 각 방법마다 부작용이 다르기 때문에, 상황에 맞는 방법을 선택하는 게 중요해요.
중급
BFC(Block Formatting Context, 블록 포매팅 컨텍스트)는 CSS 레이아웃에서 블록 레벨 박스들이 독립적으로 배치되는 격리된 렌더링 영역입니다. 마진 병합은 동일한 BFC 내에서만 발생하기 때문에, BFC를 새로 생성하면 내부 마진이 외부로 전파되는 것을 차단할 수 있습니다.
BFC를 생성하는 조건 다음 CSS 속성 중 하나라도 적용되면 해당 요소는 새로운 BFC를 생성합니다.
overflow:visible이 아닌 모든 값 (hidden,auto,scroll)display:flow-root,inline-block,table-cell,flex,gridfloat:none이 아닌 값position:absolute,fixed
/* BFC를 생성하지 않음 → 자식 마진이 부모 밖으로 전이됨 */
.parent-no-bfc {
background: lightblue;
/* 기본 display: block은 BFC를 생성하지 않음 */
}
/* BFC를 생성함 → 자식 마진이 내부에 격리됨 */
.parent-with-bfc {
background: lightblue;
overflow: hidden; /* BFC 생성 */
}
.child {
margin-top: 30px; /* BFC 없으면 부모 밖으로 전이됨 */
}
BFC의 마진 격리 원칙 BFC는 내부 마진과 외부 마진을 완전히 격리합니다. 즉 BFC를 가진 요소의 자식 마진은 그 BFC 안에서만 적용되고, 형제 요소나 부모와는 절대로 병합되지 않습니다. 반대로 형제 요소 간 마진 병합을 막으려면 형제들의 공통 부모가 BFC를 가져야 하는 게 아니라, 각 형제를 BFC로 만들어야 합니다.
심화
BFC는 CSS 2.1 Section 9.4.1(Block formatting contexts)에 명시적으로 정의된 레이아웃 컨텍스트입니다. 명세에 따르면 BFC는 블록 레벨 박스들의 레이아웃이 이루어지는 독립적인 환경으로, 내부 박스들은 수직 방향으로 차례대로 배치되며 인접 마진은 해당 BFC 경계 내에서만 병합됩니다.
CSS 2.1 명세 기반 BFC 생성 조건 CSS 2.1 Section 9.4.1에 따르면 다음 요소들이 새로운 BFC를 생성합니다.
- float 요소 (
float: left | right) - 절대 위치 지정 요소 (
position: absolute | fixed) - 블록 컨테이너이지만 블록 박스가 아닌 요소 (
display: inline-block | table-cell | table-caption) overflow값이visible이 아닌 블록 박스
CSS Display Level 3 명세는 이 목록을 확장하여 display: flow-root를 BFC 생성 전용 속성으로 추가했습니다. 또한 display: flex와 display: grid는 각각 FFC(Flex Formatting Context)와 GFC(Grid Formatting Context)를 생성하는데, 이들은 BFC가 아니지만 마찬가지로 내부 마진 전파를 차단하고 자식 간 마진 병합을 발생시키지 않습니다.
Blink 렌더링 엔진의 BFC 구현
Blink 엔진(Chrome/Edge)에서 BFC는 LayoutBlockFlow 클래스로 표현됩니다. establishesBlockFormattingContext() 메서드가 해당 요소가 BFC를 생성하는지 판단하며, BFC를 생성하는 요소는 BlockFormattingContext 객체를 보유합니다. 마진 병합 로직은 BlockLayoutAlgorithm 내에서 처리되며, 현재 레이아웃이 BFC를 생성하는 경우 자식 요소들의 마진 스트럿(margin strut)이 외부로 전파되지 않도록 격리됩니다. 이 격리는 BFC 루트 요소의 레이아웃 결과를 확정할 때 margin strut를 부모에게 전달하지 않는 방식으로 구현됩니다.
컴포넌트 캡슐화 관점에서의 BFC 현대 컴포넌트 기반 개발(React, Vue)에서 BFC는 마진 캡슐화(margin encapsulation)의 단위입니다. 컴포넌트 래퍼가 BFC를 생성하지 않으면, 내부 자식 마진이 컴포넌트 바깥 레이아웃에 영향을 줄 수 있어 컴포넌트의 외부 인터페이스가 내부 구현에 종속됩니다. 이는 캡슐화 원칙 위반이며, 컴포넌트를 재배치하거나 컨텍스트를 바꿀 때 예기치 않은 레이아웃 버그를 유발합니다.
분리 요소 삽입으로 마진 인접성 해제
입문
마진 병합이 일어나려면 두 마진이 서로 직접 닿아 있어야 해요. 사이에 무언가를 끼워 넣으면 두 마진이 만나지 못해서 병합이 일어나지 않아요!
🧲 자석이 붙지 못하게 하려면? 두 자석이 서로 끌어당겨 붙으려 할 때, 그 사이에 나무 조각을 끼우면 자석끼리 직접 닿지 못해서 붙지 않아요. CSS 마진도 마찬가지예요. 두 마진 사이에 무언가 끼워 넣으면 병합이 차단돼요.
🧱 border(테두리)를 끼워 넣기
부모 요소에 아주 얇은 테두리(border)를 주면, 그 테두리가 부모 마진과 자식 마진 사이에 끼어들어서 두 마진이 직접 만나지 못해요. 심지어 transparent(투명)한 테두리도 효과가 있어서 보이지 않아도 작동해요.
🛋️ padding(안쪽 여백)을 끼워 넣기
border 대신 padding을 아주 작게 주는 방법도 있어요. 예를 들어 padding-top: 1px를 부모에 주면 부모의 위 마진과 자식의 위 마진 사이에 1px의 공간이 생겨서 두 마진이 만나지 못해요.
⚠️ 주의할 점이 있어요 이 방법은 실제로 layout에 1px의 추가 공간이 생겨요. 디자인에 영향을 줄 수 있고, 동료 개발자가 “이 1px은 뭐지?” 하고 지워버릴 수도 있어요. 그래서 주석으로 이유를 꼭 설명해두는 게 좋아요.
중급
마진 병합은 CSS 2.1에서 정의한 “마진 인접성(adjoining)” 조건이 충족될 때 발생합니다. 두 마진 사이에 라인 박스(line box), 클리어런스(clearance), 패딩(padding), 보더(border) 중 하나라도 존재하면 인접성 조건이 해제되어 병합이 차단됩니다. 이를 이용해 border나 padding을 최소 크기로 삽입하는 방식이 전통적인 병합 차단 방법입니다.
/* 부모-자식 마진 전이 차단 */
.parent {
/* 투명한 border가 부모 마진과 자식 마진 사이에 분리선 역할 */
border-top: 1px solid transparent;
}
.child {
margin-top: 30px; /* 이제 부모 안에서만 적용됨 */
}
/* 방법: 최소 padding 삽입 */
.parent {
padding-top: 1px; /* 분리선 역할, 0.0625rem으로 대체 가능 */
}
/* 단점: 레이아웃에 1px 추가 공간이 생김 */
/* 해결: 음수 마진으로 보정하거나, box-sizing에 따라 조정 */
.parent {
padding-top: 1px;
margin-top: -1px; /* 추가된 1px을 보정 */
}
방법 선택 시 고려사항 border와 padding 삽입 방식은 BFC를 생성하지 않으므로 형제 요소 간 마진 병합에는 효과가 없습니다. 이 방법은 주로 부모-자식 마진 전이를 막을 때 사용됩니다. 레이아웃에 의도하지 않은 픽셀이 추가되므로 팀 내 주석 규칙을 명확히 정하는 것이 권장됩니다.
심화
마진 인접성(adjoining) 해제는 CSS 2.1 Section 8.3.1의 adjoining 정의에서 열거한 분리 조건을 명시적으로 충족시키는 방식입니다. 명세는 두 마진이 adjoining하지 않으려면 그 사이에 “라인 박스, 클리어런스, 패딩 또는 보더”가 있어야 한다고 규정합니다. 이 조건을 최소 비용으로 충족하는 방법이 1px border 또는 1px padding 삽입입니다.
마진 인접성 조건과 분리 요소의 작동 원리 CSS 2.1 명세에서 adjoining margin은 다음 조건을 모두 만족할 때 정의됩니다.
- 두 마진이 동일한 BFC 내에 있을 것
- 블록 방향(block direction)으로 인접할 것
- 그 사이에 라인 박스, 클리어런스, padding, border가 없을 것
border나 padding을 삽입하면 세 번째 조건이 위반되어 adjoining 관계가 성립하지 않고 마진 병합이 차단됩니다. 이는 BFC를 변경하거나 포매팅 컨텍스트를 바꾸지 않고, 오직 adjoinning 조건만 해제하는 최소 개입(minimal intervention) 방식입니다.
실무 적용과 의도 명시 문제
border/padding 삽입 방식의 가장 큰 문제는 코드 의도의 불명확성입니다. border-top: 1px solid transparent나 padding-top: 1px는 CSS 코드만 보면 마진 병합 차단 목적인지 스타일링 목적인지 구분할 수 없습니다. 이로 인해 리팩터링 과정에서 해당 코드가 삭제되거나, 레이아웃에서 의도치 않게 1px 오차가 발생하는 문제가 생깁니다.
이 문제를 해결하기 위해 CSS Custom Properties(변수)나 유틸리티 클래스를 활용하는 방식이 권장됩니다. 예를 들어 .margin-isolation { border-top: 1px solid transparent; } 같은 명명 규칙을 팀 내에서 표준화하면 의도를 코드에 표현할 수 있습니다. 또는 display: flow-root처럼 의미가 명확한 속성으로 대체하는 것이 장기적으로 더 나은 선택입니다.
서브픽셀 렌더링과 1px 정밀도 고DPI 디스플레이(Retina, 2x/3x 픽셀 밀도)에서 1px border나 padding은 서브픽셀 단위로 렌더링될 수 있습니다. CSS의 1px는 논리 픽셀(logical pixel)이므로 물리 픽셀과 다를 수 있으나, adjoining 조건 해제는 CSS 레이아웃 연산 수준에서 처리되므로 렌더링 해상도와 무관하게 동작합니다. 0이 아닌 어떤 값이든 분리 효과가 있습니다.
overflow와 display: flow-root 비교
입문
마진 병합을 막는 방법 중 가장 많이 쓰이는 두 가지를 비교해 볼게요. 둘 다 효과는 있지만 부작용이 달라요!
🏠 overflow: hidden — 담장이 있는 집
overflow: hidden을 쓰면 그 요소가 독립된 공간(BFC)이 돼요. 마진이 밖으로 새어 나가지 않아요. 그런데 이름 그대로 내용이 넘치면 잘라버리는 부작용이 있어요. 마치 집에 높은 담장을 치면 마당에서 기르는 나무가 담장 밖으로 나오지 못하는 것과 같아요.
🏡 display: flow-root — 부작용 없는 독립 공간
display: flow-root는 딱 하나의 목적만 있어요. 요소를 독립된 공간(BFC)으로 만드는 것이에요. 내용을 자르지도 않고, 레이아웃을 바꾸지도 않아요. 오직 마진 병합만 차단해요. 이 속성을 보는 사람은 “아, 마진 병합을 막으려고 한 거구나”를 바로 알 수 있어요.
🤔 그럼 왜 overflow: hidden이 더 많이 쓰이나요?
display: flow-root는 비교적 새로운 속성이라서, 오래된 코드에서는 overflow: hidden을 마진 병합 차단 용도로 많이 썼어요. 또 일부 개발자들은 아직 flow-root를 모르기도 해요. 새로 작성하는 코드에서는 flow-root를 쓰는 게 더 좋아요.
💡 어떤 걸 써야 하나요?
내용이 넘칠 때 잘리면 안 되는 상황이라면 반드시 display: flow-root를 사용해야 해요. 내용이 절대 넘치지 않는다고 확신할 수 있을 때만 overflow: hidden을 써도 괜찮아요. 모르겠으면 flow-root가 더 안전해요.
중급
BFC를 생성하여 마진 병합을 차단하는 방법 중 가장 흔히 사용되는 두 가지는 overflow: hidden과 display: flow-root입니다. 둘 다 BFC를 생성하지만, 의도와 부작용이 다릅니다.
overflow: hidden
- BFC를 생성하여 마진 병합 차단
- 자식 콘텐츠가 요소 경계를 넘으면 잘림(clipping) 부작용
- position: absolute인 드롭다운, 툴팁이 잘릴 수 있음
- 오랜 브라우저 지원으로 레거시 코드에서 많이 사용됨
display: flow-root
- BFC 생성만을 목적으로 설계된 명시적 속성
- 시각적, 레이아웃 부작용 없음
- 코드에서 의도를 명확히 표현할 수 있음
/* overflow: hidden — 부작용 있음 */
.container-overflow {
overflow: hidden; /* BFC 생성, 하지만 내용 클리핑 가능 */
}
/* display: flow-root — 부작용 없음 (권장) */
.container-flow-root {
display: flow-root; /* BFC 생성 전용, 다른 영향 없음 */
}
.dropdown-parent {
overflow: hidden; /* 마진 병합 차단 목적 */
}
/* 문제: 절대 위치 드롭다운이 잘림 */
.dropdown-menu {
position: absolute;
top: 100%;
/* overflow: hidden으로 인해 부모 밖으로 나오지 못함 */
}
언제 어떤 것을 써야 하는가 실무에서는 다음 기준으로 선택합니다.
- 내부에
position: absolute자식이 없고, 콘텐츠 오버플로우가 발생하지 않는다고 확신하면overflow: hidden도 가능 - 불확실하거나 새로 작성하는 코드라면
display: flow-root가 더 안전하고 명확함
심화
overflow: hidden과 display: flow-root는 모두 BFC를 생성하지만, 명세 수준에서의 설계 목적과 발생하는 부수 효과(side effect)가 근본적으로 다릅니다.
overflow: hidden의 BFC 생성 메커니즘
CSS 2.1 Section 9.4.1에 따르면, overflow 값이 visible이 아닌 블록 박스는 새로운 BFC를 생성합니다. 이는 BFC 생성이 overflow의 주 목적이 아닌 부수 효과(side effect)임을 의미합니다. overflow: hidden의 주 목적은 콘텐츠 클리핑(content clipping)이며, 클리핑 영역은 CSS Overflow Module Level 3에 정의된 오버플로우 클립 경계(overflow clip boundary)를 따릅니다. 결과적으로 position: absolute나 position: fixed인 자식 요소를 포함하는 컨테이너에 overflow: hidden을 적용하면, 의도하지 않게 오버플로우 컨텍스트를 생성하여 절대 위치 요소의 클리핑이 발생할 수 있습니다.
display: flow-root의 명세적 정의
CSS Display Module Level 3 명세에서 display: flow-root는 블록 레벨 박스를 생성하고 그 콘텐츠를 flow 레이아웃으로 배치하되, 항상 새로운 블록 포매팅 컨텍스트를 설정하는 속성으로 정의됩니다. 이 속성은 오직 BFC 생성만을 목적으로 설계되었으며, overflow, float, display: inline-block 등이 BFC를 부수적으로 생성하는 것과 달리, 명시적으로 BFC를 생성합니다.
브라우저 지원과 폴리필 전략
display: flow-root는 Chrome 58+, Firefox 53+, Safari 13+에서 지원되며, IE와 구형 Edge(EdgeHTML)는 지원하지 않습니다. 레거시 브라우저 지원이 필요한 경우, ::before 가상 요소와 display: table을 이용한 clearfix 패턴이 폴리필로 사용됩니다. display: table은 테이블 셀 역할을 하면서 BFC를 생성하는 부수 효과를 이용한 것입니다.
의도 표현(Expressive Intent)의 관점
코드 품질 관점에서 display: flow-root는 overflow: hidden보다 의도가 명확합니다. 미래 유지보수자가 코드를 읽을 때, overflow: hidden은 클리핑 목적인지 BFC 생성 목적인지 알 수 없지만, display: flow-root는 명백히 BFC 생성 목적임을 알 수 있습니다. CSS 명세 팀이 flow-root를 설계한 핵심 이유 중 하나가 바로 이 의도 명확성(explicit intent)입니다.
Flexbox, Grid와 gap을 활용한 현대적 접근
입문
마진 병합을 막는 가장 깔끔한 방법은 처음부터 마진 병합이 일어나지 않는 레이아웃 방식을 쓰는 거예요. Flexbox와 Grid는 마진 병합이 아예 없어요!
🔲 Flexbox와 Grid는 왜 마진 병합이 없나요? Flexbox와 Grid는 기존 블록 레이아웃과 완전히 다른 방식으로 동작해요. 마치 전통 건축 방식(블록 레이아웃)에서는 벽돌을 쌓을 때 마진 병합이 생기지만, 현대식 조립 건축(Flexbox/Grid)에서는 각 모듈이 독립적으로 배치되어 마진이 서로 병합되지 않아요.
📐 gap이 마진보다 좋은 이유
Flexbox나 Grid를 쓸 때, 요소 사이 간격을 margin 대신 gap으로 주면 더 좋아요. gap은 부모 컨테이너가 자식들 사이에 균일하게 간격을 넣어주는 방식이에요. 첫 번째 요소 앞이나 마지막 요소 뒤에 불필요한 여백이 생기지 않아요.
🧩 실제로 어떻게 쓰나요?
세로로 쌓인 카드 목록을 만들 때, 부모에 display: flex; flex-direction: column; gap: 16px를 주면 카드들이 16px 간격으로 딱딱 배치돼요. 마진 병합 걱정 없이요! 마진을 각 카드에 일일이 줄 필요도 없어요.
💡 언제 마진을 쓰고 언제 gap을 써야 하나요?
컴포넌트 안에서 자식들 사이의 간격은 gap이 좋아요. 컴포넌트 자체가 바깥 요소들과 얼마나 떨어질지는 margin으로 관리해요. 두 역할이 다르기 때문에 구분해서 사용하는 게 더 명확해요.
중급
display: flex 또는 display: grid를 적용한 컨테이너는 각각 FFC(Flex Formatting Context)와 GFC(Grid Formatting Context)를 생성합니다. 이 포매팅 컨텍스트 안에서는 자식 요소들 사이에 마진 병합이 발생하지 않습니다. 여기에 마진 대신 gap 속성을 사용하면 마진 병합 문제를 원천적으로 우회할 수 있습니다.
gap의 장점
- 마진 병합 없음 (FFC/GFC에서는 마진 병합 자체가 없음)
- 첫/마지막 자식에 불필요한 여백 생성 안 함
- 모든 간격을 부모 컨테이너 한 곳에서 관리
column-gap,row-gap으로 방향별 간격 제어 가능
.card-list {
display: flex;
flex-direction: column;
gap: 16px; /* 카드 사이 간격 — 마진 병합 없음 */
}
/* 각 카드에 margin이 없어도 gap으로 간격 유지 */
.card {
padding: 16px;
border: 1px solid #ddd;
}
/* 마진 방식 — 마진 병합 발생 가능성 */
.item-margin {
margin-bottom: 16px; /* 마지막 아이템에도 불필요한 마진 */
}
/* gap 방식 — 마진 병합 없음, 불필요한 여백 없음 */
.list-with-gap {
display: flex;
flex-direction: column;
gap: 16px; /* 아이템 사이에만 적용, 마지막 아이템 뒤에는 없음 */
}
마진과 gap의 역할 분리
실무 가이드라인으로 컴포넌트 내부 간격(자식 요소 사이)은 gap으로 관리하고, 컴포넌트 외부 간격(컴포넌트와 외부 요소 사이)은 margin으로 관리하는 패턴이 권장됩니다. 이 분리를 따르면 컴포넌트 간 마진 병합 문제 자체가 발생하지 않습니다.
심화
Flexbox와 Grid는 블록 포매팅 컨텍스트(BFC)와 별개의 포매팅 컨텍스트를 생성하며, 각 명세에서 마진 병합을 명시적으로 배제하고 있습니다.
FFC와 GFC에서 마진 병합 배제 근거 CSS Flexbox Level 1 명세 Section 8.1에 따르면, flex 아이템들은 서로 마진을 병합하지 않습니다(Flex items do not collapse margins with each other or their flex container). CSS Grid Layout Level 1 명세도 동일하게 그리드 아이템 간 마진 병합이 발생하지 않음을 명시합니다. 이는 BFC의 마진 병합 규칙이 FFC/GFC에서는 적용되지 않음을 의미합니다.
gap의 명세적 정의와 마진과의 차이
gap은 CSS Box Alignment Level 3 명세에 정의된 레이아웃 프리미티브(layout primitive)로, 마진과 근본적으로 다른 개념입니다. 마진은 박스 모델의 일부로 요소에 귀속되는 속성이지만, gap은 컨테이너 레이아웃 알고리즘이 트랙(track, flex line 또는 grid track) 사이에 삽입하는 거터(gutter)입니다.
명세에 따른 gap의 주요 속성은 다음과 같습니다.
- 첫 번째와 마지막 자식 요소의 바깥에는 거터가 삽입되지 않음 (마진과 다른 점)
- 마진 병합 대상이 아님 (gap은 마진이 아님)
align-content: space-between과 달리 각 트랙 사이에 고정 공간을 삽입함row-gap과column-gap으로 방향별 독립 제어 가능
Blink 엔진의 gap 처리
Blink 엔진에서 gap은 flex/grid 레이아웃 알고리즘 내부에서 처리됩니다. Flex 레이아웃의 경우 FlexLayoutAlgorithm이 각 플렉스 라인을 배치할 때 row-gap 값을 트랙 간 거터로 추가합니다. 이 처리는 마진 계산 단계와 분리되어 있으므로 마진 병합 로직의 영향을 받지 않습니다. Grid 레이아웃도 마찬가지로 GridLayoutAlgorithm이 그리드 트랙 간 거터를 별도 단계에서 처리합니다.
디자인 시스템 관점에서의 gap vs margin
토큰 기반 디자인 시스템(Token-based Design System)에서 간격 관리를 gap과 margin으로 명확히 분리하는 패턴이 확산되고 있습니다. 컴포넌트 내부 간격(intra-component spacing)은 gap으로, 컴포넌트 외부 간격(inter-component spacing)은 margin으로 관리하면, 마진 병합 이슈가 컴포넌트 외부(조합 레이어)로만 국한됩니다. 조합 레이어에서는 Flex/Grid 컨테이너를 기본 래퍼로 사용하기 때문에 여기서도 마진 병합이 발생하지 않아, 결과적으로 전체 시스템에서 마진 병합 문제를 원천 제거할 수 있습니다.