안녕하세요! 프론트엔드 개발 강사입니다. 이번에 가져오신 내용은 현대 웹 레이아웃의 핵심 중 하나인 Flexbox(플렉스박스)가 다른 CSS 레이아웃 방식들과 어떤 관계를 맺고 있는지에 대한 문서군요!
우리가 Flexbox를 배우다 보면 "Grid랑은 뭐가 다르지?", "float를 썼던 요소에 flex를 주면 어떻게 될까?" 같은 궁금증이 생기기 마련입니다. 영문 문서라 조금 딱딱하게 느껴지셨을 텐데, 제가 실무에서 겪은 경험과 꿀팁들을 섞어서 아주 쉽고 명쾌한 구어체로 전부 번역해 드릴게요! 자, 시작해 볼까요? 😊
이 문서에서는 Flexbox(플렉스박스)가 다른 CSS 모듈들과 어떻게 어우러져 작동하는지 살펴보겠습니다. Flexbox를 제대로 배우기 위해 꼭 함께 알아두어야 할 다른 사양(specifications)은 무엇인지, 그리고 Flexbox가 다른 레이아웃 방식들과 어떤 점에서 다른지 알아볼게요.
많은 분들이 Flexbox를 처음 찾게 되는 이유는, 컨테이너 안의 아이템들을 '제대로 정렬'하고 싶어서일 겁니다. Flexbox는 교차 축(cross axis)에서 아이템을 정렬(align)하고 중심 축(main axis)에서 아이템의 간격을 분배(justify)할 수 있는 아주 유용한 속성들을 제공하죠.
사실 Flexbox는 원래 독자적인 플렉스 박스 레이아웃(flexible box layout) 모듈로 정의되었지만, 이제 다른 레이아웃 방식(예: Grid)에서도 공통으로 쓰이는 정렬 관련 속성과 값들은 CSS 박스 정렬(CSS box alignment) 모듈이라는 곳으로 따로 분리되어 정의되어 있습니다. 이 박스 정렬 모듈은 Flexbox뿐만 아니라 모든 레이아웃 시스템에서 정렬(alignment), 간격 분배(justification), 틈새(gaps), 여백(gutters)이 어떻게 작동하는지를 상세히 다룹니다.
💡 강사의 실무 팁! "align-items랑 justify-content는 Flex 전용이 아닙니다!"
많은 분들이align-items나gap같은 속성을 Flexbox 전용이라고 생각하시는데, 사실 이 속성들은 CSS Box Alignment 모듈 소속이에요. 그래서 CSS Grid에서도 똑같은 이름, 똑같은 느낌으로 사용할 수 있답니다! (만약 두 사양서에 같은 기능이 겹쳐서 정의되어 있다면, 항상 박스 정렬 모듈의 내용이 Flexbox 모듈의 내용을 덮어쓰고(supersede) 우선한다는 점을 기억하세요.)
Flexbox 기본 개념(Basic concepts of flexbox) 문서에서 Flexbox는 쓰기 모드(writing mode)를 인지한다고 설명한 바 있습니다. 쓰기 모드는 CSS 쓰기 모드(CSS writing modes) 모듈에 아주 자세히 정의되어 있는데, 전 세계에 존재하는 다양한 쓰기 방식을 CSS가 어떻게 지원하는지 설명해 줍니다. 우리는 쓰기 모드가 바뀔 때 Flex 레이아웃에 어떤 영향을 미치는지 알아야 합니다. 쓰기 모드가 문서 내에서 블록(blocks)들이 배치되는 '방향' 자체를 바꿔버리기 때문이죠. 새로운 레이아웃 방식을 익히려면 블록 방향(block direction)과 인라인 방향(inline direction)을 이해하는 것이 핵심입니다.
참고로, 우리가 다른 쓰기 모드를 사용하는 언어로 콘텐츠를 만들 때뿐만 아니라, 완전히 창의적이고 독특한 디자인을 만들기 위해서도 문서의 쓰기 모드를 변경할 수 있습니다. CSS 쓰기 모드 모듈은 텍스트가 가로로(왼쪽에서 오른쪽, 오른쪽에서 왼쪽) 써질지, 아니면 세로로(위에서 아래로) 써질지를 정의합니다. 이는 다국어 지원(internationalization)에 필수적이지만, 디자인적인 측면에서도 아주 유용하게 쓰일 수 있습니다.
쓰기 모드 사양은 writing-mode 속성에 쓸 수 있는 다음과 같은 값들을 정의합니다. 이 값들은 해당 쓰기 모드로 콘텐츠가 텍스트를 나열할 때, 블록들이 페이지에 배치되는 방향을 바꿔줍니다. 아래의 라이브 예제에서 코드를 직접 수정해 보면서 Flex 레이아웃이 어떻게 변하는지 관찰해 보세요!
horizontal-tb (가로 쓰기, 위에서 아래로 - 기본값)vertical-rl (세로 쓰기, 오른쪽에서 왼쪽으로)vertical-lr (세로 쓰기, 왼쪽에서 오른쪽으로)sideways-rl (옆으로 눕혀 쓰기)sideways-lr(MDN Playground에서 실행해보기 (Play))
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
.box {
width: 500px;
border: 2px dotted rgb(96 139 168);
display: flex;
writing-mode: horizontal-tb; /* 이 부분을 vertical-rl 등으로 바꿔보세요! */
}
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
}
📝 참고: 현재
sideways-rl과sideways-lr값은 Firefox 브라우저에서만 지원됩니다.
보통 문서 전체를 다른 쓰기 모드로 바꿀 때는 CSS의 writing-mode 속성을 직접 쓰기보다는, HTML 최상위 <html> 요소에 문서의 언어와 기본 텍스트 방향을 나타내는 dir 속성과 lang 속성을 추가하는 방식을 사용해야 합니다. 이렇게 하면 CSS 파일이 로드되지 않는 최악의 상황에서도 문서가 올바른 방향으로 표시될 수 있기 때문입니다.
CSS의 어떤 속성들은 오로지 '표준 블록 레이아웃 시스템'에서만 작동하도록 설계되었기 때문에, Flex 레이아웃 환경에서는 아무런 의미를 갖지 못하고 무시됩니다.
display: flex가 적용된 요소 자체는 (겉으로 볼 때는) 컨테이닝 블록(containing block)을 형성하는 다른 일반적인 블록 레벨 컨테이너와 거의 똑같이 행동합니다. 밖에서 플롯(float)된 요소가 이 안으로 침범할 수 없고, 이 컨테이너의 마진은 다른 요소의 마진과 병합(collapse)되지 않습니다.
Flex 컨테이너 '안에' 있는 Flex 아이템들에 대해 이야기해 볼까요? 만약 어떤 요소에 float이나 clear가 적용되어 있었는데, 그 부모 요소에 display: flex가 짠! 하고 적용되어 그 요소가 Flex 아이템이 되었다면? 적용되어 있던 float과 clear는 즉시 무효화되어 작동하지 않게 됩니다. 마치 한 번도 float 된 적 없는 것처럼 일반 흐름(normal flow)에서 빠져나오지 않게 되죠.
또한, inline-block이나 테이블 레이아웃에서 수직 정렬을 위해 흔히 쓰던 vertical-align 속성 역시 Flex 아이템이 되는 순간 먹히지 않습니다. 그 대신 Flexbox 전용 정렬 속성(align-items, align-self 등)을 사용하셔야 합니다.
👨🏫 강사의 실무 팁! "Flex 아이템의 마법"
초보 시절 많이 하는 실수 중 하나가 Flex 아이템한테 자꾸float: left나vertical-align: middle을 주고 "왜 안 움직이지?" 하는 거예요. 부모가display: flex를 선언하는 순간, 자식 요소들은 과거의 신분(블록이든, 인라인이든, float이든)을 다 버리고 오직 "Flex 아이템" 이라는 새로운 신분으로 다시 태어나게 됩니다. 그러니까 정렬은 Flex의 룰(justify-content,align-items)에 맡기세요!
다음의 라이브 예제에서는 세 개의 자식 요소들에 float: left가 적용되어 있고, 그 부모 컨테이너에는 display: flex가 적용되어 있습니다.
만약 여러분이 코드에서 display: flex를 지워버리면, 내부의 float를 해제(clearing) 해주는 코드가 없기 때문에 부모 .box 요소의 높이가 0으로 쪼그라드는(collapse) 현상을 보게 될 겁니다. (즉, float이 살아서 작동하고 있다는 증거죠.)
다시 display: flex를 원상 복구 시키면 쪼그라드는 현상이 마법처럼 사라집니다! 자식 요소들이 Flex 아이템으로 변신하면서 float 속성이 무력화되었기 때문입니다.
(MDN Playground에서 실행해보기 (Play))
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
.box {
width: 500px;
border: 2px dotted rgb(96 139 168);
display: flex; /* 이 줄을 지웠다 다시 써보며 테스트해 보세요! */
}
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
float: left; /* 부모가 flex일 때는 이 float이 완전히 무시됩니다. */
}
CSS 그리드 레이아웃(CSS grid layout)과 Flexbox는 수많은 속성과 값들을 공유합니다. 하지만 동작이 엇갈리는 속성도 있습니다. 만약 Flex 아이템이었던 요소가 어쩌다 Grid 아이템으로 신분이 바뀐다면, 자식 요소에 부여되었던 flex-end 같은 flex 관련 값들은 모조리 무시됩니다. 앞서 언급했듯이, 두 레이아웃 방식 모두에서 작동하는 '박스 정렬 모듈(box alignment module)'에 정의된 값들이 오직 Flexbox에만 정의된 값들보다 우선시되기 때문입니다.
가장 많이 받는 질문 중 하나가 바로 "Flexbox와 CSS Grid 레이아웃의 차이가 뭔가요?"입니다. 왜 때로는 똑같은 일을 하는 것처럼 보이는 두 가지의 사양(specifications)이 존재하는 걸까요?
이 질문에 대한 가장 명쾌한 대답은 사양서 자체에 정의되어 있습니다. Flexbox는 1차원(1D) 레이아웃 방식이고, Grid 레이아웃은 2차원(2D) 레이아웃 방식입니다.
아래 예제는 Flex 레이아웃을 사용했습니다. 이미 기본 개념 문서에서 설명했듯, Flex 아이템들은 부모 공간이 좁으면 밑으로 줄바꿈(wrap)되도록 허용할 수 있습니다. 하지만 일단 줄바꿈이 일어나고 나면, 쪼개진 각각의 줄(line)은 마치 자기가 완전히 독립적인 Flex 컨테이너인 것처럼 행동합니다. 남은 공간을 분배할 때, Flexbox는 다른 줄에 있는 아이템들이 어디에 배치되어 있는지 전혀 신경 쓰지 않고 오직 '자기 줄' 안에서만 정렬을 시도합니다. (그래서 위아래 줄의 아이템들끼리 세로 선이 딱딱 맞아떨어지지 않죠.)
(MDN Playground에서 실행해보기 (Play))
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
<div>Four</div>
<div>Five</div>
<div>Six</div>
<div>Seven</div>
</div>
.box {
border: 2px dotted rgb(96 139 168);
display: flex;
flex-wrap: wrap; /* 자리가 모자라면 밑으로 떨어집니다 */
padding: 1em;
}
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
background-color: rgb(96 139 168 / 0.2);
padding: 1em;
flex: 1 1 200px; /* 기본 너비를 200px로 하되, 남는 공간을 똑같이 나눠가집니다 */
}
반면에 Grid를 사용해서 아주 비슷한 레이아웃을 만들면 어떨까요? Grid에서는 행(rows)과 열(columns) 두 방향 모두에서 레이아웃을 완벽하게 통제할 수 있습니다.
(MDN Playground에서 실행해보기 (Play))
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
<div>Four</div>
<div>Five</div>
<div>Six</div>
<div>Seven</div>
</div>
.box {
border: 2px dotted rgb(96 139 168);
padding: 1em;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, auto)); /* 200px 이상의 칸을 자동으로 꽉 채워 만듭니다 */
}
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
padding: 1em;
background-color: rgb(96 139 168 / 0.2);
}
이 두 예제는 두 레이아웃 방식 사이의 또 다른 핵심적인 차이점을 짚어줍니다.
flex-grow, flex-shrink 등)에 대한 통제권은 각각의 아이템들 자신에게 주어져 있습니다.어떤 경우에는 두 가지 방식을 모두 사용할 수도 있습니다. 두 방식 모두에 능숙해지면, 각 레이아웃의 요구사항에 따라 어떤 방식이 더 적합할지 본능적으로 알게 되고, 결국 여러분의 CSS 파일에는 두 가지 방식이 적절히 섞여서 쓰이게 될 겁니다. 이 세계에 무조건적인 정답이나 오답은 드무니까요.
💡 강사의 실무 팁! "Flex vs Grid 선택 가이드"
"어떨 때 Flex를 쓰고 어떨 때 Grid를 써야 하나요?"
아주 간단한 룰을 알려드릴게요.
여러 줄로 꺾이는 레이아웃을 만들 때, 아랫줄에 있는 아이템들이 윗줄에 있는 아이템들과 수직선(열)이 딱딱 예쁘게 맞아떨어져야 한다면 무조건 Grid(2차원)를 쓰세요. 반대로, 가로 한 줄짜리 메뉴바를 만들거나 요소 안의 콘텐츠 크기에 따라 자유롭게 넓이가 유동적으로 변해야 한다면 Flex(1차원)가 훨씬 유리합니다.
단, "작은 컴포넌트에는 Flexbox를 쓰고 크고 거대한 뼈대 레이아웃에는 Grid를 써야 한다" 같은 절대적인 규칙은 없습니다. 아주 작은 컴포넌트라도 2차원 격자 형태가 필요할 수 있고, 커다란 레이아웃이라도 1차원 흐름이 더 잘 어울릴 수 있습니다. 직접 둘 다 써보며 부딪혀 보세요. 여러분에겐 무기가 두 개나 생겼으니 마음껏 활용하시기 바랍니다.
Grid와 Flexbox의 더 자세한 비교를 원하신다면 Grid 레이아웃과 다른 레이아웃 방법의 관계(Relationship of grid layout to other layout methods) 문서를 꼭 읽어보세요.
display 속성의 contents라는 값은 사양서에서 다음과 같이 설명하고 있습니다:
"이 요소 자체는 아무런 박스도 생성하지 않지만, 이 요소의 자식들과 가상 요소들은 정상적으로 박스를 생성합니다. 박스 생성 및 레이아웃을 계산할 때, 이 요소는 마치 문서 트리에서 통째로 사라지고 그 자리를 자신의 자식들과 가상 요소들로 대체한 것처럼 취급되어야 합니다."
쉽게 말해, display: contents는 해당 요소(껍데기) 자체의 박스는 화면이나 레이아웃 계산에서 완전히 날려버리고, 그 안에 들어있던 알맹이(자식 요소)들만 부모의 한 단계 위로 끌어올려서 부모의 레이아웃 방식에 직접 참여하게 만드는 마법 같은 속성입니다. 예제를 보면 훨씬 이해가 쉬울 거예요.
다음 라이브 예제에는 세 개의 Flex 아이템을 가진 Flex 컨테이너가 있습니다. 그런데 그 세 번째 아이템(div.nested) 안에는 또다시 두 개의 하위 자식(div)이 중첩되어 들어있죠. 원래대로라면 이 하위 자식들은 Flex 레이아웃에 참여할 수 없습니다. Flex 레이아웃은 오직 Flex 컨테이너의 '직계 자식'들에게만 적용되니까요.
그런데 중첩된 자식들을 감싸고 있는 껍데기(div.nested)에 display: contents를 추가해 보세요. 껍데기 요소가 레이아웃에서 뿅 하고 사라지면서, 그 안에 갇혀있던 두 명의 손자 요소들이 마치 할아버지(Flex 컨테이너)의 직계 자식인 것처럼 위로 끌어올려져(moved up) 나란히 Flex 레이아웃으로 정렬되는 것을 볼 수 있습니다! (직접 display: contents 줄을 지웠다가 써보며 차이를 확인해 보세요.)
👨🏫 강사의 실무 팁! "display: contents는 언제 쓰나요?"
리액트(React)나 뷰(Vue) 같은 프레임워크를 쓰다 보면 어쩔 수 없이 의미 없는<div>껍데기(래퍼)로 컴포넌트를 감싸야 할 때가 있습니다. 이 껍데기 때문에 Flex나 Grid 레이아웃이 꼬여버릴 때, 껍데기에display: contents를 주면 레이아웃 트리에서 이 껍데기만 유령처럼 싹 지워버릴 수 있어요!
(MDN Playground에서 실행해보기 (Play))
<div class="box">
<div>One</div>
<div>Two</div>
<div class="nested">
<div>Sub-item 1</div>
<div>Sub-item 2</div>
</div>
</div>
.box > * {
border: 2px solid rgb(96 139 168);
border-radius: 5px;
padding: 1em;
background-color: rgb(96 139 168 / 0.2);
}
.box {
border: 2px dotted rgb(96 139 168);
padding: 1em;
display: flex;
}
.nested {
background-color: orange;
display: contents; /* 이 껍데기를 유령으로 만듭니다! */
}
주의할 점은, 이 속성은 오직 '박스(레이아웃)'만을 없앨 뿐이라는 겁니다. 손자 요소들이 레이아웃 상으로는 Flex 컨테이너의 직계 자식처럼 행동하게 되었지만, CSS 선택자 체계에서까지 진짜 직계 자식이 된 것은 아닙니다. 위 코드에서 우리는 .box > * (직계 자식 선택자)를 사용해 파란색 배경과 테두리를 주었죠? 이 스타일은 중첩된 손자들에게는 적용되지 않은 걸 볼 수 있습니다. 그들은 Flex 레이아웃 규칙은 따르게 되었지만, CSS 구조상으로는 여전히 직계 자식이 아니기 때문에 저 스타일을 받지 못하는 것입니다.
또한 껍데기 박스(.nested) 자체가 레이아웃에서 사라졌기 때문에, 이 껍데기에 background-color 같은 속성을 줘도 화면에 아무것도 나타나지 않습니다. 라이브 예제에서 display: contents를 지워보면 그제서야 껍데기 요소가 원래 가지고 있던 주황색(orange) 배경이 나타나는 걸 확인할 수 있습니다. 박스가 사라졌으니 그 박스의 배경색도 사라지는 게 당연하겠죠.
⚠️ 경고! (접근성 관련 주의사항)
현재 일부 브라우저에서는display: contents를 사용하면 해당 요소의 의미(semantics)를 접근성 트리(accessibility tree)에서 실수로 날려버리는 치명적인 버그가 있습니다. (자식 콘텐츠는 남아있지만요). 즉, 시각장애인이 사용하는 스크린 리더기가 이 요소가 무엇인지 읽어주지 못할 수도 있습니다. 이 속성을 사용할 때는 항상 접근성에 문제가 없는지 주의해야 합니다. 자세한 내용은display: contents레퍼런스와 display: contents considered harmful 글을 참고하세요.
이 페이지가 도움이 되셨나요? [네 (Yes)] / [아니요 (No)]
기여하는 방법 알아보기 (Learn how to contribute)
이 페이지는 2025년 12월 15일에 MDN 기여자들 (MDN contributors)에 의해 마지막으로 수정되었습니다.