본문으로 건너뛰기 CSS Flexbox 완벽 가이드 — 레이아웃 알고리즘·flex-shrink·실무 패턴

CSS Flexbox 완벽 가이드 — 레이아웃 알고리즘·flex-shrink·실무 패턴

CSS Flexbox 완벽 가이드 — 레이아웃 알고리즘·flex-shrink·실무 패턴

이 글의 핵심

Flexbox 기본부터 CSS Flexbox Level 1 스펙 관점의 가용 공간·수축(flex-shrink) 계산, flex-basis와 width 관계, 프로덕션 패턴까지 한 번에 정리합니다.

들어가며

”레이아웃의 혁명, Flexbox”

Flexbox는 CSS의 1차원 레이아웃 모듈로, 가로 또는 세로 한 방향으로 자식 요소를 느낌 있게 정렬할 수 있습니다. 속성 사전·예제에 더해, 아래 「Flexbox 심화: 레이아웃 알고리즘·스펙 관점 이해」 절에서는 가용 공간 분배, flex-basiswidth의 역할, flex-shrink 가중 수축, 컨테이너·아이템 관계, 프로덕션 패턴을 스펙 수준에서 짚습니다. 비유(유연한 선반): 컨테이너는 길이가 늘어나는 유연한 선반이으며, 아이템은 그 위에 놓인 물건이라고 보시면 됩니다. justify-content는 주 방향으로 물건을 어떻게 퍼놓을지, align-items는 선반 깊이(교차축) 방향으로 맞출지를 조절합니다. 전통적인 방법의 문제:

/* float 사용 (구식) */
.item {
    float: left;
    width: 33.33%;
}
.clearfix::after {
    content: "";
    display: table;
    clear: both;
}

Flexbox로 해결:

/* 간단하고 직관적 */
.container {
    /* display: flex - Flexbox 레이아웃 활성화 */
    /* 이 한 줄로 자식 요소들이 flex item이 됨 */
    display: flex;
    
    /* gap - 아이템 사이의 간격 설정 */
    /* margin 없이도 간격 조절 가능 */
    gap: 20px;
}
.item {
    /* flex: 1 - 남은 공간을 균등하게 분배 */
    /* flex-grow: 1, flex-shrink: 1, flex-basis: 0의 축약형 */
    /* 모든 아이템이 flex: 1이면 같은 크기로 나눠 가짐 */
    flex: 1;
}

코드 설명

  • display: flex: 부모 요소를 flex container로 만듦
  • gap: 20px: 아이템 사이 간격 (margin보다 편리)
  • flex: 1: 각 아이템이 남은 공간을 균등하게 차지 Flexbox의 장점:
  • 간단한 문법: 복잡한 레이아웃을 몇 줄로 구현
  • 반응형: 화면 크기에 자동 대응
  • 정렬: 수평/수직 정렬이 쉬움
  • 순서 제어: HTML 순서와 무관하게 배치
  • 공간 분배: 남은 공간을 자동 분배

1. Flexbox 기본 개념

컨테이너와 아이템

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     │      │
│  └───────────┘      │
└─────────────────────┘

2. 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>

3. 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>

4. 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;
}

5. 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;  /* 기본값 */
}

6. Flex Item 속성

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 -->

Flexbox 심화: 레이아웃 알고리즘·스펙 관점 이해

문법만 알면 “왜 이 폭이 되지?” 같은 문제에 시간을 씁니다. 아래는 CSS Flexible Box Layout Module Level 1에서 정의하는 흐름을 실무자 관점으로 압축한 것입니다. 브라우저마다 부동소수·반올림으로 1px 차이는 날 수 있으나, 원인 분석과 DevTools 해석에는 충분합니다.

1. 레이아웃 알고리즘과 가용 공간(available space) 분배

Flex 컨테이너는 플렉스 포맷팅 컨텍스트(flex formatting context) 를 만듭니다. 직계 자식만 플렉스 아이템이 되며, 주축(main axis)과 교차축(cross axis)이 정해진 뒤 레이아웃이 진행됩니다.

스펙이 말하는 큰 단계를 한 줄로 요약하면 다음과 같습니다.

  1. 플렉스 라인(flex line) 을 만든다. flex-wrap에 따라 한 줄 또는 여러 줄이 됩니다.
  2. 각 아이템에 대해 가상의 주축 크기(hypothetical main size) 를 계산합니다. 이때 출발점이 되는 값이 flex-basismin-* / max-* 클램프입니다.
  3. 한 줄 안에서 주축 방향 남거나 모자라는 공간(여유 공간, free space) 을 구합니다.
  4. 여유가 양수이면 flex-grow 비율로 나누으며, 음수(넘침)이면 flex-shrink에 따라 수축합니다.
  5. 교차축 크기·정렬(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; /* 늘리지도 줄이도 않음 → 남는 공간 발생 */
}

2. flex-basis와 width·height: 무엇이 “기준 크기”인가

주축이 가로(flex-direction: row) 일 때, 아이템의 주축 크기 결정에 가장 직접적으로 관여하는 것은 flex-basis입니다. flex-basis: auto(초깃값)일 때는, 명시된 width가 있으면 그것이 flex base size에 사용되는 경우가 일반적입니다. 반대로 flex-basis200px처럼 길이를 주면, 주축 기준점은 그 값이 되고 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를 먼저 확정합니다).

3. flex-shrink 계산: “얼마나 줄어들지”의 가중치

주축 한 줄에서 가상 주축 크기의 합 + gap이 컨테이너 안쪽 주축 길이를 초과하면, 브라우저는 넘치는 분(음의 여유 공간)을 아이템에 나누어 줍니다. 이때 각 아이템이 맡는 수축량은 단순히 flex-shrink 숫자만으로 결정되지 않으며, 가상 주축 크기와 함께 가중됩니다.

직관적인 근사식은 다음과 같습니다(교육용; 실제는 스펙 절차에 따라 후속 보정이 있습니다).

  1. 넘침 (O = \sum \text{hypothetical main size} + \text{gap} - \text{container inner main size}) (양수면 수축 필요).
  2. 각 아이템 (i)에 대해 가중치 (w_i = (\text{flex-shrink}_i) \times (\text{가상 주축 크기}_i)).
  3. 아이템 (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 전략과 함께 봐야 합니다.

4. 플렉스 컨테이너와 아이템의 관계

포함 관계만 보면 단순합니다. 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;
}

5. 프로덕션 Flexbox 패턴(요약)

이미 본문 예제와 겹치지 않게, 운영 환경에서 반복되는 조합만 압축합니다.

  1. 가변 폭 + 말줄임 한 줄: flex: 1 1 auto + min-width: 0 + text-overflow: ellipsis. 테이블 셀 대체, 헤더 타이틀 바에 필수에 가깝습니다.
  2. 고정 폭 사이드바 + 유동 메인: .sidebar { flex: 0 0 240px; }, .main { flex: 1 1 auto; min-width: 0; }. flex-basiswidth를 이중으로 주지 않습니다.
  3. 바닥 고정 푸터(스티키 푸터): body { min-height: 100vh; display: flex; flex-direction: column; }, 메인에 flex: 1 1 auto. 콘텐츠가 짧아도 푸터가 아래에 붙습니다.
  4. 반응형 카드 열: flex-wrap: wrap + flex: 1 1 280px + gap. 마지막 줄만 간격이 어색하면 Grid의 auto-fill/minmax를 검토합니다.
  5. 스크롤 영역 분리: 부모가 flex이고 자식 스크롤이 필요하면, 스크롤되는 자식에 min-height: 0(또는 주축이 세로면 min-width: 0) 을 잊지 않습니다. 그렇지 않으면 flex 아이템의 기본 min-size: auto가 스크롤을 삼킵니다.

이 다섯 가지는 DevTools의 Flex 오버레이와 함께 보면, 주축 여유가 있는지·어디서 수축이 발생하는지가 한 번에 드러납니다.


7. 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;  /* 마지막 제거 */
}

8. 실전 예제

예제 1: 네비게이션 바

<!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>

예제 2: 카드 그리드

<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>

예제 3: 성배 레이아웃 (Holy Grail)

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>성배 레이아웃</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            display: flex;
            flex-direction: column;
            min-height: 100vh;
        }
        
        header {
            background: #2c3e50;
            color: white;
            padding: 1rem;
            text-align: center;
        }
        
        .main-content {
            display: flex;
            flex: 1;  /* 남은 공간 채우기 */
        }
        
        .sidebar {
            flex: 0 0 250px;  /* 고정 250px */
            background: #ecf0f1;
            padding: 1rem;
        }
        
        main {
            flex: 1;  /* 남은 공간 */
            padding: 2rem;
        }
        
        footer {
            background: #34495e;
            color: white;
            padding: 1rem;
            text-align: center;
        }
        
        /* 반응형 */
        @media (max-width: 768px) {
            .main-content {
                flex-direction: column;
            }
            
            .sidebar {
                flex: 0 0 auto;
            }
        }
    </style>
</head>
<body>
    <header>
        <h1>헤더</h1>
    </header>
    
    <div class="main-content">
        <aside class="sidebar">
            <h3>사이드바</h3>
            <ul>
                <li>메뉴 1</li>
                <li>메뉴 2</li>
                <li>메뉴 3</li>
            </ul>
        </aside>
        
        <main>
            <h2>메인 콘텐츠</h2>
            <p>내용이 들어갑니다...</p>
        </main>
    </div>
    
    <footer>
        <p>&copy; 2026 MyWebsite</p>
    </footer>
</body>
</html>

예제 4: 폼 레이아웃

<style>
    .form-container {
        max-width: 600px;
        margin: 2rem auto;
        padding: 2rem;
        background: white;
        border-radius: 10px;
        box-shadow: 0 2px 20px rgba(0,0,0,0.1);
    }
    
    .form-row {
        display: flex;
        gap: 20px;
        margin-bottom: 20px;
    }
    
    .form-group {
        flex: 1;
        display: flex;
        flex-direction: column;
    }
    
    label {
        margin-bottom: 5px;
        font-weight: 500;
        color: #2c3e50;
    }
    
    input {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 5px;
        font-size: 1rem;
    }
    
    .form-actions {
        display: flex;
        justify-content: flex-end;
        gap: 10px;
        margin-top: 30px;
    }
    
    button {
        padding: 10px 20px;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        font-size: 1rem;
    }
    
    .btn-cancel {
        background: #95a5a6;
        color: white;
    }
    
    .btn-submit {
        background: #3498db;
        color: white;
    }
</style>
<div class="form-container">
    <h2>회원가입</h2>
    
    <form>
        <div class="form-row">
            <div class="form-group">
                <label>이름</label>
                <input type="text" placeholder="홍길동">
            </div>
            <div class="form-group">
                <label>나이</label>
                <input type="number" placeholder="25">
            </div>
        </div>
        
        <div class="form-row">
            <div class="form-group">
                <label>이메일</label>
                <input type="email" placeholder="example@email.com">
            </div>
        </div>
        
        <div class="form-row">
            <div class="form-group">
                <label>비밀번호</label>
                <input type="password" placeholder="********">
            </div>
        </div>
        
        <div class="form-actions">
            <button type="button" class="btn-cancel">취소</button>
            <button type="submit" class="btn-submit">가입</button>
        </div>
    </form>
</div>

9. 자주 발생하는 문제

1. 아이템이 줄어들지 않음

문제:

.item {
    width: 300px;  /* 고정 크기 */
}

해결:

.item {
    flex: 1 1 300px;  /* 줄어들 수 있음 */
    /* 또는 */
    min-width: 0;  /* 최소 크기 제한 해제 */
}

2. 텍스트 오버플로우

문제:

.item {
    flex: 1;
    /* 긴 텍스트가 넘침 */
}

해결:

.item {
    flex: 1;
    min-width: 0;  /* 중요! */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

3. 마지막 행 정렬

문제: 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;
}

4. 높이가 같지 않음

문제: 카드 높이가 제각각 해결:

.container {
    display: flex;
    align-items: stretch;  /* 기본값, 높이 동일 */
}
.card {
    display: flex;
    flex-direction: column;
}
.card-content {
    flex: 1;  /* 남은 공간 채우기 */
}
.card-footer {
    /* 하단 고정 */
}

10. Flexbox vs Grid

언제 무엇을 사용할까?

기준FlexboxGrid
차원1차원 (가로 또는 세로)2차원 (가로 + 세로)
용도네비게이션, 버튼 그룹, 카드 나열페이지 레이아웃, 복잡한 그리드
정렬주축/교차축 정렬행/열 정렬
아이템 크기내용 기반 + flex명시적 크기 지정
브라우저 지원IE 10+ (부분)IE 10+ (부분)
선택 가이드:
✅ Flexbox 사용:
- 네비게이션 바
- 버튼 그룹
- 카드를 가로로 나열
- 중앙 정렬
- 폼 필드 정렬
✅ Grid 사용:
- 전체 페이지 레이아웃
- 갤러리 (행과 열)
- 복잡한 그리드
- 정확한 위치 지정

11. 실전 팁

1. 디버깅

.container {
    /* 경계선으로 확인 */
    border: 2px solid red;
}
.item {
    border: 1px solid blue;
}

Chrome DevTools:

  1. 요소 선택
  2. Styles 패널에서 Flexbox 아이콘 클릭
  3. 시각적으로 확인

2. 반응형 패턴

/* 모바일: 세로 */
.container {
    display: flex;
    flex-direction: column;
}
/* 태블릿 이상: 가로 */
@media (min-width: 768px) {
    .container {
        flex-direction: row;
    }
}

3. 자주 사용하는 패턴

/* 균등 분배 */
.item { flex: 1; }
/* 고정 사이드바 + 유연한 메인 */
.sidebar { flex: 0 0 250px; }
.main { flex: 1; }
/* 중앙 정렬 */
.center {
    display: flex;
    justify-content: center;
    align-items: center;
}
/* 양끝 정렬 */
.space-between {
    display: flex;
    justify-content: space-between;
}

4. 성능 최적화

/* ❌ 나쁨: 레이아웃 변경 */
.item:hover {
    width: 300px;  /* 리플로우 발생 */
}
/* ✅ 좋음: transform 사용 */
.item {
    transition: transform 0.3s;
}
.item:hover {
    transform: scale(1.05);  /* GPU 가속 */
}

12. 브라우저 지원

지원 현황

브라우저버전
Chrome29+
Firefox28+
Safari9+
Edge12+
IE11 (부분)

벤더 프리픽스 (구형 브라우저)

.container {
    display: -webkit-box;      /* iOS 6-, Safari 3.1-6 */
    display: -webkit-flex;     /* Safari 6.1+ */
    display: -ms-flexbox;      /* IE 10 */
    display: flex;             /* 표준 */
    
    -webkit-box-orient: horizontal;
    -webkit-box-direction: normal;
    -webkit-flex-direction: row;
    -ms-flex-direction: row;
    flex-direction: row;
}

Autoprefixer 사용 (권장):

npm install autoprefixer

PostCSS 파이프라인에 Autoprefixer를 넣으면 구형 브라우저용 접두사가 빌드 시 자동으로 붙습니다.

13. 실전 한눈에: 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 오버레이로 주축 화살표를 확인하는 것이 가장 빠릅니다.

14. flex-grow, flex-shrink, flex-basis 실무 해석

속성역할기억법
flex-basis남은 공간을 나누기 전 기준 크기 (auto면 width/콘텐츠 기준)“출발선”
flex-grow남는 공간을 비율로 나눠 가짐“늘어날 때”
flex-shrink공간이 부족할 때 줄어드는 비율“줄어들 때”
자주 쓰는 축약
  • flex: 1 → 보통 flex: 1 1 0%에 가깝게 해석되어, 균등 분배에 자주 씁니다(브라우저 기본값과 조합 시 세부는 다를 수 있음).
  • flex: 0 0 200px200px 고정, 늘어나지도 줄지도 않음(사이드바 등).
  • flex: 1 1 300px기준 300px이지만 늘어나고 줄어들 수 있음(반응형 카드 열). 주의: flex-basiswidth가 동시에 있으면 상황에 따라 flex-basis가 우선되는 경우가 많습니다. “고정 폭”이면 flex: 0 0 200px처럼 한 번에 쓰는 편이 안전합니다.

15. 실전 레이아웃 보강: 반응형 네비 + 카드 그리드 요약

이미 8. 실전 예제에 네비게이션·카드 그리드가 있습니다. 여기서는 패턴만 정리합니다. 네비게이션: 한 줄에 로고 | 메뉴 | 액션을 두려면 부모에 display: flex, justify-content: space-between, align-items: center, 메뉴는 display: flex + gap이 기본 조합입니다. 모바일에서는 flex-direction: column 또는 햄버거 메뉴로 바꿉니다. 카드 그리드: flex-wrap: wrap + flex: 1 1 280px처럼 최소 너비를 basis로 두면, 화면이 좁아질수록 자연스럽게 줄바꿈됩니다. 마지막 줄 정렬이 어색하면 Grid의 auto-fit/minmax를 검토합니다(해당 글 Grid 편 참고).

16. 흔한 실수 추가

  1. justify-content로 세로 중앙 정렬을 기대: 주축이 가로일 때는 세로 정렬은 align-items입니다.
  2. flex: 1인데 내용이 안 줄어듦: 자식에 min-width: 0(또는 세로 스택이면 min-height: 0)을 주지 않아 최소 크기가 콘텐츠에 막힌 경우가 많습니다.
  3. gapmargin을 동시에 중복: 간격이 두 배로 벌어져 보입니다. 한 가지 방식으로 통일합니다.
  4. 중첩 flex마다 height: 100% 남발: 부모 높이가 불명확하면 기대와 다릅니다. flex: 1 + min-height: 0 조합을 우선 검토합니다.

정리

핵심 요약

  1. display: flex: Flexbox 활성화
  2. flex-direction: 주축 방향 (row/column)
  3. justify-content: 주축 정렬(주로 여유 공간이 있을 때 배치 방식이 달라짐)
  4. align-items: 교차축 정렬
  5. flex-wrap: 줄바꿈 설정
  6. flex: grow shrink basis 축약 — 양의 여유flex-grow, 음의 여유(넘침)flex-shrink로 분배
  7. gap: 아이템 간격
  8. flex-basis vs width: 주축 기준은 flex-basis 중심으로 이해하며, flex-basis: auto일 때 width/height가 기여한다고 보면 혼란이 줄어듦
  9. min-width/min-height: 0: 말줄임·스크롤·가변 폭에서 레이아웃이 막힐 때 우선 확인

Flexbox 속성 치트시트

속성대상설명
display: flexContainerFlexbox 활성화
flex-directionContainer주축 방향 (row/column)
justify-contentContainer주축 정렬
align-itemsContainer교차축 정렬
align-contentContainer여러 줄 정렬
flex-wrapContainer줄바꿈
gapContainer간격
flexItemgrow shrink basis
flex-growItem늘어남 비율
flex-shrinkItem줄어듦 비율
flex-basisItem기본 크기
align-selfItem개별 정렬
orderItem순서 변경

실전 팁

  1. gap 사용: margin보다 간단하고 명확
  2. flex: 1: 균등 분배의 기본
  3. min-width: 0: 텍스트 오버플로우 방지
  4. align-items: center: 수직 중앙 정렬
  5. justify-content: space-between: 양끝 정렬
  6. flex-wrap: wrap: 반응형 카드 그리드
  7. order: HTML 순서 변경 없이 시각적 순서 변경
  8. Chrome DevTools: Flexbox 디버깅 도구 활용

다음 단계


관련 글


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. CSS Flexbox 내부 동작: 가용 공간 분배, flex-basis와 width, flex-shrink 계산, 컨테이너·아이템 관계, 프로덕션 패턴까지 한글로 정리합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

CSS, Flexbox, 레이아웃, 플렉스박스, 프론트엔드 등으로 검색하시면 이 글이 도움이 됩니다.