01 — Flexbox 축 모델 (main / cross)
이 문서가 답하는 질문: Flexbox는 정확히 어떤 축 위에서 동작하며,
justify-*와align-*은 왜 다른 이름인가?
한 줄 답
Flexbox는 두 개의 직교축(main / cross) 위에서 동작하는 1차원 레이아웃이다.
justify-*는 항상 main 축,align-*은 항상 cross 축을 다룬다 —flex-direction이 바뀌면 두 축의 물리적 방향이 함께 회전한다.
Why — 왜 축 추상화가 필요한가
float이 풀지 못한 문제는 수직 정렬이었다. vertical-align은 inline·table-cell에서만 동작했고, margin: auto는 블록의 수평 중앙만 잡았다.
Flexbox는 이를 물리적 방향(top/left)을 버리고 논리적 축(main/cross)으로 재설계하여 해결했다. 그 결과:
flex-direction: row→ main = 가로, cross = 세로flex-direction: column→ main = 세로, cross = 가로- RTL(우→좌) 언어 → main의 시작점이 자동으로 우측
같은 코드(justify-content: flex-start)가 언어와 방향에 따라 다른 물리적 결과를 낸다 — 이게 logical property 사상의 핵심이다.
How — 어떻게 동작하는가
1) 두 축, 네 끝점
| 축 | 시작 | 끝 | 정렬 속성 |
|---|---|---|---|
| main | main-start | main-end | justify-content, justify-items, justify-self |
| cross | cross-start | cross-end | align-items, align-content (여러 줄), align-self |
justify-items / justify-self는 Grid 전용이고 Flexbox에서는 무시된다. Flex에서 main 축의 개별 정렬은 margin: auto로 한다.
2) flex-direction 4종
| 값 | main 축 | cross 축 | 시작점 |
|---|---|---|---|
row (기본) | 가로 → | 세로 ↓ | 좌상 (LTR 기준) |
row-reverse | 가로 ← | 세로 ↓ | 우상 |
column | 세로 ↓ | 가로 → | 좌상 |
column-reverse | 세로 ↑ | 가로 → | 좌하 |
*-reverse는 시각적 순서만 뒤집고 DOM 순서는 그대로 — 스크린리더는 DOM 순서로 읽는다. 접근성 문제를 일으키는 흔한 함정이다.
3) flex-wrap
| 값 | 동작 |
|---|---|
nowrap (기본) | 한 줄 강제 — 항목이 넘쳐도 줄바꿈 X, shrink 발동 |
wrap | cross 축으로 줄바꿈 |
wrap-reverse | 줄바꿈 방향 반전 |
여러 줄이 되면 align-content가 활성화된다 (한 줄일 때는 무시).
4) gap (2021~)
.flex {
display: flex;
gap: 16px; /* row-gap + column-gap */
/* gap: 8px 16px; -- row 8, column 16 */
}gap은 항목 사이에만 적용 — 컨테이너 끝에는 안 붙는다. margin 트릭(> * + *)을 대체한 사실상 표준.
What — 구체 사양
컨테이너 속성
| 속성 | 값 | 의미 |
|---|---|---|
display | flex / inline-flex | Flex 포매팅 컨텍스트 생성 |
flex-direction | row / row-reverse / column / column-reverse | main 축 방향 |
flex-wrap | nowrap / wrap / wrap-reverse | 줄바꿈 |
flex-flow | <direction> <wrap> | direction + wrap shorthand |
justify-content | flex-start / flex-end / center / space-between / space-around / space-evenly / start / end | main 축 정렬 |
align-items | stretch (기본) / flex-start / flex-end / center / baseline / start / end | cross 축 항목 정렬 |
align-content | (justify-content와 동일 값) | 여러 줄일 때 cross 축 줄 정렬 |
gap / row-gap / column-gap | <length> | 항목 사이 간격 |
place-content | <align-content> <justify-content> | shorthand |
place-items | <align-items> <justify-items> | shorthand (Grid에서 더 유용) |
항목 속성 (다음 문서 02 참고)
flex, flex-grow, flex-shrink, flex-basis, order, align-self.
justify-content 6값 시각화
| 값 | 끝 여백 | 사이 여백 |
|---|---|---|
flex-start | 0 / X | 0 |
space-between | 0 / 0 | 균등 |
space-around | 0.5단위 | 1단위 |
space-evenly | 1단위 | 1단위 |
What-if — 잘못 쓰면
1) align-items: stretch로 이미지가 찌그러진다
기본값 stretch는 cross 축으로 항목을 늘린다. flex item 안에 <img>가 있으면 이미지가 컨테이너 높이만큼 늘어나 비율이 깨진다.
/* fix */
.flex { align-items: flex-start; } /* 또는 center */
/* 또는 항목별로 */
.flex > img { align-self: flex-start; }2) justify-content: space-between인데 한 줄에 마지막 항목이 짝수가 아닐 때
마지막 줄의 항목이 좌측 정렬되어 보기 흉하다. 빈 placeholder item 추가 또는 Grid의 auto-fill로 전환하는 게 정석.
3) flex-direction: column에서 width: 100%인데 안 늘어난다
column일 때 cross 축이 가로다. cross 축은 align-items가 지배 — align-items: stretch(기본)면 늘어나지만, align-items: flex-start였다면 콘텐츠 폭만큼만 차지한다.
4) gap이 IE에서 안 먹는다
gap (flex 컨텍스트)는 2021년 Safari 14.1부터 지원. 그 이전 호환이 필요하면 margin 트릭으로 회귀.
5) flex-wrap: wrap인데 항목이 줄어든다
flex-shrink(기본 1)가 살아있어서 줄바꿈보다 축소를 먼저 시도한다. flex-shrink: 0을 항목에 주거나, 항목의 flex-basis를 명시.
Insight — Flexbox의 디자인 결정
Flexbox 스펙은 이미 늘어난 상태에서 정렬한다(post-stretch alignment)는 모델이다. 즉:
- 먼저
flex-grow/flex-shrink/flex-basis로 공간 분배가 끝난다. - 그 다음
justify-content/align-items가 남은 공간을 정렬한다.
이 순서를 모르면 “왜 justify-content: center가 안 먹지?” 같은 혼란이 생긴다. 답은 — flex: 1로 모든 공간을 다 차지해서 남은 공간이 0이라 정렬할 게 없기 때문이다.
또 하나의 디자인 결정: flex 컨테이너의 자식들은 자동으로 inline의 anonymous text node를 제외하고 박싱된다. 그래서 flex container 안의 텍스트는 anonymous wrapper에 감싸진다 — :first-child 선택자가 의도와 다르게 동작하는 원인이다.
역사적으로 Flexbox는 세 번 스펙이 바뀌었다 — 2009년 box-*, 2011년 flexbox(중간 문법), 2012년 현재 문법. display: -webkit-box 같은 옛 prefix가 아직 일부 코드에 남아있는 이유다.
요약 + Mermaid
- Flexbox는 main + cross 두 축.
justify-*는 main,align-*은 cross. flex-direction이 바뀌면 두 축이 함께 회전한다.gap은 항목 사이에만, 끝에는 안 붙는다 —margin트릭의 정식 후계.align-items: stretch(기본)는 이미지·고정 박스를 찌그러뜨리는 흔한 함정.
다음: 02-flex-item — flex shorthand의 세 값과 min-width: 0 함정.