언제 Grid이고, 언제 Flexbox인가?
Flexbox와 Grid는 모두 CSS 레이아웃 도구지만, 각각 강점이 다릅니다. Flexbox는 **한 방향(가로 또는 세로)**의 배치에 강하고, Grid는 **두 방향(가로 + 세로)**의 배치에 강합니다.
책장에 비유하면, Flexbox는 한 줄 선반에 책을 나란히 놓는 것이고, Grid는 격자형 수납장 전체를 설계하는 것입니다.
| 기준 | Flexbox | Grid |
|---|---|---|
| 차원 | 1차원 (행 또는 열) | 2차원 (행 + 열) |
| 적합한 용도 | 네비게이션, 카드 행, 정렬 | 페이지 레이아웃, 대시보드 |
| 아이템 크기 | 콘텐츠 기반 | 트랙 기반 |
| 정렬 | 한 축 기준 | 두 축 기준 |
Flexbox 핵심 패턴
기본 배치
/* 네비게이션 바 — 로고 왼쪽, 메뉴 오른쪽 */
.navbar {
display: flex;
justify-content: space-between; /* 양쪽 끝 배치 */
align-items: center; /* 세로 가운데 정렬 */
padding: 1rem 2rem;
gap: 1rem; /* 아이템 간 간격 */
}
.nav-menu {
display: flex;
gap: 1.5rem; /* 메뉴 항목 간 간격 */
list-style: none;
}
/* 카드 행 — 균등 분배 + 줄바꿈 */
.card-row {
display: flex;
flex-wrap: wrap; /* 공간 부족 시 줄바꿈 */
gap: 1rem;
}
.card {
flex: 1 1 300px; /* 최소 300px, 나머지 공간 균등 */
/* flex-grow: 1 → 남은 공간 채움 */
/* flex-shrink: 1 → 공간 부족 시 줄어듦 */
/* flex-basis: 300px → 기본 너비 */
}
자주 쓰는 정렬 패턴
/* 완전 중앙 정렬 (가장 깔끔한 방법) */
.center-everything {
display: flex;
justify-content: center; /* 가로 중앙 */
align-items: center; /* 세로 중앙 */
min-height: 100vh;
}
/* 푸터를 항상 하단에 고정 */
.page-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.page-content {
flex: 1; /* 남은 공간 모두 차지 → 푸터가 하단으로 */
}
.page-footer {
/* flex: 0으로 자연 높이 유지 */
}
/* 마지막 아이템만 오른쪽 정렬 */
.toolbar {
display: flex;
gap: 0.5rem;
}
.toolbar .spacer {
margin-left: auto; /* auto margin으로 밀어내기 */
}
CSS Grid 핵심 패턴
페이지 레이아웃
/* 전형적인 사이드바 + 메인 콘텐츠 레이아웃 */
.page-grid {
display: grid;
grid-template-columns: 250px 1fr; /* 사이드바 250px, 나머지 메인 */
grid-template-rows: auto 1fr auto; /* 헤더, 콘텐츠, 푸터 */
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
gap: 1rem;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* 반응형 — 모바일에서 사이드바 숨김 */
@media (max-width: 768px) {
.page-grid {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"footer";
}
.sidebar { display: none; }
}
자동 반응형 카드 그리드
/* auto-fill + minmax — 미디어 쿼리 없이 반응형 */
.auto-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
padding: 1rem;
}
/* auto-fill vs auto-fit 차이 */
/* auto-fill: 빈 트랙도 공간 차지 (정렬 유지) */
/* auto-fit: 빈 트랙 제거 (아이템이 남은 공간 채움) */
.auto-grid-fit {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
/* 아이템 수가 적을 때 → 아이템이 늘어남 */
}
대시보드 레이아웃
/* 다양한 크기의 위젯을 배치하는 대시보드 */
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 200px; /* 각 행 높이 200px */
gap: 1rem;
}
/* 위젯별 크기 지정 */
.widget-wide {
grid-column: span 2; /* 가로 2칸 */
}
.widget-tall {
grid-row: span 2; /* 세로 2칸 */
}
.widget-large {
grid-column: span 2;
grid-row: span 2; /* 가로 2칸 x 세로 2칸 */
}
/* 반응형 대시보드 */
@media (max-width: 1024px) {
.dashboard {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.dashboard {
grid-template-columns: 1fr;
}
.widget-wide,
.widget-tall,
.widget-large {
grid-column: span 1;
grid-row: span 1;
}
}
Container Query: 컴포넌트 단위 반응형
기존 미디어 쿼리는 뷰포트(브라우저 창) 기준이지만, Container Query는 부모 컨테이너 기준으로 스타일을 적용합니다. 같은 카드 컴포넌트가 사이드바에서는 세로형, 메인 영역에서는 가로형으로 자동 변환됩니다.
/* 1. 컨테이너 선언 */
.card-container {
container-type: inline-size; /* 너비 기준 컨테이너 쿼리 활성화 */
container-name: card; /* 선택적 이름 지정 */
}
/* 2. 기본 카드 스타일 (좁은 컨테이너) */
.card {
display: flex;
flex-direction: column; /* 기본: 세로 배치 */
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.card-image {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}
.card-body {
padding: 1rem;
}
/* 3. 컨테이너 쿼리 — 컨테이너가 500px 이상이면 가로 배치 */
@container card (min-width: 500px) {
.card {
flex-direction: row; /* 가로 배치로 전환 */
}
.card-image {
width: 200px;
aspect-ratio: 1; /* 정사각형으로 변경 */
}
}
/* 4. 더 넓은 컨테이너에서는 추가 정보 표시 */
@container card (min-width: 700px) {
.card-meta {
display: flex; /* 메타 정보 표시 */
gap: 0.5rem;
}
.card-description {
-webkit-line-clamp: 3; /* 3줄까지 표시 */
}
}
Subgrid: 부모 그리드 라인 상속
Subgrid는 자식 요소가 부모 Grid의 트랙 라인을 상속받아, 중첩 그리드에서도 정렬이 일치하도록 합니다.
/* 부모 그리드 */
.product-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
/* 카드가 내부 요소(이미지, 제목, 가격)의 높이를 서로 맞춤 */
.product-card {
display: grid;
grid-template-rows: subgrid; /* 부모의 행 트랙 상속 */
grid-row: span 3; /* 3행을 차지 */
gap: 0.5rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: hidden;
}
/* 결과: 모든 카드의 이미지 높이, 제목 높이, 가격 높이가 정렬됨 */
.product-image { /* 첫 번째 행 */ }
.product-title { /* 두 번째 행 — 다른 카드와 높이 일치 */ }
.product-price { /* 세 번째 행 — 다른 카드와 높이 일치 */ }
레이아웃 선택 가이드
| 상황 | 추천 방법 |
|---|---|
| 아이템을 한 줄로 나열 | Flexbox |
| 아이템을 균등 그리드로 배치 | Grid + auto-fill |
| 전체 페이지 구조 (헤더, 사이드바, 메인) | Grid + grid-template-areas |
| 세로 가운데 정렬 | Flexbox align-items: center |
| 반응형 카드 레이아웃 | Grid + minmax() |
| 컴포넌트 단위 반응형 | Container Query |
| 중첩 그리드 정렬 일치 | Subgrid |
실전 팁
- Grid와 Flexbox를 섞어 쓰세요: 페이지 전체 레이아웃은 Grid, 컴포넌트 내부 배치는 Flexbox가 자연스럽습니다.
auto-fill + minmax를 먼저 시도하세요: 미디어 쿼리 없이도 반응형 레이아웃을 만들 수 있습니다.- Container Query로 컴포넌트를 독립적으로 만드세요: 뷰포트가 아닌 부모 크기에 반응하면, 어디에 배치해도 잘 동작하는 컴포넌트를 만들 수 있습니다.
gap속성을 적극 활용하세요:margin으로 간격을 주면 첫 번째/마지막 아이템 처리가 번거롭지만,gap은 아이템 사이에만 적용됩니다.grid-template-areas로 레이아웃을 시각화하세요: ASCII 아트처럼 레이아웃을 표현하면 코드의 가독성이 크게 높아집니다.- DevTools의 Grid/Flexbox 인스펙터를 활용하세요: Chrome, Firefox 모두 Grid 라인과 Flexbox 구조를 시각적으로 확인할 수 있습니다.