한 줄 정답 — 부모에 display: flex를 주면 자식들이 한 줄로 늘어선다. 이때 주축(기본 가로)justify-content로, 교차축(기본 세로)align-items로 정렬한다. flex-direction으로 방향을 바꾸면 이 두 축의 역할도 함께 바뀐다. 그래서 "가로/세로"가 아니라 "주축/교차축"으로 외우는 게 핵심이다.

핵심 요약#

  • flex는 부모(컨테이너)에 display: flex 를 주는 데서 시작한다. 그러면 자식들이 flex 아이템이 된다.
  • 주축 정렬은 justify-content, 교차축 정렬은 align-items. 기본 방향(row)에서 주축은 가로, 교차축은 세로다.
  • flex-direction: column으로 바꾸면 주축이 세로가 되어, 두 속성이 맡는 방향이 서로 뒤바뀐다.
  • 줄바꿈은 flex-wrap: wrap, 간격은 gap. 아이템의 늘고 줄어듦은 flex(grow·shrink·basis)로 조절한다.
  • 정렬이 "안 되는" 대부분은 축을 반대로 줬거나, 아이템이 flex-shrink 때문에 줄어든 경우다.

CSS flexbox 정렬, 주축과 교차축부터#

flexbox가 헷갈리는 단 하나의 이유는 축(axis) 을 안 잡고 속성부터 외워서다. flex에는 두 개의 축이 있다.

  • 주축(main axis) — 아이템이 늘어서는 방향. flex-direction이 정한다(기본 row = 가로).
  • 교차축(cross axis) — 주축에 수직인 방향(기본은 세로).

정렬 속성은 이 두 축에 각각 붙는다.

  • justify-content주축 방향 정렬
  • align-items교차축 방향 정렬

기본 상태(flex-direction: row)에서는 주축이 가로라서 justify-content가 좌우, align-items가 위아래를 맡는다. 이 대응만 머릿속에 있으면 나머지는 값을 고르는 일이다.

컨테이너(부모)에 주는 속성#

정렬과 배치의 대부분은 부모에 준다.

display: flex — 시작점#

.container {
  display: flex; /* 직계 자식들이 flex 아이템이 되어 한 줄로 늘어선다 */
}

이 한 줄만으로 자식들이 가로로 나란히 배치된다(블록 요소라도). 여기서부터 정렬을 얹는다.

flex-direction — 주축의 방향#

.container {
  display: flex;
  flex-direction: row; /* row(기본) | row-reverse | column | column-reverse */
}

row는 가로로, column은 세로로 쌓는다. 이 값이 주축을 정하기 때문에, 뒤에 나오는 justify-content·align-items가 어느 방향을 맡는지가 여기서 결정된다.

justify-content — 주축 정렬#

.container {
  display: flex;
  justify-content: center;
  /* flex-start(기본) | center | flex-end | space-between | space-around | space-evenly */
}

자주 쓰는 값은 이렇다.

  • flex-start / center / flex-end — 시작/가운데/끝으로 모음
  • space-between — 양 끝에 붙이고 사이를 균등하게(내비게이션 바에 자주 쓴다)
  • space-around — 각 아이템 양옆에 같은 여백
  • space-evenly — 아이템 사이·양 끝 여백을 모두 똑같이

align-items — 교차축 정렬#

.container {
  display: flex;
  align-items: center;
  /* stretch(기본) | flex-start | center | flex-end | baseline */
}

justify-contentalign-items의 차이가 바로 이것이다. justify-content는 주축(기본 가로), align-items는 교차축(기본 세로) 을 정렬한다. 기본 동작인 stretch는 아이템에 높이가 따로 없을 때(교차 크기 auto) 교차축을 가득 채운다. 높이가 제각각인 카드를 한 줄 안에서 같은 높이로 맞추고 싶을 때 유용하다.

flex-wrap — 한 줄을 넘으면 줄바꿈#

.container {
  display: flex;
  flex-wrap: wrap; /* nowrap(기본) | wrap | wrap-reverse */
}

기본값 nowrap은 공간이 모자라도 한 줄을 유지한다. 그래서 아이템이 flex-shrink로 줄어들고, 더는 못 줄면(내용 크기 한계) 컨테이너 밖으로 넘친다. wrap을 주면 넘치는 아이템이 다음 줄로 내려가, 반응형 레이아웃의 기본이 된다.

align-content — 여러 줄 사이 정렬#

.container {
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  /* flex-start | center | flex-end | space-between | space-around | stretch(기본) */
}

flex-wrap으로 여러 줄이 됐을 때, 줄 묶음 전체를 교차축에서 정렬·분배하는 건 align-items가 아니라 align-content다. align-items는 한 줄 안에서만 동작하므로, 줄과 줄 사이 간격이 의도와 다르면 이 속성을 본다. 단, 한 줄(또는 nowrap)일 때는 효과가 없다.

gap — 아이템 사이 간격#

.container {
  display: flex;
  gap: 16px; /* 아이템 사이 간격. row-gap / column-gap로 따로도 가능 */
}

예전엔 margin으로 간격을 줬지만, 이제는 gap 한 줄이 가장 깔끔하다. 양 끝에는 여백이 안 생기고 아이템 사이에만 간격이 들어간다.

아이템(자식)에 주는 속성#

각 아이템이 공간을 어떻게 차지할지는 자식에 준다.

flex — 늘고, 줄고, 기본 크기#

flex는 세 속성을 한 번에 적는 단축이다.

.item {
  flex: 1 1 240px; /* flex-grow  flex-shrink  flex-basis */
}
  • flex-grow(기본 0) — 남는 공간을 얼마나 차지할지. 0이면 안 늘어난다.
  • flex-shrink(기본 1) — 공간이 모자랄 때 얼마나 줄지. 1이면 줄어든다.
  • flex-basis(기본 auto) — 늘고 줄기 전의 기본 크기.

자주 쓰는 flex: 11 1 0%과 같아서, 형제들과 남는 공간을 똑같이 나눠 갖는다. 컬럼을 균등 분할할 때 쓴다.

align-self — 이 아이템만 교차축 정렬 바꾸기#

.item {
  align-self: flex-end; /* 부모의 align-items를 무시하고 이 아이템만 따로 */
}

order — 시각적 순서 바꾸기#

.item {
  order: -1; /* 기본 0. 작을수록 앞으로. HTML 순서는 그대로 두고 보이는 순서만 변경 */
}

실전 예제#

내비게이션 바 — 로고는 왼쪽, 메뉴는 오른쪽#

<nav class="nav">
  <div class="logo">LOGO</div>
  <ul class="menu">
    <li></li>
    <li>소개</li>
    <li>연락</li>
  </ul>
</nav>
.nav {
  display: flex;
  justify-content: space-between; /* 로고와 메뉴를 양 끝으로 */
  align-items: center;            /* 세로 가운데로 높이 맞춤 */
}

.menu {
  display: flex; /* 메뉴 항목도 가로로 */
  gap: 16px;
  list-style: none;
}

space-between이 로고와 메뉴를 양 끝으로 밀어내고, align-items: center가 둘의 높이를 맞춘다. 메뉴 안쪽도 flex로 만들어 항목을 가로로 늘어놓는 점에 주목하자.

반응형 카드 — 좁아지면 자동 줄바꿈#

.cards {
  display: flex;
  flex-wrap: wrap; /* 좁아지면 다음 줄로 */
  gap: 16px;
}

.card {
  flex: 1 1 240px; /* 기본 240px, 공간이 남으면 늘고 모자라면 줄어든다 */
}

flex: 1 1 240px는 카드를 240px 기준으로 두되, 한 줄에 남는 공간은 나눠 갖고, 폭이 좁아지면 wrap으로 다음 줄에 떨어뜨린다. 대부분은 미디어쿼리 없이도 자연스러운 반응형이 되고, 특정 폭에서 컬럼 수를 강제하는 등 세밀한 제어가 필요할 때만 미디어쿼리 글을 더하면 된다.

flex 정렬이 안 될 때#

대부분 다음 네 가지 중 하나다.

1. 축을 반대로 줬다#

justify-content로 세로를, align-items로 가로를 맞추려 한 경우다. 기본 방향에서는 justify-content가 가로, align-items가 세로다. 둘을 바꿔 적지 않았는지 먼저 본다.

2. flex-direction이 column이라 축이 뒤바뀌었다#

flex-direction: column이면 주축이 세로가 된다. 그래서 justify-content가 세로, align-items가 가로를 맡는다. 방향을 바꿨다면 두 속성의 역할도 바뀐 것이다.

3. 아이템이 너무 줄거나, 안 줄어서 넘친다#

비슷해 보이지만 원인이 다른 두 상황이 섞이기 쉽다.

  • 너무 줄어든다flex-shrink 기본값이 1이라 공간이 모자라면 아이템이 내용보다 작아진다. 줄지 않게 하려면 flex-shrink: 0을 주거나 min-width로 하한을 정한다.
  • 안 줄어서 넘친다 — flex 아이템은 묵시적으로 min-width: auto(내용 최소 크기)라, 긴 텍스트가 내용보다 작게는 안 줄어 컨테이너 밖으로 넘치거나 text-overflow: ellipsis가 안 먹는다. 이때는 그 아이템(또는 중첩된 flex 자식)에 min-width: 0을 줘야 더 줄어든다.

4. 세로 가운데가 안 맞는다 (기본 방향 기준)#

기본 방향(row)에서 align-items: center를 줬는데 그대로라면, 보통 부모의 교차축(세로)에 여유 공간이 없어서다. 부모에 heightmin-height로 높이를 확보하면 가운데가 보인다. 가운데 정렬의 여러 방법과 실패 원인은 CSS 가운데 정렬 글에 따로 정리해 두었다.

자주 묻는 질문#

Q. justify-content와 align-items 차이가 뭔가요?

A. 정렬하는 축이 다릅니다. justify-content는 주축(기본 방향 row에서는 가로), align-items는 교차축(기본 세로)을 정렬합니다. flex-direction을 바꾸면 두 축의 방향이 서로 뒤바뀌므로, "가로/세로"가 아니라 "주축/교차축"으로 기억하는 편이 안전합니다.

Q. flexbox로 가로·세로 가운데 정렬은 어떻게 하나요?

A. 부모에 display: flex; justify-content: center; align-items: center를 주면 됩니다(세로가 보이려면 부모에 높이 확보). 다른 방법과 실패 원인까지는 CSS 가운데 정렬 글에 정리해 두었습니다.

Q. flex-direction을 column으로 바꿨더니 정렬이 반대로 돼요. 왜인가요?

A. 축이 바뀌었기 때문입니다. column이면 주축이 세로가 되어 justify-content가 세로, align-items가 가로를 맡습니다. 정렬이 의도와 반대로 보인다면 방향을 먼저 확인하세요.

Q. flex 아이템이 자꾸 줄어들어요.

A. flex-shrink 기본값이 1이라 공간이 모자라면 아이템이 줄어듭니다. 줄지 않게 하려면 해당 아이템에 flex-shrink: 0 또는 min-width를 지정하세요.

관련 글#

flex는 결국 "박스를 어떻게 배치하는가"의 한 방법이다. 토대가 되는 개념과, 정렬의 한 갈래인 가운데 정렬을 함께 보면 응용이 쉬워진다.

더 알아보기#