🎨 Frontend CSS3. Flexbox & Grid01 — Flexbox 축 모델

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) 두 축, 네 끝점

시작정렬 속성
mainmain-startmain-endjustify-content, justify-items, justify-self
crosscross-startcross-endalign-items, align-content (여러 줄), align-self

justify-items / justify-selfGrid 전용이고 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 발동
wrapcross 축으로 줄바꿈
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 — 구체 사양

컨테이너 속성

속성의미
displayflex / inline-flexFlex 포매팅 컨텍스트 생성
flex-directionrow / row-reverse / column / column-reversemain 축 방향
flex-wrapnowrap / wrap / wrap-reverse줄바꿈
flex-flow<direction> <wrap>direction + wrap shorthand
justify-contentflex-start / flex-end / center / space-between / space-around / space-evenly / start / endmain 축 정렬
align-itemsstretch (기본) / flex-start / flex-end / center / baseline / start / endcross 축 항목 정렬
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-start0 / X0
space-between0 / 0균등
space-around0.5단위1단위
space-evenly1단위1단위

What-if — 잘못 쓰면

1) align-items: stretch로 이미지가 찌그러진다

기본값 stretchcross 축으로 항목을 늘린다. 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)는 모델이다. 즉:

  1. 먼저 flex-grow / flex-shrink / flex-basis공간 분배가 끝난다.
  2. 그 다음 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 함정.