07 — Flexbox vs Grid: 선택 기준과 합성 패턴
이 문서가 답하는 질문: 같은 화면을 두 모델로 다 짤 수 있을 때, 무엇이 선택의 기준인가?
한 줄 답
차원이 결정한다. 한 축 위에 콘텐츠 흐름이면 Flex, 두 축의 격자면 Grid. 실무에서는 둘 중 하나가 아니라 계층적으로 함께 — 페이지 골격은 Grid, 그 안의 인라인 정렬(버튼 그룹, 헤더 네비, 칩 리스트)은 Flex.
Why — 왜 이 선택이 중요한가
잘못 선택하면 코드는 동작하지만 변경에 깨진다:
- Flex로 카드 그리드 → 행 사이 컬럼 정렬이 미디어 쿼리 추가 시 깨진다.
- Grid로 메뉴 바 → 메뉴 개수가 동적으로 바뀔 때 트랙 정의가 어색.
- Grid 안에 Flex 트랙 → 트랙 폭과 Flex 분배가 충돌해 디버깅 어려움.
선택은 지금 보기에 동작한다가 아니라 디자인 의도가 어느 축인가에 따라야 한다.
How — 결정 트리
8가지 패턴별 권장
| 패턴 | 권장 | 이유 |
|---|---|---|
| 헤더 네비게이션 (로고 + 메뉴 + 액션) | Flex | 한 축, 메뉴 개수 가변, gap·align만 필요 |
| 버튼 그룹 (Primary + Secondary) | Flex | 한 축, gap, optional 줄바꿈 |
| 칩/태그 리스트 | Flex wrap | 한 축 + 자동 줄바꿈, 폭 가변 |
| 폼 (label + input 행) | Grid (2 columns) | 라벨/입력 컬럼이 정렬되어야 |
| 카드 그리드 (반응형) | Grid auto-fill/fit | 화면 폭에 따라 컬럼 수 자동 |
| 페이지 골격 (header/sidebar/main) | Grid template-areas | 영역이 명시적, 반응형 재배치 쉬움 |
| 사진 갤러리 (다양한 비율) | Grid + dense 또는 Masonry/JS | 시각 밀도 |
| 사이드바 내부 메뉴 리스트 | Flex column | 한 축, gap, 줄바꿈 없음 |
합성 패턴 (계층)
/* 골격 — Grid */
.app {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 56px 1fr;
grid-template-areas:
"header header"
"side main";
}
.app > header { grid-area: header; }
.app > aside { grid-area: side; }
.app > main { grid-area: main; }
/* 헤더 내부 — Flex */
.app > header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0 24px;
}
/* 메인 콘텐츠 — Grid (카드 리스트) */
.app > main {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
padding: 24px;
}
/* 카드 내부 — Flex column */
.card {
display: flex;
flex-direction: column;
gap: 8px;
}한 컴포넌트에 둘을 동시에 쓰지 말 것 — 컨테이너의 display는 한 번에 하나, 그 자식 레벨에서 다시 선택.
What — 비교 매트릭스
| 항목 | Flexbox | Grid |
|---|---|---|
| 차원 | 1D (한 축) | 2D (양 축) |
| 트랙 정의 주체 | 콘텐츠 | 컨테이너 |
| 기본 정렬 | align-items: stretch (cross) | align-items: stretch (양 축) |
| 항목 명시 배치 | order만 (한 축 순서) | grid-area, line, span |
| 줄바꿈 | flex-wrap (각 줄 독립) | 자동 — 트랙이 정의되어 있음 |
| 빈 셀 | 없음 (콘텐츠만) | 가능 (. in areas) |
| 사이징 | flex-grow/shrink/basis | fr, minmax, intrinsic |
| 반응형 카드 | 어려움 (각 줄 독립) | 한 줄 (auto-fill + minmax) |
| 중첩 정렬 | 불가 (각 컨테이너 독립) | Subgrid로 가능 |
| 가장 흔한 함정 | min-width: 0 누락 | minmax(0, 1fr) 누락 |
같은 결과, 다른 코드 — 헤더 예제
/* Flex 버전 */
header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}/* Grid 버전 */
header {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 16px;
}
header > nav { justify-self: end; } /* 우측 정렬 */| 기준 | Flex 유리 | Grid 유리 |
|---|---|---|
| 메뉴 개수 가변 | ✓ | (auto 1fr auto는 3 영역 가정) |
space-between/evenly | ✓ (justify-content) | △ (수동 계산) |
| 영역이 고정되어야 (로고는 좌측, 액션은 우측) | △ | ✓ (justify-self) |
→ 헤더는 Flex가 사실상 표준이지만, 영역이 의미를 가져야 하면 Grid도 가능.
What-if — 잘못 쓰면
1) Flex로 12-column 그리드 흉내
.row { display: flex; flex-wrap: wrap; }
.col-4 { flex: 0 0 33.333%; }문제:
- 각 행이 독립이라 다른 행과 컬럼 폭이 안 맞음 (콘텐츠 길이 차).
gap을 줘도 percentage가 그 위에 더해져 overflow.- 카드 높이 정렬은
align-items: stretch로 부분 해결되지만 행 간엔 안 됨.
해결: Grid로 전환.
.row { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; }
.col-4 { grid-column: span 4; }2) Grid로 동적 메뉴 바
nav { display: grid; grid-template-columns: repeat(5, auto); }문제: 메뉴가 5개 가정. 4개·6개일 때 트랙이 부족하거나 빈 칸이 생김.
해결: Flex로 전환.
nav { display: flex; gap: 16px; }3) Grid 컨테이너 자식에 Flex를 또 줬는데 자식이 안 늘어남
원인: Grid 자식이 Grid item. display: flex가 그 자체 컨텍스트를 만들지만, Grid item으로서의 셀 폭은 Grid가 결정. min-width: 0 함정도 동일하게 발생.
해결: Grid track에 minmax(0, 1fr), Flex 자식에 min-width: 0.
4) place-items: center로 모든 걸 해결하려 함
place-items: center는 모든 항목을 셀 중앙에 — 카드 콘텐츠가 셀보다 작으면 가운데 정렬. 대부분의 경우 의도는 stretch인데 의도치 않게 콘텐츠가 작아 보임.
해결: 기본 stretch 유지, 필요한 셀에만 align-self: center.
5) Flex + Flex + Flex 3중 중첩
증상: 디버깅이 미궁.
원인: 1D 도구로 2D 문제를 풀려고 함.
해결: 중간을 Grid로 바꿀 수 있는지 검토.
Insight — 두 모델의 미래
CSSWG는 두 모델을 수렴시키는 방향으로 진화시키고 있다.
- Box Alignment Module Level 3 —
align-*/justify-*/gap/place-*을 Flex·Grid·Block·Multi-column에 공통으로 적용.gap이 2021년 Flex로 들어온 게 그 신호. - Subgrid — Grid의 중첩 한계를 풀면서 Flex 영역에 더 큰 그림(전체 정렬)을 가능하게 함.
- Masonry — Grid가 Flex의 “흐름” 모델을 일부 흡수하려는 시도.
미래의 이상은 — 하나의 display 모드에서 1D/2D를 매끄럽게 표현. 하지만 실무에서는 당분간 두 모델을 의식적으로 분리해서 합성하는 게 가장 안정적.
디자이너의 인지 모델과 매칭
흥미로운 관찰: 디자이너는 figma/sketch에서 두 가지 도구를 쓴다.
- Auto Layout (Figma) → Flex와 1:1 매칭. 항목·gap·정렬.
- Constraints/Grid (Figma) → Grid와 매칭. 영역 정의, 12-column.
Figma에서 *“이건 Auto Layout으로 짰어요”*가 Flex로 구현, *“12 컬럼 그리드 위에 올렸어요”*가 Grid로 구현과 거의 1:1. 디자이너의 의도를 코드 모델로 직접 옮길 수 있다는 게 모던 CSS의 큰 진전이다.
한 줄 요약 (실무용)
Flex로 줄을 짜고, Grid로 격자를 짜고, 둘을 한 컴포넌트 안에 섞지 말고 계층으로 합성한다.
요약 + Mermaid
- 차원이 결정 — 1D면 Flex, 2D면 Grid.
- 계층적으로 합성 — 골격 Grid, 영역 Flex.
- 12-column 그리드는 Grid, 메뉴/버튼/칩은 Flex.
- 반응형 카드 =
repeat(auto-fill, minmax(...))— Grid의 가장 큰 승리. - Box Alignment 표준이 두 모델을 수렴시키는 중.