CSS Flexbox 입문: flex-direction, justify-content, align-items로 레이아웃 잡기
이 글의 핵심
Flexbox의 주축과 교차축 개념, 방향 설정, 정렬 속성, 줄바꿈, flex-grow·shrink·basis를 예제로 익혀 자주 쓰는 레이아웃을 만드는 방법을 정리합니다.
Flexbox는 한 방향(가로 또는 세로)으로 아이템을 늘어놓고, 남는 공간이나 모자란 공간을 어떻게 나눌지를 정하는 CSS 레이아웃 모듈입니다. 예전에는 float와 퍼센트 폭, 클리어픽스로 흉내 내던 가로 배치와 세로 중앙 정렬을 몇 줄로 끝낼 수 있습니다.
/* float 시절 */
.item { float: left; width: 33.333%; }
.row::after { content: ""; display: table; clear: both; }
/* Flexbox */
.row { display: flex; gap: 1rem; }
.item { flex: 1; }
이 글은 시리즈 5편으로, 앞 편의 박스 모델을 안다는 전제에서 속성을 하나씩 익힌 뒤, 뒤의 폭 계산 순서 절에서 브라우저가 실제로 아이템 폭을 계산하는 순서를 따라가 봅니다. “flex: 1을 줬는데 왜 폭이 안 줄지?”, “flex-basis와 width 중 뭐가 이기지?” 같은 질문은 대부분 이 계산 순서를 알면 풀립니다. 두 방향 격자가 필요하면 다음 편 Grid가 더 맞습니다.
컨테이너·아이템과 주축·교차축
컨테이너와 아이템
Flexbox는 컨테이너(Container)와 아이템(Item) 두 가지 요소로 구성됩니다.
<div class="container"> <!-- Flex Container -->
<div class="item">1</div> <!-- Flex Item -->
<div class="item">2</div>
<div class="item">3</div>
</div>
.container {
display: flex; /* Flexbox 활성화 */
}
주축(Main Axis)과 교차축(Cross Axis)
Flexbox는 두 개의 축을 기준으로 동작합니다:
flex-direction: row (기본값)
┌─────────────────────────────────┐
│ Main Axis (주축) → │
│ ┌───┐ ┌───┐ ┌───┐ │
│ │ 1 │ │ 2 │ │ 3 │ ↑ Cross │
│ └───┘ └───┘ └───┘ ↓ Axis │
│ (교차축) │
└─────────────────────────────────┘
flex-direction: column
┌─────────────────────┐
│ ← Cross Axis │
│ ┌───────────┐ │
│ │ 1 │ ↓ │
│ └───────────┘ │
│ ┌───────────┐ Main│
│ │ 2 │ Axis│
│ └───────────┘ (주축)│
│ ┌───────────┐ ↓ │
│ │ 3 │ │
│ └───────────┘ │
└─────────────────────┘
flex-direction: 방향
방향 설정
.container {
display: flex;
/* 가로 (기본값) */
flex-direction: row;
/* 가로 역방향 */
flex-direction: row-reverse;
/* 세로 */
flex-direction: column;
/* 세로 역방향 */
flex-direction: column-reverse;
}
실전 예제
<!DOCTYPE html>
<html lang="ko">
<head>
<style>
.demo {
display: flex;
gap: 10px;
padding: 20px;
border: 2px solid #ddd;
margin-bottom: 20px;
}
.item {
background: #3498db;
color: white;
padding: 20px;
text-align: center;
min-width: 80px;
}
.row { flex-direction: row; }
.row-reverse { flex-direction: row-reverse; }
.column { flex-direction: column; }
.column-reverse { flex-direction: column-reverse; }
</style>
</head>
<body>
<h3>row (기본값)</h3>
<div class="demo row">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<h3>row-reverse</h3>
<div class="demo row-reverse">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<h3>column</h3>
<div class="demo column">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
</body>
</html>
justify-content: 주축 정렬
정렬 옵션
주축(Main Axis) 방향의 정렬을 제어합니다.
.container {
display: flex;
/* 시작점 정렬 (기본값) */
justify-content: flex-start;
/* 끝점 정렬 */
justify-content: flex-end;
/* 중앙 정렬 */
justify-content: center;
/* 양끝 정렬 (사이 균등) */
justify-content: space-between;
/* 주위 균등 */
justify-content: space-around;
/* 완전 균등 */
justify-content: space-evenly;
}
시각적 비교
flex-start (기본값)
[1][2][3]
flex-end
[1][2][3]
center
[1][2][3]
space-between
[1] [2] [3]
space-around
[1] [2] [3]
space-evenly
[1] [2] [3]
실전 예제
<style>
.justify-demo {
display: flex;
gap: 10px;
padding: 20px;
border: 2px solid #ddd;
margin-bottom: 20px;
min-height: 100px;
}
.item {
background: #e74c3c;
color: white;
padding: 20px;
min-width: 80px;
text-align: center;
}
</style>
<h3>space-between (양끝 정렬)</h3>
<div class="justify-demo" style="justify-content: space-between;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
<h3>center (중앙 정렬)</h3>
<div class="justify-demo" style="justify-content: center;">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
</div>
align-items: 교차축 정렬
정렬 옵션
교차축(Cross Axis) 방향의 정렬을 제어합니다.
.container {
display: flex;
height: 200px;
/* 늘림 (기본값) */
align-items: stretch;
/* 시작점 정렬 */
align-items: flex-start;
/* 끝점 정렬 */
align-items: flex-end;
/* 중앙 정렬 */
align-items: center;
/* 베이스라인 정렬 */
align-items: baseline;
}
시각적 비교
stretch (기본값)
┌─────────────┐
│ ┌─┐ ┌─┐ ┌─┐ │
│ │1│ │2│ │3│ │
│ └─┘ └─┘ └─┘ │
└─────────────┘
flex-start
┌─────────────┐
│ ┌─┐ ┌─┐ ┌─┐ │
│ │1│ │2│ │3│ │
│ └─┘ └─┘ └─┘ │
│ │
└─────────────┘
center
┌─────────────┐
│ │
│ ┌─┐ ┌─┐ ┌─┐ │
│ │1│ │2│ │3│ │
│ └─┘ └─┘ └─┘ │
│ │
└─────────────┘
flex-end
┌─────────────┐
│ │
│ ┌─┐ ┌─┐ ┌─┐ │
│ │1│ │2│ │3│ │
│ └─┘ └─┘ └─┘ │
└─────────────┘
완벽한 중앙 정렬
.center-box {
display: flex;
justify-content: center; /* 주축 중앙 */
align-items: center; /* 교차축 중앙 */
min-height: 100vh;
}
flex-wrap: 줄바꿈
줄바꿈 설정
.container {
display: flex;
/* 줄바꿈 안 함 (기본값) */
flex-wrap: nowrap;
/* 줄바꿈 */
flex-wrap: wrap;
/* 줄바꿈 역방향 */
flex-wrap: wrap-reverse;
}
실전 예제: 반응형 카드
<style>
.card-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 20px;
}
.card {
/* 최소 250px, 남은 공간 균등 분배 */
flex: 1 1 250px;
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card h3 {
margin: 0 0 10px 0;
color: #2c3e50;
}
.card p {
margin: 0;
color: #7f8c8d;
}
</style>
<div class="card-container">
<div class="card">
<h3>카드 1</h3>
<p>내용...</p>
</div>
<div class="card">
<h3>카드 2</h3>
<p>내용...</p>
</div>
<div class="card">
<h3>카드 3</h3>
<p>내용...</p>
</div>
<div class="card">
<h3>카드 4</h3>
<p>내용...</p>
</div>
</div>
동작 원리:
- 화면이 넓으면: 4개가 한 줄에
- 화면이 좁으면: 2개씩 또는 1개씩 줄바꿈
Align Content (여러 줄 정렬)
.container {
display: flex;
flex-wrap: wrap;
height: 400px;
/* 여러 줄의 정렬 */
align-content: flex-start;
align-content: flex-end;
align-content: center;
align-content: space-between;
align-content: space-around;
align-content: stretch; /* 기본값 */
}
아이템 속성: grow·shrink·basis·align-self·order
Flex Grow (늘어남)
남은 공간을 어떻게 분배할지 결정합니다.
.item {
/* 늘어나지 않음 (기본값) */
flex-grow: 0;
/* 남은 공간 채우기 */
flex-grow: 1;
/* 다른 아이템보다 2배 */
flex-grow: 2;
}
예제:
<style>
.grow-demo {
display: flex;
gap: 10px;
padding: 20px;
border: 2px solid #ddd;
}
.item-1 { flex-grow: 1; background: #3498db; }
.item-2 { flex-grow: 2; background: #e74c3c; }
.item-3 { flex-grow: 1; background: #2ecc71; }
.item-1, .item-2, .item-3 {
color: white;
padding: 20px;
text-align: center;
}
</style>
<div class="grow-demo">
<div class="item-1">flex-grow: 1</div>
<div class="item-2">flex-grow: 2 (2배 크기)</div>
<div class="item-3">flex-grow: 1</div>
</div>
남은 공간 400px 분배:
- 총 비율: 1 + 2 + 1 = 4
- 아이템 1: 400px × (1/4) = 100px
- 아이템 2: 400px × (2/4) = 200px
- 아이템 3: 400px × (1/4) = 100px
Flex Shrink (줄어듦)
공간이 부족할 때 어떻게 줄어들지 결정합니다.
.item {
/* 줄어들 수 있음 (기본값) */
flex-shrink: 1;
/* 줄어들지 않음 */
flex-shrink: 0;
/* 다른 아이템보다 2배 더 줄어듦 */
flex-shrink: 2;
}
예제:
<style>
.shrink-demo {
display: flex;
gap: 10px;
padding: 20px;
border: 2px solid #ddd;
width: 400px; /* 공간 부족 */
}
.item-fixed {
flex-shrink: 0; /* 고정 */
width: 150px;
background: #e74c3c;
}
.item-flexible {
flex-shrink: 1; /* 줄어듦 */
width: 200px;
background: #3498db;
}
.item-fixed, .item-flexible {
color: white;
padding: 20px;
text-align: center;
}
</style>
<div class="shrink-demo">
<div class="item-fixed">고정 (150px)</div>
<div class="item-flexible">유연 (줄어듦)</div>
</div>
Flex Basis (기본 크기)
아이템의 초기 크기를 설정합니다.
.item {
/* 자동 (width/height 사용) */
flex-basis: auto; /* 기본값 */
/* 고정 크기 */
flex-basis: 200px;
/* 퍼센트 */
flex-basis: 30%;
/* 내용 크기 */
flex-basis: content;
}
우선순위:
flex-basis > width > content
Flex 축약 속성
.item {
/* flex: grow shrink basis */
flex: 1; /* 1 1 0% (균등 분배) */
flex: auto; /* 1 1 auto (내용 기준) */
flex: none; /* 0 0 auto (고정) */
flex: 1 0 auto; /* 커스텀 */
flex: 0 0 200px; /* 고정 200px */
}
자주 사용하는 패턴:
/* 균등 분배 */
.item { flex: 1; }
/* 고정 크기 */
.sidebar { flex: 0 0 250px; }
/* 내용 기준 + 늘어남 */
.main { flex: auto; }
Align Self (개별 정렬)
개별 아이템의 교차축 정렬을 변경합니다.
.item {
/* 컨테이너의 align-items 무시 */
align-self: flex-start;
align-self: flex-end;
align-self: center;
align-self: stretch;
align-self: baseline;
}
예제:
<style>
.align-demo {
display: flex;
align-items: flex-start; /* 기본: 위쪽 정렬 */
height: 200px;
gap: 10px;
padding: 20px;
border: 2px solid #ddd;
}
.item { background: #3498db; color: white; padding: 20px; }
.item-center { align-self: center; background: #e74c3c; }
.item-end { align-self: flex-end; background: #2ecc71; }
</style>
<div class="align-demo">
<div class="item">flex-start</div>
<div class="item-center">center</div>
<div class="item-end">flex-end</div>
</div>
Order (순서 변경)
HTML 순서와 무관하게 아이템 순서를 변경합니다.
.item {
/* 기본값: 0 */
order: 0;
order: -1; /* 맨 앞 */
order: 1; /* 뒤로 */
order: 999; /* 맨 뒤 */
}
예제:
<style>
.order-demo {
display: flex;
gap: 10px;
padding: 20px;
border: 2px solid #ddd;
}
.item { background: #3498db; color: white; padding: 20px; }
.item-1 { order: 3; }
.item-2 { order: 1; }
.item-3 { order: 2; }
</style>
<div class="order-demo">
<div class="item item-1">HTML: 1 (order: 3)</div>
<div class="item item-2">HTML: 2 (order: 1)</div>
<div class="item item-3">HTML: 3 (order: 2)</div>
</div>
<!-- 화면 순서: 2 → 3 → 1 -->
한 단계 더: 브라우저가 폭을 계산하는 순서
문법만 알면 “왜 이 폭이 되지?” 같은 문제에 시간을 씁니다. 아래는 CSS Flexible Box Layout Module Level 1에서 정의하는 흐름을 실무자 관점으로 압축한 것입니다. 브라우저마다 부동소수·반올림으로 1px 차이는 날 수 있으나, 원인 분석과 DevTools 해석에는 충분합니다.
레이아웃 알고리즘과 가용 공간(available space) 분배
Flex 컨테이너는 플렉스 포맷팅 컨텍스트(flex formatting context) 를 만듭니다. 직계 자식만 플렉스 아이템이 되며, 주축(main axis)과 교차축(cross axis)이 정해진 뒤 레이아웃이 진행됩니다.
스펙이 말하는 큰 단계를 한 줄로 요약하면 다음과 같습니다.
- 플렉스 라인(flex line) 을 만듭니다.
flex-wrap에 따라 한 줄 또는 여러 줄이 됩니다. - 각 아이템에 대해 가상의 주축 크기(hypothetical main size) 를 계산합니다. 이때 출발점이 되는 값이
flex-basis와min-*/max-*클램프입니다. - 한 줄 안에서 주축 방향 남거나 모자라는 공간(여유 공간, free space) 을 구합니다.
- 여유가 양수이면
flex-grow비율로 나누고, 음수(넘침)이면flex-shrink에 따라 수축합니다. - 교차축 크기·정렬(
align-items,align-self,align-content)을 확정합니다.
여기서 중요한 직관은 justify-content는 “여유 공간이 있을 때” 그 공간을 어떻게 배치할지 다루고, grow/shrink는 “여유 공간의 부호와 크기” 자체를 아이템 폭에 반영한다는 점입니다. 따라서 flex: 1로 꽉 채운 줄에서 justify-content를 바꿔도 차이가 없는 경우가 많습니다(이미 여유가 0이므로).
/* 주축 여유가 있을 때만 justify-content가 눈에 띔 */
.container {
display: flex;
width: 600px;
gap: 0;
}
.item {
flex: 0 0 100px; /* 늘리지도 줄이도 않음 → 남는 공간 발생 */
}
flex-basis와 width·height: 무엇이 “기준 크기”인가
주축이 가로(flex-direction: row) 일 때, 아이템의 주축 크기 결정에 가장 직접적으로 관여하는 것은 flex-basis입니다. flex-basis: auto(초깃값)일 때는, 명시된 width가 있으면 그것이 flex base size에 사용되는 경우가 일반적입니다. 반대로 flex-basis에 200px처럼 길이를 주면, 주축 기준점은 그 값이 되고 width와 동시에 쓰면 디버깅이 어려워집니다.
정리하면 다음과 같습니다.
flex-basis: “늘리거나 줄이기 전” 주축의 기준점.auto | content | <length> | <percentage>등.width/height: 일반 블록 레이아웃에서의 크기이지만, 플렉스 아이템에서는 주축이 가로일 때width가 flex base size에 기여할 수 있습니다(flex-basis: auto전제).min-width/max-width: 최종적으로 클램프합니다. 특히min-width: auto는 기본적으로 콘텐츠 최소 크기를 지키려 해서,flex: 1인데도 텍스트가 줄바꿈되지 않는 전형적인 원인입니다.
/* 실무 권장: 한 축으로 의도를 몰아넣기 */
.sidebar {
flex: 0 0 240px; /* basis+grow+shrink를 한 번에 */
min-width: 0; /* 필요 시 오버플로/말줄임 허용 */
}
.main {
flex: 1 1 0%; /* 남는 공간을 메인이 우선 소비 */
min-width: 0;
}
퍼센트 flex-basis는 플렉스 컨테이너의 주축 가용 공간을 기준으로 해석됩니다(부모가 높이를 갖지 않은 column 레이아웃에서 예상과 다르게 보일 수 있으므로, 그때는 부모 높이·min-height를 먼저 확정합니다).
flex-shrink 계산: “얼마나 줄어들지”의 가중치
주축 한 줄에서 가상 주축 크기의 합 + gap이 컨테이너 안쪽 주축 길이를 초과하면, 브라우저는 넘치는 분(음의 여유 공간)을 아이템에 나누어 줍니다. 이때 각 아이템이 맡는 수축량은 단순히 flex-shrink 숫자만으로 결정되지 않으며, 가상 주축 크기와 함께 가중됩니다.
직관적인 근사식은 다음과 같습니다(교육용; 실제는 스펙 절차에 따라 후속 보정이 있습니다).
- 넘침 (O = \sum \text{hypothetical main size} + \text{gap} - \text{container inner main size}) (양수면 수축 필요).
- 각 아이템 (i)에 대해 가중치 (w_i = (\text{flex-shrink}_i) \times (\text{가상 주축 크기}_i)).
- 아이템 (i)의 수축량은 대략 (O \times \frac{w_i}{\sum w_j}).
예시: 컨테이너 내부 주축 400px, gap: 0, 아이템 두 개의 가상 주축 크기가 각각 300px이라 합이 600px이면 (O = 200)px입니다. 둘 다 flex-shrink: 1이면 (w_1 = 1 \times 300), (w_2 = 1 \times 300)이므로 각각 약 100px씩 줄어 최종 200px이 됩니다. 한쪽만 flex-shrink: 2로 두면 더 큰 비중으로 줄어듭니다.
/* 아이콘·레이블 패턴: 아이콘은 안 줄이고 텍스트만 줄게 */
.row {
display: flex;
min-width: 0;
}
.icon {
flex: 0 0 24px;
flex-shrink: 0;
}
.label {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
flex-shrink: 0은 “이 아이템은 수축 단계에서 최소한의 희생을 하지 않겠다”는 뜻에 가깝습니다. 다만 min-width·콘텐츠 최소 크기 때문에 여전히 레이아웃이 깨질 수 있으니, min-width: 0/overflow 전략과 함께 봐야 합니다.
플렉스 컨테이너와 아이템의 관계
포함 관계만 보면 단순합니다. display: flex인 요소의 직계 자식만 플렉스 아이템입니다. 텍스트 노드가 직접 자식이면 익명 플렉스 아이템이 생깁니다. position: absolute인 자식은 플렉스 배치에서 빠지는 등, 예외도 함께 기억해야 합니다.
아이템 쪽에서 자주 막히는 지점은 다음입니다.
margin: auto: 주축·교차축에서 남는 공간을 margin이 흡수합니다. 중앙 정렬을margin-left: auto; margin-right: auto로만 해결하려다 플렉스와 섞이면 예상과 다른 여백이 생깁니다.align-self: 컨테이너의align-items를 아이템 단위로 덮어씁니다.order: 시각적 순서만 바꿀 뿐, 접근성·포커스 순서는 DOM 순서를 따릅니다. 탭 이동 순서가 중요하면 DOM을 조정하거나 신중히 설계합니다.z-index: 플렉스 아이템은 특정 조건에서 쌓임 맥락이 달라질 수 있어, 겹침 UI에서 예상 밖의 렌더 순서가 나올 수 있습니다.
/* 세로 툴바: 위/아래는 고정, 가운데만 남는 공간 */
.column-toolbar {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.column-toolbar .top {
flex: 0 0 auto;
}
.column-toolbar .middle {
flex: 1 1 auto;
min-height: 0; /* 스크롤 영역에 필수인 경우가 많음 */
overflow: auto;
}
.column-toolbar .bottom {
flex: 0 0 auto;
}
프로덕션 Flexbox 패턴(요약)
실무에서 반복해서 쓰게 되는 조합을 추리면 다음 다섯 가지입니다.
- 가변 폭 + 말줄임 한 줄:
flex: 1 1 auto+min-width: 0+text-overflow: ellipsis. 테이블 셀 대체, 헤더 타이틀 바에 필수에 가깝습니다. - 고정 폭 사이드바 + 유동 메인:
.sidebar { flex: 0 0 240px; },.main { flex: 1 1 auto; min-width: 0; }.flex-basis와width를 이중으로 주지 않습니다. - 바닥 고정 푸터(스티키 푸터):
body { min-height: 100vh; display: flex; flex-direction: column; }, 메인에flex: 1 1 auto. 콘텐츠가 짧아도 푸터가 아래에 붙습니다. - 반응형 카드 열:
flex-wrap: wrap+flex: 1 1 280px+gap. 마지막 줄만 간격이 어색하면 Grid의auto-fill/minmax를 검토합니다. - 스크롤 영역 분리: 부모가
flex이고 자식 스크롤이 필요하면, 스크롤되는 자식에min-height: 0(또는 주축이 세로면min-width: 0) 을 잊지 않습니다. 그렇지 않으면 flex 아이템의 기본min-size: auto가 스크롤을 삼킵니다.
이 다섯 가지는 DevTools의 Flex 오버레이와 함께 보면, 주축 여유가 있는지·어디서 수축이 발생하는지가 한 번에 드러납니다.
gap: 간격
간격 설정
.container {
display: flex;
/* 모든 방향 간격 */
gap: 20px;
/* 가로/세로 간격 */
gap: 20px 10px; /* row-gap column-gap */
/* 개별 설정 */
row-gap: 20px;
column-gap: 10px;
}
gap vs margin 비교:
/* gap 사용 (권장) */
.container {
display: flex;
gap: 20px; /* 간단 */
}
/* margin 사용 (구식) */
.container {
display: flex;
}
.item {
margin-right: 20px;
}
.item:last-child {
margin-right: 0; /* 마지막 제거 */
}
네비게이션 바와 카드 그리드 만들기
네비게이션 바
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>네비게이션</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
background: #2c3e50;
padding: 1rem 2rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.logo {
color: white;
font-size: 1.5rem;
font-weight: bold;
}
.nav-menu {
display: flex;
list-style: none;
gap: 2rem;
}
.nav-menu a {
color: white;
text-decoration: none;
transition: color 0.3s;
}
.nav-menu a:hover {
color: #3498db;
}
.nav-actions {
display: flex;
gap: 1rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
}
.btn-login {
background: transparent;
color: white;
border: 2px solid white;
}
.btn-signup {
background: #3498db;
color: white;
}
.btn:hover {
transform: translateY(-2px);
}
</style>
</head>
<body>
<nav class="navbar">
<div class="logo">MyBrand</div>
<ul class="nav-menu">
<li><a href="#">홈</a></li>
<li><a href="#">소개</a></li>
<li><a href="#">서비스</a></li>
<li><a href="#">연락처</a></li>
</ul>
<div class="nav-actions">
<button class="btn btn-login">로그인</button>
<button class="btn btn-signup">회원가입</button>
</div>
</nav>
</body>
</html>
카드 그리드
<style>
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 20px;
background: #f8f9fa;
}
.card {
/* 최소 300px, 최대 1fr */
flex: 1 1 300px;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
}
.card h3 {
margin: 0 0 10px 0;
color: #2c3e50;
}
.card p {
margin: 0 0 15px 0;
color: #7f8c8d;
line-height: 1.6;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #ecf0f1;
}
.price {
font-size: 1.5rem;
font-weight: bold;
color: #3498db;
}
.btn-buy {
padding: 8px 16px;
background: #3498db;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
<div class="card-grid">
<div class="card">
<h3>상품 1</h3>
<p>상품 설명이 들어갑니다.</p>
<div class="card-footer">
<span class="price">29,000원</span>
<button class="btn-buy">구매</button>
</div>
</div>
<div class="card">
<h3>상품 2</h3>
<p>상품 설명이 들어갑니다.</p>
<div class="card-footer">
<span class="price">39,000원</span>
<button class="btn-buy">구매</button>
</div>
</div>
<div class="card">
<h3>상품 3</h3>
<p>상품 설명이 들어갑니다.</p>
<div class="card-footer">
<span class="price">49,000원</span>
<button class="btn-buy">구매</button>
</div>
</div>
<div class="card">
<h3>상품 4</h3>
<p>상품 설명이 들어갑니다.</p>
<div class="card-footer">
<span class="price">59,000원</span>
<button class="btn-buy">구매</button>
</div>
</div>
</div>
줄어들지 않는 아이템·마지막 행 정렬·높이 불일치
아이템이 줄어들지 않음
문제:
.item {
width: 300px; /* 고정 크기 */
}
해결:
.item {
flex: 1 1 300px; /* 줄어들 수 있음 */
/* 또는 */
min-width: 0; /* 최소 크기 제한 해제 */
}
텍스트 오버플로우
문제:
.item {
flex: 1;
/* 긴 텍스트가 넘침 */
}
해결:
.item {
flex: 1;
min-width: 0; /* 중요! */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
마지막 행 정렬
문제: justify-content: space-between에서 마지막 행이 이상하게 정렬됨
[1] [2] [3]
[4] [5] ← 문제!
해결 1: 빈 요소 추가
<style>
.grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.item {
flex: 1 1 200px;
}
/* 빈 요소 (최대 개수 - 1개) */
.spacer {
flex: 1 1 200px;
height: 0;
}
</style>
<div class="grid">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="spacer"></div>
<div class="spacer"></div>
</div>
해결 2: CSS Grid 사용 (권장)
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
높이가 같지 않음
문제: 카드 높이가 제각각 해결:
.container {
display: flex;
align-items: stretch; /* 기본값, 높이 동일 */
}
.card {
display: flex;
flex-direction: column;
}
.card-content {
flex: 1; /* 남은 공간 채우기 */
}
.card-footer {
/* 하단 고정 */
}
Flexbox vs Grid
언제 무엇을 사용할까?
| 기준 | Flexbox | Grid |
|---|---|---|
| 차원 | 1차원 (가로 또는 세로) | 2차원 (가로 + 세로) |
| 용도 | 네비게이션, 버튼 그룹, 카드 나열 | 페이지 레이아웃, 복잡한 그리드 |
| 정렬 | 주축/교차축 정렬 | 행/열 정렬 |
| 아이템 크기 | 내용 기반 + flex | 명시적 크기 지정 |
선택 가이드:
✅ Flexbox 사용:
- 네비게이션 바
- 버튼 그룹
- 카드를 가로로 나열
- 중앙 정렬
- 폼 필드 정렬
✅ Grid 사용:
- 전체 페이지 레이아웃
- 갤러리 (행과 열)
- 복잡한 그리드
- 정확한 위치 지정
헷갈릴 때: justify-content vs align-items
둘 다 “정렬”이지만 축이 다릅니다.
- justify-content: 주축(main axis) 방향으로 아이템 뭉치를 배치합니다.
flex-direction: row이면 가로,column이면 세로입니다. - align-items: 교차축(cross axis) 방향으로 각 줄(라인) 안에서 아이템을 정렬합니다.
row일 때는 세로 정렬에 해당합니다.
flex-direction: row (주축 → 가로)
justify-content → [아이템들을 가로로 어디에 모을지]
align-items → [한 줄 안에서 세로로 어디에 붙일지]
헷갈릴 때: flex-direction을 바꾸면 주축·교차축이 바뀌므로, 같은 속성이라도 화면에서 담당하는 방향이 뒤집힙니다. 그때는 DevTools의 Flex 오버레이로 주축 화살표를 확인하는 것이 가장 빠릅니다.
flex-grow·flex-shrink·flex-basis 한 표로
| 속성 | 역할 | 기억법 |
|---|---|---|
| flex-basis | 남은 공간을 나누기 전 기준 크기 (auto면 width/콘텐츠 기준) | “출발선” |
| flex-grow | 남는 공간을 비율로 나눠 가짐 | “늘어날 때” |
| flex-shrink | 공간이 부족할 때 줄어드는 비율 | “줄어들 때” |
자주 쓰는 축약:
flex: 1→ 보통flex: 1 1 0%에 가깝게 해석되어, 균등 분배에 자주 씁니다(브라우저 기본값과 조합 시 세부는 다를 수 있음).flex: 0 0 200px→ 200px 고정, 늘어나지도 줄지도 않음(사이드바 등).flex: 1 1 300px→ 기준 300px이지만 늘어나고 줄어들 수 있음(반응형 카드 열). 주의:flex-basis와width가 동시에 있으면 상황에 따라 flex-basis가 우선되는 경우가 많습니다. “고정 폭”이면flex: 0 0 200px처럼 한 번에 쓰는 편이 안전합니다.
정렬 방향 착각과 gap·margin 중복 같은 실수
justify-content로 세로 중앙 정렬을 기대: 주축이 가로일 때는 세로 정렬은align-items입니다.flex: 1인데 내용이 안 줄어듦: 자식에min-width: 0(또는 세로 스택이면min-height: 0)을 주지 않아 최소 크기가 콘텐츠에 막힌 경우가 많습니다.gap과margin을 동시에 중복: 간격이 두 배로 벌어져 보입니다. 한 가지 방식으로 통일합니다.- 중첩 flex마다
height: 100%남발: 부모 높이가 불명확하면 기대와 다릅니다.flex: 1+min-height: 0조합을 우선 검토합니다.
Flexbox 요약
- Flexbox는 1차원 레이아웃입니다. 주축은
flex-direction이 정하고,justify-content는 주축,align-items는 교차축 정렬입니다. - 폭은
flex-basis(없으면width)에서 출발해, 남는 공간은flex-grow로 나누고 모자라면flex-shrink × 기준 크기가중치로 줄입니다. - “안 줄어든다”는 문제의 대부분은 플렉스 아이템의
min-width: auto이고,min-width: 0으로 풉니다. order와row-reverse는 보이는 순서만 바꿉니다. 키보드 탭 순서는 DOM을 따르므로 접근성이 필요한 곳에서는 DOM 순서를 바꿉니다.- 행과 열을 함께 맞춰야 하면 Grid를 씁니다.