본문 바로가기

Flexbox 레이아웃

Flex 컨테이너의 주축·교차축과 정렬·줄바꿈 속성을 사용해 내비게이션과 카드의 1차원 배치를 만듭니다.

앞 글에서는 박스 모델과 display로 요소의 기본 배치를 정하는 방법을 익혔습니다. block, inline, inline-block은 간단한 배치에는 충분하지만, 요소들을 가운데에 맞추거나 남는 공간을 고르게 나누거나 화면 크기에 따라 다시 배치하기는 어렵습니다. 이런 일을 위해 만든 것이 Flexbox(Flexible Box Layout)입니다.

Flexbox는 요소를 한 줄이나 한 열로 놓는 1차원 레이아웃을 위한 CSS 기능입니다. 부모 요소 안의 자식 요소들을 정렬하고, 공간을 나누고, 순서를 바꾸는 일을 몇 가지 속성으로 할 수 있습니다. 이 글에서는 Flexbox의 기본 개념과 주요 속성을 익힌 뒤, 내비게이션 바와 상품 카드 목록을 만들어 봅니다.

Flexbox 축과 남는 공간 분배 — 상태 추적

줄 형성 뒤 각 줄의 grow/scaled-shrink와 정렬을 적용한다. 같은 아이템/컨테이너의 남은 공간·각 크기·정렬 여백이 바뀌는 계산을 구분할 이득이 있다.

Flex는 줄을 만든 뒤 각 줄의 공간을 나눕니다

FLEX LAYOUT

Flex는 줄을 만든 뒤 각 줄의 공간을 나눕니다

주축·교차축과 기본 크기를 정하고 gap·wrap을 반영해 줄을 만듭니다. 크기를 해소한 다음 남은 공간을 정렬합니다.

Flex의 줄 형성과 공간 분배 단계축, 기본 크기, gap을 포함한 줄 형성, 각 줄의 grow/scaled-shrink 크기 해소, 주축 정렬과 교차축 정렬의 여섯 노드와 다섯 방향이다. min/max와 order 조건은 아래 설명을 함께 읽는다. 화면이 좁아져도 단계와 연결은 같다.
1 · 축direction · writing mode
연결 관계1 · 축 · direction · writing mode → 2 · 기본 크기 · basis · 콘텐츠 · min/max
2 · 기본 크기basis · 콘텐츠 · min/max
연결 관계2 · 기본 크기 · basis · 콘텐츠 · min/max → 3 · 줄 형성 · wrap · 바깥 크기 · gap
3 · 줄 형성wrap · 바깥 크기 · gap
연결 관계3 · 줄 형성 · wrap · 바깥 크기 · gap → 4 · 줄 안의 크기 · grow · scaled shrink
4 · 줄 안의 크기grow · scaled shrink
연결 관계4 · 줄 안의 크기 · grow · scaled shrink → 5 · 주축 정렬 · auto margin · justify
5 · 주축 정렬auto margin · justify
연결 관계5 · 주축 정렬 · auto margin · justify → 6 · 교차축 정렬 · items/self · content
6 · 교차축 정렬items/self · content

줄 형성에서는 order로 정한 시각 순서를 사용하며 기본 order: 0에서는 DOM 순서입니다. 부족한 공간은 shrink×기본 크기로 나누되 min/max가 최종 크기를 제한합니다. 주축의 auto margin은 남은 공간을 먼저 흡수합니다. align-items/self는 아이템, align-content는 wrap/wrap-reverse 컨테이너의 줄들을 정렬하며 실제 한 줄이어도 적용될 수 있습니다.

gap과 줄 형성을 먼저, 공간 분배는 각 줄 안에서

gap은 줄 형성과 가용 공간 계산에 반영합니다. grow·shrink는 min/max 제약을 따릅니다. order가 있으면 시각 순서를 따라 줄을 만들지만 DOM·기본 focus·스크린 리더 순서를 바꾸지는 않습니다.

align-content는 nowrap 컨테이너에는 적용되지 않습니다. wrap/wrap-reverse에서는 실제 줄 수가 하나여도 영향을 줄 수 있습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
줄 구성현재: line별 아이템 / free space: 아직 분배 전컨테이너와 아이템 basis로 line을 구성한다flex는 line별로 크기를 분배한다
공간 분배grow: 양의 공간 / shrink: basis에 따른 가중치남는 공간은 grow, 부족한 공간은 scaled shrink로 나눈다shrink factor만으로 크기를 같은 비율로 줄이지 않는다
배치아이템: 최종 주축 크기 / 정렬: 남은 공간의 위치제약을 적용한 최종 크기로 정렬한다크기 분배와 min/max 제약 해소 뒤 남은 공간을 auto margin과 정렬이 사용해 아이템의 위치를 결정한다

Flexbox 축과 남는 공간 분배 — 상태 추적

줄 형성 뒤 각 줄의 grow/scaled-shrink와 정렬을 적용한다. 같은 아이템/컨테이너의 남은 공간·각 크기·정렬 여백이 바뀌는 계산을 구분할 이득이 있다.

Flex는 줄을 만든 뒤 각 줄의 공간을 나눕니다

FLEX LAYOUT

Flex는 줄을 만든 뒤 각 줄의 공간을 나눕니다

주축·교차축과 기본 크기를 정하고 gap·wrap을 반영해 줄을 만듭니다. 크기를 해소한 다음 남은 공간을 정렬합니다.

Flex의 줄 형성과 공간 분배 단계축, 기본 크기, gap을 포함한 줄 형성, 각 줄의 grow/scaled-shrink 크기 해소, 주축 정렬과 교차축 정렬의 여섯 노드와 다섯 방향이다. min/max와 order 조건은 아래 설명을 함께 읽는다. 화면이 좁아져도 단계와 연결은 같다.
1 · 축direction · writing mode
연결 관계1 · 축 · direction · writing mode → 2 · 기본 크기 · basis · 콘텐츠 · min/max
2 · 기본 크기basis · 콘텐츠 · min/max
연결 관계2 · 기본 크기 · basis · 콘텐츠 · min/max → 3 · 줄 형성 · wrap · 바깥 크기 · gap
3 · 줄 형성wrap · 바깥 크기 · gap
연결 관계3 · 줄 형성 · wrap · 바깥 크기 · gap → 4 · 줄 안의 크기 · grow · scaled shrink
4 · 줄 안의 크기grow · scaled shrink
연결 관계4 · 줄 안의 크기 · grow · scaled shrink → 5 · 주축 정렬 · auto margin · justify
5 · 주축 정렬auto margin · justify
연결 관계5 · 주축 정렬 · auto margin · justify → 6 · 교차축 정렬 · items/self · content
6 · 교차축 정렬items/self · content

줄 형성에서는 order로 정한 시각 순서를 사용하며 기본 order: 0에서는 DOM 순서입니다. 부족한 공간은 shrink×기본 크기로 나누되 min/max가 최종 크기를 제한합니다. 주축의 auto margin은 남은 공간을 먼저 흡수합니다. align-items/self는 아이템, align-content는 wrap/wrap-reverse 컨테이너의 줄들을 정렬하며 실제 한 줄이어도 적용될 수 있습니다.

gap과 줄 형성을 먼저, 공간 분배는 각 줄 안에서

gap은 줄 형성과 가용 공간 계산에 반영합니다. grow·shrink는 min/max 제약을 따릅니다. order가 있으면 시각 순서를 따라 줄을 만들지만 DOM·기본 focus·스크린 리더 순서를 바꾸지는 않습니다.

align-content는 nowrap 컨테이너에는 적용되지 않습니다. wrap/wrap-reverse에서는 실제 줄 수가 하나여도 영향을 줄 수 있습니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
줄 구성현재: line별 아이템 / free space: 아직 분배 전컨테이너와 아이템 basis로 line을 구성한다flex는 line별로 크기를 분배한다
공간 분배grow: 양의 공간 / shrink: basis에 따른 가중치남는 공간은 grow, 부족한 공간은 scaled shrink로 나눈다shrink factor만으로 크기를 같은 비율로 줄이지 않는다
배치아이템: 최종 주축 크기 / 정렬: 남은 공간의 위치제약을 적용한 최종 크기로 정렬한다크기 분배와 min/max 제약 해소 뒤 남은 공간을 auto margin과 정렬이 사용해 아이템의 위치를 결정한다

위 그림은 브라우저가 Flexbox의 배치를 정하는 여섯 단계를 보여 줍니다. 먼저 flex-direction과 글쓰기 방향으로 주축을 정하고, 각 아이템의 기본 크기를 flex-basis, 내용, 최소와 최대 크기로 정합니다. 그다음 flex-wrap과 gap을 반영해 아이템을 줄로 나누고, 줄마다 flex-grow와 flex-shrink로 남거나 모자란 공간을 나눠 크기를 정합니다. 마지막으로 남은 공간에서 주축 정렬과 교차축 정렬을 합니다.

그림 아래의 설명처럼 공간이 모자랄 때는 flex-shrink에 기본 크기를 곱한 값의 비율로 줄이고, 최소와 최대 크기가 최종 크기를 제한합니다. 주축의 margin: auto는 정렬보다 먼저 남은 공간을 가져갑니다. order로 시각적 순서를 바꿔도 DOM의 순서, 키보드 초점의 순서, 스크린 리더가 읽는 순서는 바뀌지 않습니다.


컨테이너와 아이템

Flexbox는 부모와 자식의 관계로 동작합니다. display: flex나 display: inline-flex를 준 부모 요소가 Flex 컨테이너(flex container)가 되고, 그 바로 아래 자식 요소들이 Flex 아이템(flex item)이 됩니다. 손자 요소는 아이템이 아닙니다. 컨테이너에 주는 속성은 아이템 전체의 배치와 정렬을, 아이템에 주는 속성은 그 아이템 하나의 크기와 정렬을 정합니다.

Flexbox에는 두 개의 축이 있습니다. 주축(main axis)은 아이템이 나열되는 방향이고, 교차축(cross axis)은 주축에 수직인 방향입니다. 이 교재처럼 가로로 쓰고 왼쪽에서 오른쪽으로 읽는 문서에서, 기본값인 row는 주축이 가로, 교차축이 세로입니다. column으로 바꾸면 주축이 세로, 교차축이 가로가 됩니다. 정렬 속성은 축을 기준으로 동작하므로, 어느 축이 주축인지부터 확인해야 합니다.


컨테이너 속성

Flex 컨테이너 만들기: display

display: flex는 블록 수준의 Flex 컨테이너를 만듭니다. 컨테이너 자체는 블록처럼 새 줄에서 부모의 폭을 차지하며, 가장 많이 씁니다. display: inline-flex는 인라인 수준의 Flex 컨테이너를 만들어, 컨테이너 자체가 글의 흐름을 따라 옆으로 놓입니다.

주축 방향: flex-direction

flex-direction은 주축의 방향을 정합니다. 기본값인 row는 아이템을 글의 진행 방향대로 가로로, row-reverse는 그 반대로 놓습니다. column은 위에서 아래로 세로로, column-reverse는 그 반대로 놓습니다.

.container {
    display: flex;
    flex-direction: column; /* 아이템들을 세로로 쌓음 */
}

줄 바꿈: flex-wrap

flex-wrap은 아이템이 한 줄에 다 들어가지 않을 때 줄을 바꿀지 정합니다. 기본값인 nowrap은 모든 아이템을 한 줄에 놓으며, 그래서 아이템이 줄어들 수 있습니다. wrap은 넘치는 아이템을 다음 줄로 보내고, wrap-reverse는 줄을 반대 방향으로 쌓습니다.

.container {
    display: flex;
    flex-wrap: wrap; /* 아이템이 넘치면 자동으로 줄바꿈 */
}

주축 정렬: justify-content

justify-content는 주축 방향의 정렬과 남는 공간의 분배를 정합니다.

  • flex-start: 주축의 시작 쪽으로 모읍니다. 기본값인 normal도 Flexbox에서는 이렇게 동작합니다.
  • flex-end: 주축의 끝 쪽으로 모읍니다.
  • center: 주축의 가운데로 모읍니다.
  • space-between: 첫 아이템과 마지막 아이템을 양 끝에 붙이고, 남는 공간을 아이템 사이에 똑같이 나눕니다.
  • space-around: 각 아이템의 양옆에 같은 공간을 둡니다. 그래서 양 끝의 공간은 아이템 사이 공간의 절반입니다.
  • space-evenly: 양 끝과 아이템 사이의 모든 공간을 똑같이 나눕니다.
.container {
    display: flex;
    justify-content: center; /* 아이템들을 가로 중앙 정렬 */
}

교차축 정렬: align-items

align-items는 교차축 방향으로 아이템을 정렬합니다.

  • stretch: 기본값입니다. 교차축 크기가 auto이고 교차축의 margin이 auto가 아닌 아이템을 줄의 교차축 크기만큼 늘립니다. row에서는 높이가, column에서는 너비가 늘어나며, 최소와 최대 크기의 제한을 받습니다.
  • flex-start, flex-end, center: 교차축의 시작, 끝, 가운데에 놓습니다.
  • baseline: 아이템 안의 첫 줄 글자 기준선(baseline)을 맞춥니다.
.container {
    display: flex;
    align-items: center; /* 아이템들을 세로 중앙 정렬 */
    height: 200px; /* align-items를 보려면 컨테이너에 높이 지정 필요 */
}

row인 컨테이너의 높이가 내용과 같으면 세로로 움직일 공간이 없어 정렬의 차이가 보이지 않으므로, 이 예제는 컨테이너에 높이를 줬습니다.

여러 줄의 정렬: align-content

align-content는 flex-wrap: wrap인 컨테이너에서 여러 줄 자체를 교차축 방향으로 정렬합니다. nowrap인 컨테이너에는 아무 효과가 없고, wrap인 컨테이너는 아이템이 실제로 한 줄에 다 들어가도 정렬의 대상이 됩니다. 차이를 보려면 교차축에 남는 공간이 있어야 합니다. 값은 stretch(남는 공간만큼 각 줄을 늘림), flex-start, flex-end, center, space-between, space-around이며 뜻은 justify-content와 비슷합니다.

.container {
    display: flex;
    flex-wrap: wrap;
    align-content: space-around; /* 여러 줄을 교차축 방향으로 분배 */
    height: 400px; /* align-content를 보려면 컨테이너에 충분한 높이 필요 */
}

위 예에서 height: 400px를 준 것은 교차축인 세로 방향에 남는 공간을 만들기 위해서입니다. 높이를 정하지 않아 컨테이너가 줄들의 높이에 딱 맞게 정해지면 나눌 공간이 없으므로, align-content에 어떤 값을 줘도 모습이 같습니다.

한 줄로 쓰기: flex-flow

flex-flow는 flex-direction과 flex-wrap을 한 번에 적는 단축 속성입니다. 값은 방향과 줄 바꿈 방식을 공백으로 구분해 적습니다.

.container {
    flex-flow: row wrap; /* flex-direction: row; flex-wrap: wrap; 과 동일 */
}

두 값은 순서를 바꿔 적어도 되고, 하나만 적으면 나머지는 초기값인 row나 nowrap이 됩니다. 다른 컨테이너 속성과 마찬가지로 display: flex인 요소에서만 효과가 있습니다.


아이템 속성

순서: order

order는 아이템이 보이는 순서를 바꿉니다. 기본값은 0이고, 값이 작을수록 먼저 보이며, 값이 같으면 HTML의 순서를 따릅니다. 하지만 보이는 순서만 바뀔 뿐 DOM의 순서와 키보드 초점, 스크린 리더의 읽기 순서는 그대로이므로, 의미가 있는 순서는 HTML에서 먼저 정합니다.

.item-a { order: 2; }
.item-b { order: 1; } /* item-b가 item-a보다 먼저 표시됨 */

늘어나는 비율: flex-grow

flex-grow는 컨테이너에 공간이 남을 때 그 공간을 아이템이 어떤 비율로 나눠 가질지 정합니다. 기본값 0은 늘어나지 않는다는 뜻입니다.

.item-1 { flex-grow: 1; } /* 남는 공간을 1의 비율로 가져감 */
.item-2 { flex-grow: 2; } /* 남는 공간을 2의 비율로 가져감 (item-1의 2배) */

이 예에서 남는 공간이 90px이면 item-1은 30px, item-2는 60px을 더 가집니다. 아이템 전체의 크기가 1 대 2가 되는 것이 아니라, 남는 공간을 1 대 2로 나눈다는 점에 주의합니다.

줄어드는 비율: flex-shrink

flex-shrink는 아이템들이 컨테이너보다 클 때 어떤 비율로 줄어들지 정합니다. 기본값은 1이라 공간이 모자라면 줄어들고, 0으로 두면 줄어들지 않습니다.

.item-1 { flex-shrink: 0; } /* 공간이 부족해도 줄어들지 않음 */
.item-2 { flex-shrink: 1; } /* 기본 비율로 줄어듦 */

줄어드는 양은 flex-shrink 값만이 아니라 flex-shrink × 기본 크기의 비율로 나눕니다. 그래서 flex-shrink가 같아도 기본 크기가 큰 아이템이 더 많이 줄어듭니다. 최소와 최대 크기 제한이 있으면 이렇게 나눈 결과가 다시 조정될 수 있습니다. 자세한 계산은 MDN의 주축 비율 조절 설명을 참고하세요.

더 깊이: 아이템이 내용보다 작아지지 않는 이유

flex-shrink가 1이어도 아이템이 끝까지 줄어들지 않을 때가 있습니다. 긴 URL이나 띄어쓰기 없는 영문 단어, 큰 이미지가 든 아이템은 컨테이너가 좁아져도 그 내용의 폭 아래로 줄지 않고 바깥으로 넘칩니다. 원인은 min-width의 초기값인 auto입니다. 일반 블록에서 auto는 사실상 0이지만, Flexbox 명세는 Flex 아이템의 주축 최소 크기가 auto이면 그 아이템의 내용에 기반한 최소 크기(content-based minimum size)를 쓰도록 정합니다. 대체로 내용을 더 줄 바꿈할 수 없을 때의 폭인 최소 내용 크기(min-content size)에 가깝습니다.

명세는 이 규칙을 Flex 아이템에 더 합리적인 기본 최소 크기를 주기 위한 것이라고 설명하며, 덕분에 아이템이 내용보다 좁아져 내용이 옆 아이템 위로 넘치는 일이 기본으로 줄어듭니다. 하지만 말줄임표로 줄여 보여 줄 제목이나 가로로 스크롤해야 하는 코드 블록처럼, 내용보다 작아져야 하는 아이템에서는 이 기본값이 방해가 됩니다. 이때는 아이템에 min-width: 0을 주어 최소 크기를 직접 0으로 정하고, 넘치는 내용은 overflow나 text-overflow로 처리합니다. flex-direction: column이라면 같은 일을 min-height: 0이 합니다.

아이템의 overflow를 hidden, auto, scroll 가운데 하나로 정해 스크롤 컨테이너가 된 경우에는 내용 기반 최소 크기 대신 0이 쓰이므로 따로 정하지 않아도 됩니다. 정확한 규칙은 Flexbox 명세의 자동 최소 크기 절에 있습니다.

기본 크기: flex-basis

flex-basis는 늘이거나 줄이기 전에 아이템이 주축 방향으로 가질 기본 크기입니다. row에서는 너비, column에서는 높이에 해당합니다. 기본값인 auto는 지정한 width나 height를, 그것도 없으면 내용의 크기를 씁니다. px, %, em 같은 단위로 직접 정할 수도 있습니다.

.item-1 { flex-basis: 100px; } /* 기본 너비 100px */
.item-2 { flex-basis: 30%; } /* 기본 너비 컨테이너의 30% */

30%는 컨테이너의 주축 크기를 기준으로 계산됩니다. 두 값 모두 출발점일 뿐이어서, 실제 크기는 앞에서 본 flex-grow와 flex-shrink에 따라 이보다 커지거나 작아질 수 있습니다.

한 줄로 쓰기: flex

flex는 flex-grow, flex-shrink, flex-basis를 이 순서로 한 번에 적는 단축 속성입니다. 다음 아이템은 200px에서 출발해, 공간이 남으면 다른 아이템과 같은 비율로 늘어나고 모자라면 같은 비율로 줄어듭니다.

.item {
    flex: 1 1 200px; /* 늘어나는 비율 1, 줄어드는 비율 1, 기본 크기 200px */
}

자주 쓰는 형태는 다음과 같습니다.

  • flex: 1: 늘어나는 비율 1, 줄어드는 비율 1, 기본 크기 0으로, 모든 아이템이 공간을 똑같이 나눠 가집니다. 기본 크기를 정확히 0px로 고정하고 싶다면 flex: 1 1 0px처럼 세 값을 모두 적습니다.
  • flex: auto: flex: 1 1 auto와 같습니다. 내용 크기에서 시작해 늘거나 줄어듭니다.
  • flex: none: flex: 0 0 auto와 같습니다. 늘어나지도 줄어들지도 않습니다.

아이템 하나의 교차축 정렬: align-self

align-self는 아이템 하나의 교차축 정렬을 정하며, 컨테이너의 align-items보다 우선합니다. 기본값인 auto는 컨테이너의 align-items를 따르고, 나머지 값은 align-items와 같습니다.

.item-special {
    align-self: flex-end; /* 해당 아이템만 교차축 끝에 정렬 */
}

실습: 내비게이션 바와 상품 카드

Flexbox로 흔히 쓰는 두 가지 레이아웃인 내비게이션 바와 카드 목록을 만들어 봅니다. 다음 구조로 파일을 준비합니다.

flexbox_layout.html
flex.css

flexbox_layout.html에는 다음 코드를 씁니다.

flexbox_layout.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flexbox 레이아웃 실습</title>
    <link rel="stylesheet" href="css/flex.css">
</head>
<body>
    <header class="navbar">
        <div class="logo">MyBrand</div>
        <nav class="nav-menu">
            <a href="#" class="nav-item">홈</a>
            <a href="#" class="nav-item">제품</a>
            <a href="#" class="nav-item">서비스</a>
            <a href="#" class="nav-item special-item">문의하기</a>
        </nav>
    </header>

    <main class="main-content">
        <h1>Flexbox로 만드는 유연한 레이아웃</h1>

        <section class="card-section">
            <h2>인기 상품</h2>
            <div class="product-cards">
                <div class="card">
                    <h3>제품명 1</h3>
                    <p>이 제품은 뛰어난 품질과 디자인을 자랑합니다.</p>
                    <span class="price">49,000원</span>
                </div>
                <div class="card">
                    <h3>제품명 2</h3>
                    <p>일상에 편리함을 더해주는 필수 아이템입니다.</p>
                    <span class="price">75,000원</span>
                </div>
                <div class="card">
                    <h3>제품명 3</h3>
                    <p>놀라운 기능과 합리적인 가격으로 만나보세요.</p>
                    <span class="price">120,000원</span>
                </div>
                <div class="card">
                    <h3>제품명 4</h3>
                    <p>새로운 경험을 선사할 프리미엄 제품.</p>
                    <span class="price">99,000원</span>
                </div>
                <div class="card wide-card">
                    <h3>특별 할인 제품</h3>
                    <p>지금 구매하시면 특별한 혜택이 기다리고 있습니다! 놓치지 마세요.</p>
                    <span class="price">150,000원</span>
                </div>
            </div>
        </section>
    </main>

    <footer>
        <p>&copy; 2026 Flexbox 연습. 모든 권리 보유.</p>
    </footer>
</body>
</html>

이 HTML에서 Flex 컨테이너가 될 요소는 .navbar, .nav-menu, .product-cards, 그리고 각 .card입니다. 그 바로 아래의 로고와 메뉴, 링크, 카드, 카드 안의 제목과 설명과 가격이 각 컨테이너의 아이템이 됩니다.

css/flex.css에는 다음 CSS를 씁니다. 번호를 붙인 주석이 내비게이션 바와 상품 카드 부분을 나누므로, 각 부분에서 어느 요소에 display: flex를 주는지 먼저 찾아보세요. 맨 아래의 @media 규칙은 화면 너비가 768px 이하일 때만 적용되어 가로 배치를 세로로 바꿉니다.

css/flex.css
/* 기본 스타일 및 박스 사이징 */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f4f7f6;
    color: #333;
    line-height: 1.6;
}

/* 1. 내비게이션 바 (Flex 컨테이너) */
.navbar {
    display: flex; /* Flexbox 활성화 */
    justify-content: space-between; /* 로고와 메뉴를 양 끝으로 정렬 */
    align-items: center; /* 수직 중앙 정렬 */
    background-color: #2c3e50;
    color: white;
    padding: 15px 30px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}

.navbar .logo {
    font-size: 1.8em;
    font-weight: bold;
    color: #f1c40f; /* 로고 색상 */
}

.navbar .nav-menu {
    display: flex; /* 메뉴 아이템들을 Flexbox로 정렬 */
    gap: 25px; /* 메뉴 아이템들 사이 간격 */
}

.nav-item {
    color: white;
    text-decoration: none;
    font-size: 1.1em;
    padding: 5px 0;
    transition: color 0.3s ease;
}

.nav-item:hover {
    color: #f1c40f;
}

/* Flex 아이템 개별 정렬 예시 */
.special-item {
    background-color: #e67e22; /* 주황색 버튼처럼 */
    padding: 8px 15px;
    border-radius: 5px;
    font-weight: bold;
    transition: background-color 0.3s ease;
    align-self: center; /* 해당 아이템만 수직 중앙 정렬 */
}

.special-item:hover {
    color: white; /* 앞의 .nav-item:hover와 명시도가 같아 나중에 쓴 이 규칙이 이김 */
    background-color: #d35400;
}

/* 메인 콘텐츠 */
.main-content {
    max-width: 1200px;
    margin: 40px auto;
    padding: 0 20px;
}

.main-content h1 {
    text-align: center;
    color: #3498db;
    margin-bottom: 40px;
    font-size: 2.5em;
}

/* 2. 상품 카드 섹션 (Flex 컨테이너) */
.card-section h2 {
    text-align: center;
    color: #2c3e50;
    margin-bottom: 30px;
    font-size: 2em;
}

.product-cards {
    display: flex; /* Flexbox 활성화 */
    flex-wrap: wrap; /* 공간 부족 시 줄바꿈 */
    justify-content: center; /* 카드들을 가로 중앙 정렬 */
    gap: 25px; /* 카드들 사이 간격 */
}

.card {
    background-color: white;
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 20px;
    width: 280px; /* 각 카드의 고정 너비 */
    text-align: center;
    box-shadow: 0 2px 10px rgba(0,0,0,0.08);
    transition: transform 0.2s ease-in-out;
    display: flex; /* 카드 내부의 콘텐츠도 Flexbox로 배치 */
    flex-direction: column; /* 카드 내부 콘텐츠를 세로로 쌓음 */
    justify-content: space-between; /* 카드 콘텐츠 위아래로 분배 */
}

.card:hover {
    transform: translateY(-8px);
}

.card h3 {
    font-size: 1.4em;
    color: #2980b9;
    margin-bottom: 10px;
}

.card p {
    font-size: 0.95em;
    color: #555;
    margin-bottom: 15px;
    flex-grow: 1; /* 단락이 남는 공간을 차지하여 늘어남 */
}

.card .price {
    font-size: 1.2em;
    font-weight: bold;
    color: #e67e22;
    margin-top: 10px; /* 가격 위에 마진 */
}

/* 특정 카드에 Flex 아이템 속성 적용 */
.wide-card {
    width: 600px; /* 더 넓은 카드 */
    flex-shrink: 0; /* 줄어들지 않도록 설정 */
}

/* 푸터 스타일 */
footer {
    background-color: #34495e;
    color: #ecf0f1;
    text-align: center;
    padding: 20px 0;
    margin-top: 50px;
    font-size: 0.9em;
}

/* 미디어 쿼리 (반응형: 화면 너비 768px 이하) */
@media (max-width: 768px) {
    .navbar {
        flex-direction: column; /* 내비게이션을 세로로 쌓음 */
        align-items: flex-start; /* 왼쪽 정렬 */
        padding: 15px 20px;
    }
    .navbar .nav-menu {
        flex-direction: column; /* 메뉴 아이템 세로 정렬 */
        gap: 10px;
        width: 100%; /* 너비 전체 차지 */
        margin-top: 15px;
    }
    .nav-item {
        width: 100%;
        text-align: center;
        padding: 10px 0;
        background-color: rgba(255, 255, 255, 0.1);
        border-radius: 5px;
    }
    .special-item {
        align-self: stretch; /* 전체 너비로 늘어남 */
    }
    .product-cards {
        flex-direction: column; /* 모바일에서는 카드들을 세로로 쌓음 */
        align-items: center; /* 교차축인 가로 방향으로 중앙 정렬 */
    }
    .card {
        width: 90%; /* 모바일에서 카드 너비 조정 */
    }
    .wide-card {
        width: 90%; /* 모바일에서 넓은 카드 너비 조정 */
    }
}

Live Server로 flexbox_layout.html을 엽니다. 내비게이션 바는 justify-content: space-between으로 로고를 왼쪽 끝에, 메뉴를 오른쪽 끝에 놓고, align-items: center로 높이가 다른 둘을 세로 가운데에 맞춥니다. 메뉴 안의 링크들은 다시 Flex 컨테이너가 된 .nav-menu 안에서 gap: 25px 간격으로 가로로 놓입니다.

상품 카드는 가로로 놓이다가, 화면이 좁아져 한 줄에 다 들어가지 않으면 flex-wrap: wrap으로 다음 줄로 넘어가며, 카드 사이의 25px 간격은 gap이 유지합니다. 카드 하나하나도 flex-direction: column인 Flex 컨테이너이고, 문단에 flex-grow: 1을 줬으므로 문단이 남는 높이를 채워, 같은 줄의 카드들은 설명의 길이가 달라도 가격이 맨 아래에 나란히 놓입니다.

문의하기 링크에 마우스를 올리면 주황색이 진해지고 글자는 흰색을 유지합니다. 앞의 .nav-item:hover가 글자를 노랗게 바꾸려 하지만, .special-item:hover도 명시도가 0-2-0으로 같고 나중에 나오므로 이 규칙이 이깁니다. !important 없이 규칙의 순서로 해결한 것입니다.

브라우저 창을 768px 이하로 줄이면 미디어 쿼리가 적용되어, 내비게이션 바와 메뉴가 세로로 쌓이고 카드도 한 열로 놓입니다.


정리

  • display: flex를 준 부모가 Flex 컨테이너, 그 바로 아래 자식이 Flex 아이템이 됩니다.
  • 주축은 아이템이 나열되는 방향, 교차축은 그에 수직인 방향이며, flex-direction이 row인지 column인지에 따라 바뀝니다.
  • justify-content는 주축, align-items는 교차축, align-content는 wrap인 컨테이너의 여러 줄을 정렬합니다.
  • flex-grow는 남는 공간을 비율대로 나누고, flex-shrink는 flex-shrink × 기본 크기의 비율로 모자란 공간을 줄입니다.
  • flex-basis는 늘이고 줄이기 전의 기본 크기이며, flex 단축 속성으로 세 값을 한 번에 적습니다.
  • order는 보이는 순서만 바꾸므로, 읽기와 초점의 순서는 HTML에서 정합니다.

연습 문제

1. 남는 공간과 모자란 공간

기본 크기가 각각 100px과 200px인 두 아이템이 한 줄에 있습니다. 두 아이템 모두 flex-grow와 flex-shrink가 1이고, gap, 안쪽 여백, 테두리, 바깥 여백은 0이며, 최소와 최대 크기 제한도 없습니다. 컨테이너가 360px일 때와 240px일 때 두 아이템의 최종 크기를 구해 보세요.

해설 보기

360px에서는 기본 크기의 합 300px을 빼고 남는 60px을 flex-grow 1 대 1로 나눠 30px씩 더하므로, 130px과 230px이 됩니다.

240px에서는 60px이 모자랍니다. 줄이는 비율은 flex-shrink × 기본 크기라 1 × 100 대 1 × 200, 즉 1 대 2이므로 각각 20px과 40px을 줄여 80px과 160px이 됩니다. 두 값이 모두 1이라고 똑같이 30px씩 줄이는 것이 아닙니다.

2. 좁은 화면에서 축 고르기

768px 이하에서 .product-cards는 flex-direction: column입니다. 이때 카드를 가로 가운데에 놓는 속성은 justify-content와 align-items 중 무엇일까요?

해설 보기

align-items입니다. column에서는 주축이 세로, 교차축이 가로이므로, 가로 정렬은 교차축 정렬인 align-items: center가 맡습니다. 실습 코드도 그렇게 썼습니다.

여기서 justify-content: center를 쓰면 세로 방향으로 카드를 모으려 할 뿐이고, 컨테이너의 높이가 내용과 같아 아무 변화도 보이지 않습니다.

3. 넓은 카드는 두 칸일까

.wide-card의 너비 600px은 일반 카드 두 개와 그 사이 간격을 합친 280 + 280 + 25 = 585px과 같을까요? Flexbox에서 넓은 카드가 정확히 두 칸을 차지하게 할 수 있을까요?

해설 보기

같지 않습니다. 600px은 585px보다 15px 넓으므로, 일반 카드 두 개가 들어가는 줄에 넓은 카드를 놓으면 위아래 카드의 가장자리가 맞지 않습니다.

Flexbox는 줄마다 아이템을 따로 배치할 뿐, 여러 줄이 함께 쓰는 열 칸을 만들지 않습니다. 여러 줄에 걸쳐 칸을 맞추고 아이템이 두 칸을 차지하게 하려면, 다음 글에서 배울 Grid가 알맞습니다.

다음 글 Grid 레이아웃에서는 행과 열을 함께 다루는 2차원 레이아웃인 CSS Grid를 다룹니다.