안녕하세요! 오늘 다뤄볼 주제는 프론트엔드 개발의 핵심이자, 우리가 웹 페이지 레이아웃을 짤 때 숨 쉬듯 사용하게 될 플렉스박스(Flexbox)의 기본 개념입니다.
React나 Next.js로 컴포넌트를 만들고 Storybook으로 테스트하는 컴포넌트 주도 개발(CDD) 환경에서, 각 컴포넌트의 내부 배치를 책임지는 가장 강력한 도구가 바로 이 플렉스박스입니다. 준비 중이신 웹 프로필 사이트나 독후감 사이트의 네비게이션 바, 카드 리스트 등을 구성할 때 아주 유용하게 쓰일 테니 꼼꼼히 살펴보겠습니다!
플렉서블 박스 레이아웃(flexible box layout) 모듈(보통 플렉스박스, flexbox라고 부릅니다)은 아이템들 사이의 공간을 분배하고 수많은 정렬 기능을 제공하는 1차원(one-dimensional) 레이아웃 모델입니다. 이 글에서는 플렉스박스의 주요 기능들에 대한 개요를 제공하며, 이어지는 다른 가이드들에서 더욱 자세히 탐구해 볼 것입니다.
우리가 플렉스박스를 1차원이라고 부르는 이유는, 플렉스박스가 한 번에 하나의 차원(가로 행(row)이든 세로 열(column)이든)만을 다루기 때문입니다. 이는 열과 행을 동시에 제어하는 2차원 모델인 CSS 그리드 레이아웃(CSS Grid Layout)과 대조되는 부분입니다.
플렉스박스로 작업할 때는 주축(main axis)과 교차축(cross axis)이라는 두 가지 축을 기준으로 생각해야 합니다. 주축(main axis)은 flex-direction 속성에 의해 정의되며, 교차축(cross axis)은 주축에 수직으로 교차하여 달립니다. 우리가 플렉스박스로 하는 모든 작업은 이 두 축을 기준으로 이루어지므로, 처음부터 이 축들이 어떻게 작동하는지 확실히 이해하는 것이 매우 중요합니다.
💡 강사의 기술 면접 꿀팁!
기술 면접에서 "플렉스박스의 작동 원리를 설명해 보세요"라는 질문을 받으면, 반드시 이 주축(Main Axis)과 교차축(Cross Axis) 개념을 먼저 언급하셔야 합니다. 플렉스박스의 모든 정렬 속성(justify-content,align-items등)은 절대적인 상하좌우가 아니라 이 두 축을 기준으로 작동하기 때문입니다.
주축(main axis)은 flex-direction에 의해 정의되며, 이 속성은 다음 4가지 값을 가질 수 있습니다:
rowrow-reversecolumncolumn-reverserow나 row-reverse를 선택하면, 주축은 인라인 방향(inline direction, 글자가 써지는 가로 방향)을 따라 행(row)으로 달리게 됩니다.
column이나 column-reverse를 선택하면, 주축은 블록 방향(block direction)을 따라 페이지의 위에서 아래로(세로로) 달리게 됩니다.
교차축(cross axis)은 주축과 수직으로 교차하는 축입니다. 따라서 만약 flex-direction(주축)이 row나 row-reverse로 설정되어 있다면, 교차축은 열(column)을 따라 세로로 내려가게 됩니다.
만약 주축이 column이나 column-reverse라면, 교차축은 행(row)을 따라 가로로 달리게 됩니다.
플렉스박스를 이해하는 데 있어 또 하나 중요한 부분은, 플렉스박스가 문서의 쓰기 모드(writing mode)에 대해 어떠한 가정도 하지 않는다는 점입니다. 과거에는 모든 텍스트가 문서의 왼쪽 위에서 시작하여 오른쪽으로 흐르고 줄바꿈이 일어난다고 당연하게 여겼지만, 플렉스박스는 다른 논리적 속성 및 값(logical properties and values)들처럼 세상의 모든 쓰기 모드를 지원합니다.
플렉스박스와 쓰기 모드의 관계에 대해서는 나중에 더 자세히 읽어보실 수 있습니다. 하지만 지금은 플렉스 아이템들이 흘러가는 방향을 설명할 때 왜 우리가 좌/우, 상/하라는 단어 대신 시작(start)과 끝(end)이라는 단어를 사용하는지 이해하는 데 다음 설명이 도움이 될 것입니다.
만약 flex-direction이 row이고 영어나 한국어로 작업하고 있다면, 주축의 시작(start) 가장자리는 왼쪽이 되고 끝(end) 가장자리는 오른쪽이 될 것입니다.
하지만 아랍어(우에서 좌로 읽는 언어)로 작업한다면, 주축의 시작 가장자리는 오른쪽이 되고 끝 가장자리는 왼쪽이 됩니다.
두 경우 모두 언어가 가로 쓰기 모드(horizontal writing mode)를 가지므로, 교차축의 시작 가장자리는 플렉스 컨테이너의 위쪽(top)이고 끝 가장자리는 아래쪽(bottom)이 됩니다.
시간이 지나면 좌/우 대신 시작/끝으로 생각하는 것이 훨씬 자연스러워질 것이며, 이는 동일한 패턴을 따르는 CSS 그리드 레이아웃과 같은 다른 레이아웃 방식을 다룰 때도 매우 유용할 것입니다.
플렉스박스를 사용하여 레이아웃이 잡힌 문서의 영역을 플렉스 컨테이너(flex container)라고 부릅니다. 플렉스 컨테이너를 만들려면 해당 영역의 display 속성을 flex로 설정하면 됩니다. 이렇게 하면, 그 컨테이너의 직계 자식 요소들은 자동으로 플렉스 아이템(flex items)이 됩니다.
컨테이너 자체가 인라인(inline)으로 표시될지, 블록(block) 서식 문맥으로 표시될지도 직접 제어할 수 있습니다. 인라인 플렉스 컨테이너를 원한다면 inline flex나 inline-flex를 사용하고, 블록 레벨 플렉스 컨테이너를 원한다면 block flex나 flex를 사용하면 됩니다.
CSS의 모든 속성들이 그렇듯, 플렉스박스에도 기본적으로 정의된 초깃값(initial values)이 있습니다. 그래서 새로운 플렉스 컨테이너를 만들면, 내부의 콘텐츠는 다음과 같이 행동합니다:
flex-direction 속성의 기본값이 row이기 때문입니다.)flex-grow 기본값은 0이고, flex-shrink 기본값은 1이기 때문입니다.)align-items 속성의 기본값이 stretch이기 때문입니다.)flex-basis 속성 기본값은 auto입니다. 이는 기본적으로 가로 쓰기 모드에서는 아이템의 width 값을, 세로 쓰기 모드에서는 height 값을 사용한다는 뜻입니다. 만약 해당 너비/높이도 auto라면, 아이템이 가진 콘텐츠(content) 자체의 크기가 기준이 됩니다.flex-wrap 속성의 기본값이 nowrap이기 때문입니다.) 만약 아이템들의 총 너비/높이가 부모 컨테이너의 크기를 초과하면, 줄바꿈되지 않고 밖으로 삐져나가는(overflow) 현상이 발생합니다.결과적으로 여러분의 아이템들은 주축 방향을 따라 각자 콘텐츠가 차지하는 크기만큼의 공간만 가지면서 한 줄로 쫙 정렬됩니다. 아이템들이 컨테이너보다 많아도 다음 줄로 넘어가지 않고 컨테이너 밖으로 넘치게 됩니다. 또한 어떤 아이템의 높이가 다른 것들보다 높다면, 나머지 아이템들도 그 가장 높은 아이템에 맞춰 교차축 방향으로 쭉 늘어나게 됩니다.
아래 코드에서 이 기본 동작을 확인해 보세요. (부모에 display: flex;만 주었을 때 일어나는 현상입니다!)
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three <br />has <br />extra <br />text</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
}
.box {
border: 2px dotted rgb(96 139 168);
display: flex;
}
플렉스 컨테이너에 flex-direction 속성을 추가하면 플렉스 아이템들이 표시되는 방향을 바꿀 수 있습니다. flex-direction: row-reverse로 설정하면 아이템들은 여전히 행(row)을 따라 배열되지만, 시작선과 끝선이 반대로 뒤집힙니다.
flex-direction을 column으로 변경하면 주축이 세로로 바뀌어 아이템들이 열(column)로 배열됩니다. column-reverse로 설정하면 이 역시 시작선과 끝선이 뒤집혀 아래에서 위로 쌓이게 됩니다.
아래 코드에서는 flex-direction이 row-reverse로 설정되어 있습니다.
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
}
.box {
border: 2px dotted rgb(96 139 168);
display: flex;
flex-direction: row-reverse;
}
플렉스박스는 1차원 모델이지만, 플렉스 아이템들이 여러 줄에 걸쳐 감싸지도록(wrap) 만들 수 있습니다. 이렇게 여러 줄로 감싸질 때, 여러분은 각각의 줄을 마치 독립적인 새로운 플렉스 컨테이너인 것처럼 생각해야 합니다. 공간 분배는 오직 그 줄 안에서만 일어날 뿐, 이전 줄이나 다음 줄의 아이템과는 연관 지어 정렬되지 않기 때문입니다. (이것이 2차원 모델인 Grid와의 결정적 차이입니다!)
줄바꿈이 일어나게 하려면 flex-wrap 속성을 추가하고 값으로 wrap을 주면 됩니다. 이제 아이템들이 한 줄에 다 들어가기에 너무 크다면, 자연스럽게 다음 줄로 넘어가게 됩니다.
기본값인 nowrap을 그대로 두면, 아이템들은 부모 컨테이너에 억지로 맞춰지기 위해 줄어들게 됩니다. 앞서 배운 기본값 flex-shrink: 1 때문에 아이템들이 쪼그라들 수 있는 것이죠. 만약 아이템들에 줄어들지 못하게 하는 설정이 되어 있거나 더 이상 줄어들 수 없는 상황에서 nowrap을 쓰면, 아이템들이 부모 박스를 뚫고 나가는 오버플로우(overflow) 현상이 발생합니다.
💡 강사의 실무 포트폴리오 팁!
독후감 사이트에서 책 목록을 보여주는 카드 UI를 만들 때display: flex; flex-wrap: wrap;조합은 필수입니다. 창 크기를 줄였을 때 카드들이 찌그러지지 않고 자연스럽게 밑으로 떨어져서 반응형 레이아웃을 아주 쉽게 구성할 수 있거든요.
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
width: 200px;
}
.box {
width: 500px;
border: 2px dotted rgb(96 139 168);
display: flex;
flex-wrap: wrap;
}
flex-direction과 flex-wrap 이 두 속성을 합쳐서 flex-flow라는 단축 속성(shorthand) 하나로 쓸 수도 있습니다.
첫 번째 값으로는 row 같은 방향을, 두 번째 값으로는 wrap 같은 줄바꿈 여부를 적어주면 됩니다.
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
width: 200px;
}
.box {
width: 500px;
border: 2px dotted rgb(96 139 168);
display: flex;
flex-flow: row wrap; /* direction과 wrap을 한 번에! */
}
각 플렉스 아이템이 인라인 방향으로 차지하는 크기를 통제하기 위해, 우리는 아이템 자체에 직접 다음 세 가지 속성을 부여할 수 있습니다:
이 속성들을 제대로 이해하려면 먼저 가용 공간(available space, 남는 여백)이라는 개념을 알아야 합니다. 우리가 이 flex 속성들의 값을 변경한다는 것은, 결과적으로 '부모 컨테이너 안에 남은 가용 공간을 아이템들 사이에 어떻게 분배할 것인가'를 결정하는 행위입니다.
예를 들어 너비가 500px인 부모 컨테이너 안에 너비가 각각 100px인 3개의 아이템이 있다고 가정해 봅시다. 아이템들이 차지하는 총 공간은 300px입니다. 그러면 부모 안에는 200px의 가용 공간(남는 여백)이 생깁니다. 아무 설정도 건드리지 않았다면(초깃값 상태라면), 플렉스박스는 이 200px의 남는 공간을 마지막 아이템 뒤에 빈 공간으로 내버려 둡니다.
만약 이 빈 공간을 그냥 두는 대신, 아이템들이 스스로 쑥쑥 자라나서(grow) 빈 공간을 꽉 채우도록 만들고 싶다면 어떻게 해야 할까요? 바로 아이템 자체에 적용하는 flex 속성들을 사용해서 이 남는 공간을 형제 아이템들끼리 어떻게 나눠 가질지 규칙을 정해주면 됩니다.
flex-basis는 아이템이 공간을 분배받거나 빼앗기기 전의 '기본적인 크기(base size)'를 정의합니다. 이 속성의 기본값은 auto입니다. auto일 때 브라우저는 해당 아이템에 고정된 크기(width나 height)가 있는지 확인합니다. 위에서 든 예제에서 모든 아이템은 100px의 width를 가지고 있었으므로, 이 100px이 바로 그들의 flex-basis로 사용됩니다.
만약 아이템에 고정된 크기가 명시되어 있지 않다면, 그 아이템이 품고 있는 콘텐츠의 크기(content size)가 flex-basis가 됩니다. 이것이 바로 부모에 display: flex만 주었을 때, 자식 아이템들이 글자 길이 등 자신의 콘텐츠 길이만큼만 딱 자리를 차지하고 옹기종기 모이는 이유입니다.
flex-grow 속성을 0보다 큰 양의 정수로 설정하면, 부모 안에 남는 가용 공간이 있을 때 아이템이 자신의 flex-basis 크기를 넘어서 쭈욱 늘어날(grow) 수 있게 됩니다.
남는 공간을 한 아이템이 독식할지, 아니면 다른 아이템들과 사이좋게 나눠 가질지는 각각의 아이템들이 가진 flex-grow 비율에 따라 결정됩니다.
만약 세 개의 아이템 모두에 flex-grow: 1을 주었다면, 남은 200px의 공간은 세 명에게 똑같이 분배되어 세 아이템 모두 동일한 비율로 늘어납니다. 반면 첫 번째 아이템에 flex-grow: 2를 주고 나머지 두 아이템에 flex-grow: 1을 주었다면? 총 비율은 4(2+1+1)가 됩니다. 따라서 남은 200px 중 4분의 2(100px)를 첫 번째 아이템이 가져가고, 나머지 두 개가 각각 4분의 1(50px)씩 나누어 가져가며 늘어나게 됩니다.
flex-grow가 남는 공간을 추가하는 문제를 다룬다면, flex-shrink는 공간이 부족할 때 어떻게 크기를 깎아낼 것인가를 제어합니다. 컨테이너 안에 아이템들을 다 넣을 공간이 부족할 때 flex-shrink가 양수로 설정되어 있다면, 그 아이템은 자신의 flex-basis 크기보다 더 작게 쪼그라들(shrink) 수 있습니다. flex-grow와 마찬가지로 더 큰 flex-shrink 값을 가진 아이템이 낮은 값을 가진 형제들보다 더 빠르고 많이 쪼그라들게 됩니다.
💡 노트 (Note):
flex-grow와flex-shrink에 넣는 값들은 '비율(proportions)'입니다. 예를 들어 한 아이템이 다른 아이템보다 2배 더 빠르게 늘어나길 원한다면flex: 2 1 200px를 줄 수 있습니다. 하지만 원한다면flex: 20 1 200px과flex: 10 1 200px처럼 20과 10을 사용해도 완전히 똑같이 작동합니다.
실무에서 flex-grow, flex-shrink, flex-basis를 각각 따로 적는 경우는 거의 없습니다. 대신 이 셋을 하나로 합친 flex 단축 속성(shorthand)을 주로 사용합니다. 이 flex 단축 속성은 무조건 flex-grow, flex-shrink, flex-basis 순서로 3개의 값을 나란히 적어줍니다.
<div class="box">
<div class="one">One</div>
<div class="two">Two</div>
<div class="three">Three</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
}
.box {
border: 2px dotted rgb(96 139 168);
display: flex;
}
.one {
flex: 1 1 auto;
}
.two {
flex: 1 1 auto;
}
.three {
flex: 1 1 auto;
}
또한, 가장 자주 쓰이는 패턴들을 위해 미리 정의된 특별한 단축 키워드들도 있습니다. 실무 코드나 튜토리얼에서 이런 키워드들을 자주 보시게 될 텐데, 대부분의 상황에서는 이 키워드들만으로도 충분합니다:
flex: initial (기본값 동작. flex: 0 1 auto와 동일. 안 늘어나고, 줄어들긴 하며, 콘텐츠 크기를 기준으로 함)flex: auto (flex: 1 1 auto와 동일. 남는 공간이 있으면 꽉 채우게 늘어나고, 부족하면 줄어듦)flex: none (flex: 0 0 auto와 동일. 늘어나지도 줄어들지도 않는 완전 고정 상태)flex: <양수> (예: flex: 1)튜토리얼에서 흔히 볼 수 있는 flex: 1이나 flex: 2 같은 문법은 사실 flex: 1 1 0이나 flex: 2 1 0을 쓴 것과 똑같습니다. 즉 flex-basis가 0이 되어 아이템의 시작 크기가 완전히 0부터 시작하고, 순수하게 flex-grow의 비율대로만 공간을 나눠 가지게 됩니다. 남는 공간을 계산할 때 기존 콘텐츠의 크기를 완전히 무시하고 정확한 비율로 나누고 싶을 때 즐겨 쓰는 기법입니다.
플렉스박스의 또 다른 핵심 기능은 바로 주축(main axis)과 교차축(cross axis)을 기준으로 아이템들을 원하는 대로 정렬하고 공간을 분배하는 능력입니다. 이 정렬 속성들은 플렉스 아이템 자체가 아니라 부모인 플렉스 컨테이너에 부여해야 한다는 점을 꼭 기억하세요!
align-items 속성은 교차축(cross axis)을 기준으로 모든 플렉스 아이템들을 정렬합니다.
이 속성의 기본값은 stretch입니다. 그래서 플렉스 아이템들이 따로 높이를 지정하지 않아도 부모 컨테이너의 높이만큼 쭈욱 늘어나는 것입니다.
이 값을 flex-start로 설정하면 아이템들이 교차축의 시작 부분(보통 위쪽)에 달라붙고, flex-end로 설정하면 끝 부분(아래쪽)에 달라붙게 됩니다. 그리고 대망의 center를 주면 중앙에 완벽하게 정렬되죠!
stretch (기본값)flex-start (또는 start)flex-end (또는 end)center<div class="box">
<div>One</div>
<div>Two</div>
<div>Three <br />has <br />extra <br />text</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
}
.box {
width: 500px;
height: 130px;
border: 2px dotted rgb(96 139 168);
display: flex;
align-items: flex-start; /* 교차축(세로) 위쪽 정렬 */
}
참고로 align-items는 부모에 줘서 전체를 컨트롤하지만, 만약 특정 아이템 하나만 다르게 정렬하고 싶다면 그 아이템에 직접 align-self 속성을 부여하면 됩니다.
justify-content 속성은 flex-direction이 정해준 주축(main axis)을 기준으로 아이템들을 정렬하고 남는 공간을 분배합니다.
기본값은 flex-start라서 아이템들이 모두 왼쪽(시작점)으로 몰려 있습니다. 이를 flex-end로 바꾸면 끝쪽으로 밀려나고, center로 바꾸면 주축을 기준으로 정중앙에 모입니다.
가장 유용하게 쓰이는 값들은 공간을 분배하는 값들입니다:
space-between: 양 끝 아이템은 양쪽 끝에 바짝 붙이고, 나머지 여백을 아이템들 사이에 똑같이 나눠줍니다. (헤더의 로고와 메뉴를 양끝으로 보낼 때 아주 많이 씁니다!)space-around: 각 아이템의 좌우에 동일한 여백을 줍니다. (양 끝에 있는 여백은 아이템 사이의 여백보다 절반 크기가 됩니다.)space-evenly: 양 끝을 포함해 모든 아이템 사이의 여백 공간이 완벽하게 동일하도록 띄워줍니다.<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
}
.box {
border: 2px dotted rgb(96 139 168);
display: flex;
justify-content: flex-start; /* 주축(가로) 정렬 방법 */
}
justify-items 속성은 Grid 레이아웃에서는 쓰이지만 플렉스박스 레이아웃에서는 완전히 무시됩니다. (이것도 면접에 가끔 나오는 함정이니 기억해 두세요!)
place-items 속성은 align-items와 justify-items를 한 번에 쓰는 단축 속성입니다. 하지만 방금 말씀드렸듯 플렉스박스에서는 justify-items가 무시되므로, 플렉스 컨테이너에 이 속성을 쓰면 사실상 align-items만 작동하는 셈이 됩니다.
대신 place-content라는 단축 속성이 있습니다. 이는 align-content와 justify-content를 한 번에 설정해 줍니다. (align-content는 flex-wrap: wrap으로 인해 여러 줄이 생겼을 때 그 여러 줄 묶음 전체를 교차축에서 정렬하는 속성입니다.)
이 글을 읽고 나면 플렉스박스의 기본적인 기능들에 대해 확실한 이해를 가지셨을 것입니다. 다음 글에서는 이 명세서가 CSS의 다른 부분들(예: Grid 등)과 어떻게 연관되어 작동하는지 살펴보겠습니다.
이 페이지가 도움이 되셨나요?
기여하는 방법 알아보기 (Learn how to contribute)
이 페이지는 MDN 기여자들에 의해 2025년 12월 5일에 마지막으로 수정되었습니다 (MDN contributors).