한 줄 요약 — 한 줄에 선 자식들이 남는 공간을 나눠 갖는 규칙이 flex-grow, 모자랄 때 양보하는 규칙이 flex-shrink, 그 계산의 출발 크기가 flex-basis다. 세 값을 한 번에 적은 것이 flex 단축 속성이고, 아이템 사이 간격은 gap, 교차축 정렬은 align-items가 맡는다. 오늘은 이 속성들로 사이드바·본문·도구바의 폭을 분배한다.

학습 목표#

  • flex-grow로 남는 공간을, flex-shrink로 모자란 공간을 어떻게 나누는지 설명할 수 있다.
  • flex-basis가 grow·shrink 계산의 출발 크기라는 걸 이해하고 auto0의 차이를 안다.
  • flex: 1 단축 표기가 무엇으로 풀리는지 알고 flex-grow: 1과 구분할 수 있다.
  • gap으로 아이템 사이 간격을, align-items/align-self로 교차축 정렬을 줄 수 있다.

오늘의 비유 — 짐을 나눠 드는 사람들의 협의#

좁은 복도를 여러 사람이 나란히 서서 큰 짐을 함께 옮긴다고 해 보자. 복도 폭은 정해져 있고, 사람마다 처음 차지하는 폭이 있다. 이 "처음 차지하는 폭"이 flex-basis다.

그런데 복도에 자리가 남으면 누군가는 더 벌려 서야 한다. 이때 "남는 자리는 내가 더 가져갈게"라고 손드는 정도가 flex-grow다. 반대로 복도가 비좁아 다 들어가지 못하면 누군가는 어깨를 움츠려 폭을 줄여야 한다. "내가 더 양보할게"의 정도가 flex-shrink다. 남는 공간은 grow가, 모자란 공간은 shrink가 나눈다 — 오늘은 이 협의의 규칙을 손에 익힌다. (사람과 사람 사이에 일정한 간격을 두는 일은 gap, 짐을 드는 높이를 맞추는 일은 align-items다.)

핵심 개념#

flex-basis — 나누기 전의 출발 크기#

flex-basis는 남는·모자란 공간을 나누기 전에 아이템이 차지하는 기본 크기다. 주축이 가로면 너비, 세로면 높이 역할을 한다. 기본값은 auto로, 이때는 아이템의 내용(또는 지정된 width) 크기를 출발점으로 삼는다.

.item {
  flex-basis: 200px; /* 일단 200px에서 시작 */
}

flex-basis: 0을 주면 "내용 크기는 잊고 0에서 출발하라"는 뜻이 된다. 이 차이가 뒤에서 flex: 1을 이해하는 열쇠가 된다.

flex-grow — 남는 공간을 나누는 비율#

아이템들의 기본 크기를 다 합쳐도 컨테이너에 자리가 남으면, 그 남는 공간flex-grow 값의 비율대로 나눠 가진다. 단위 없는 숫자이고 기본값은 0(늘지 않음)이다.

.a { flex-grow: 1; }
.b { flex-grow: 2; } /* 남는 공간을 a의 두 배로 가져간다 */

주의할 점은 grow가 나누는 건 전체 너비가 아니라 남는 공간이라는 것이다. 그래서 두 아이템에 똑같이 flex-grow: 1을 줘도, 출발 크기(flex-basis)가 다르면 최종 너비는 같지 않다.

flex-shrink — 모자란 공간을 양보하는 비율#

반대로 기본 크기의 합이 컨테이너보다 크면 아이템들이 줄어든다. 얼마나 줄어들지를 정하는 게 flex-shrink이고, 기본값은 1(필요하면 줄어듦)이다. 0을 주면 그 아이템은 줄어들기를 거부한다.

.logo {
  flex-shrink: 0; /* 좁아져도 이 요소는 줄지 않는다 */
}

flex 단축 속성 — 세 값을 한 줄로#

flexflex-grow, flex-shrink, flex-basis를 한 번에 적는 단축 속성이다. 자주 쓰는 표기는 외워 두면 편하다.

.item { flex: 1; }    /* = flex: 1 1 0    → 모두 같은 폭으로 */
.item { flex: auto; } /* = flex: 1 1 auto → 내용 크기를 살리며 늘어남 */
.item { flex: none; } /* = flex: 0 0 auto → 늘지도 줄지도 않음 */

여기서 flex: 1flex-basis0으로 만든다는 점이 중요하다. 그래서 여러 아이템에 flex: 1을 주면 내용 길이와 무관하게 폭이 똑같아진다.

gap과 align-items#

아이템 사이 간격은 gap 한 줄로 준다. 교차축(주축과 직각) 정렬은 지난 회차에 잠깐 본 align-items가 맡고, 특정 아이템 하나만 다르게 맞추고 싶으면 그 아이템에 align-self를 준다.

.bar {
  display: flex;
  gap: 16px;           /* 아이템 사이 간격 */
  align-items: center; /* 교차축(세로) 가운데 */
}

함께 따라하기 — 사이드바·본문·도구바 폭 나누기#

복도에 선 세 사람처럼, 화면을 사이드바·본문·도구바 세 칸으로 나눠 본다. 양쪽은 폭을 고정하고 가운데 본문만 남는 공간을 모두 차지하게 만든다.

<div class="layout">
  <aside class="sidebar">사이드바</aside>
  <main class="main">본문</main>
  <div class="toolbar">도구</div>
</div>
.layout {
  display: flex;
  gap: 16px;
  height: 320px;
}
.sidebar { flex: 0 0 200px; } /* 200px 고정, 양보도 욕심도 없음 */
.main    { flex: 1; }         /* 남는 공간을 전부 가져감 */
.toolbar { flex: 0 0 56px; }  /* 56px 고정 */

저장하고 브라우저 폭을 좁혔다 넓혔다 해 보면, 양쪽 사이드바와 도구바는 폭이 그대로 고정되고 가운데 본문만 늘었다 줄었다 한다. flex: 0 0 200px는 "안 늘고 안 줄고 200px"라는 뜻이라 폭을 못 박을 때 편하다.

흔한 실수 3가지#

1. flex: 1과 flex-grow: 1을 같다고 생각한다#

둘 다 "늘어나라"는 뜻이라 같아 보이지만 결과가 다르다. 글자 수가 다른 메뉴 두 개를 나란히 두면 차이가 드러난다.

/* (내용이 긴 쪽이 더 넓어집니다 — 폭이 같지 않습니다) */
.menu a { flex-grow: 1; }

flex-grow: 1만 주면 flex-basis는 기본값 auto라서, 각자 글자 크기에서 출발해 남는 공간만 똑같이 나눈다. 그래서 글자가 긴 항목이 더 넓다. 폭을 똑같이 맞추려면 출발점을 0으로 만드는 flex: 1을 쓴다.

.menu a { flex: 1; } /* = 1 1 0 → 내용 길이와 무관하게 같은 폭 */

2. gap으로 될 간격을 margin으로 우회한다#

예전에는 flex 아이템 사이 간격을 margin으로 줬다. 마지막 항목만 빼야 해서 코드가 번거로웠다.

/* (마지막 항목을 따로 처리해야 합니다) */
.item { margin-right: 16px; }
.item:last-child { margin-right: 0; }

지금은 flex 컨테이너의 gap이 모든 최신 브라우저에서 동작한다. 한 줄로 끝나고, 마지막 항목 예외 처리도 필요 없다.

.bar { display: flex; gap: 16px; }

3. flex-shrink: 0을 안 줘서 안 줄어야 할 게 줄어든다#

flex-shrink의 기본값은 1이라, 공간이 모자라면 모든 아이템이 같이 줄어든다. 로고나 아이콘처럼 크기가 유지돼야 할 요소가 찌그러지는 이유다.

/* (공간이 좁아지면 로고까지 함께 찌그러집니다) */
.logo { width: 120px; }

줄어들면 안 되는 요소에는 flex-shrink: 0을 박아 둔다. flex: none(0 0 auto)을 써도 같은 효과다.

.logo { width: 120px; flex-shrink: 0; }

오늘 배운 것 체크리스트#

  • flex-grow남는 공간을, flex-shrink모자란 공간을 비율대로 나눈다.
  • flex-basis는 나누기 전의 출발 크기이고, 기본값은 auto다.
  • flex: 11 1 0으로 풀려 아이템 폭을 똑같게 만든다.
  • 아이템 사이 간격은 margin이 아니라 gap으로 준다.
  • 줄면 안 되는 요소에는 flex-shrink: 0(또는 flex: none)을 준다.

자주 묻는 질문#

Q. flex: 1과 flex-grow: 1은 무엇이 다른가요?

A. flex-grow: 1flex-basis를 기본값 auto로 남겨 둬서, 각 아이템이 내용 크기에서 출발해 남는 공간만 나눠 갖습니다. flex: 11 1 0으로 풀려 출발 크기가 0이 되므로, 내용 길이와 상관없이 아이템들의 최종 폭이 같아집니다. "칸을 똑같이 나누고 싶다"면 flex: 1입니다.

Q. flex-basis: 0과 auto는 어떻게 다른가요?

A. auto는 아이템의 내용(또는 width) 크기를 출발점으로 삼고, 0은 내용 크기를 무시하고 0에서 출발합니다. 그래서 auto는 내용이 많은 쪽이 더 넓게 남고, 0은 grow 비율대로만 폭이 정해집니다.

Q. 아이템이 한 줄을 넘치면 어떻게 다음 줄로 내릴 수 있나요?

A. flex 컨테이너의 기본값은 flex-wrap: nowrap이라 줄바꿈 없이 아이템이 줄어들기만 합니다. 컨테이너에 flex-wrap: wrap을 주면 폭이 모자랄 때 아이템이 다음 줄로 넘어갑니다. 카드 목록처럼 줄이 바뀌어야 하는 레이아웃에서 자주 씁니다.

다음 시간 예고#

내일은 Grid 기초 — display:grid와 grid-template-columns의 첫 만남을 다룬다. 한 줄(또는 한 열)을 다루는 Flexbox와 달리, 가로·세로를 동시에 격자로 나누는 도구를 처음 만나 본다.

더 알아보기#