문서 읽는 데 63분 · B4

B-4: Flexbox 완전 정복

전체 23강 중 8강 · HTML·CSS·JS
난이도 · 입문

안녕하세요, 홍순구 튜터입니다. 지난 시간에 우리는 상자를 "어디에 놓을지" 큰 그림을 잡았어요. display로 상자의 흐름을 정하고, position으로 상자를 원하는 위치에 고정했죠. 스크롤해도 따라오는 상단 네비게이션 바를 fixed로 붙이고, 사진 위에 NEW 뱃지를 absolute로 얹으면서 배치 전담 파일 layout.css도 새로 만들었고요.

그런데 지난 시간 끝에 솔직하게 털어놨어요. 가로 정렬은 아직 거칠다고요. 메뉴 항목을 inline-block으로 가로로 눕히긴 했지만 간격이 들쭉날쭉하고, 위아래 정렬도 어설펐어요. 로그인 폼은 margin auto로 가로 가운데까지만 왔지, 화면 정중앙에 띄우진 못했고요. 게시물의 좋아요·댓글·공유 아이콘을 한 줄에 가지런히 늘어놓는 일도 아직 손도 못 댔죠.

생각해보면 이 셋은 다 같은 종류의 일이에요. "여러 요소를 한 줄(또는 한 칸)에 놓고, 간격을 고르게 벌리고, 가운데로 모으는" 일이거든요. 박스 모델의 여백 조절이나 position의 좌표 찍기로는 이걸 깔끔하게 하기 어려워요. 이런 정렬을 전문으로 다루는 도구가 따로 있어요. 오늘의 주인공, Flexbox(플렉스박스) 예요.

비유를 하나 들어볼게요. 옷장 서랍에 양말을 정리한다고 해봐요. 서랍(부모 상자)에 "여기 들어가는 건 다 한 줄로 세워, 간격은 똑같이 띄우고, 위아래 가운데로 맞춰" 하고 규칙을 딱 정해두면, 양말(자식 상자)을 아무리 넣고 빼도 알아서 그 규칙대로 줄을 맞춰요. Flexbox가 정확히 이거예요. 부모 상자에 정렬 규칙을 한 번 정해두면, 그 안의 자식들이 알아서 줄을 맞추는 도구죠. 자식 하나하나에 일일이 위치를 지정할 필요가 없어요.

사용법도 놀랄 만큼 간단해요. 부모 상자에 display: flex 한 줄만 주면 그 순간부터 그 상자는 "정렬 사령관"이 돼요. 그다음 justify-content로 좌우 간격을, align-items로 위아래 정렬을, gap으로 항목 사이 간격을 정하면 끝이에요. 오늘 이 규칙들을 하나씩 손에 익혀서, 지난 시간 미뤄둔 세 가지 숙제 — 메뉴 정렬, 로그인 정중앙, 좋아요·댓글 한 줄 — 를 전부 해결할 거예요.

오늘도 새 파일을 하나 시작해요. 배치의 "위치"는 layout.css가 맡지만, 게시물을 진짜 인스타그램 카드처럼 보이게 하는 "생김새"(흰 배경·테두리·둥근 모서리) 는 오늘 만들 components.css가 전담합니다. 카드·버튼 같은 화면 조각의 스타일을 따로 모아두는 파일이에요.

💡 오늘 수업의 핵심 — "부모 상자에 display: flex 한 줄을 주고, justify-content·align-items·gap으로 자식들을 정렬해서 인스타그램의 로그인 센터링과 피드 카드를 완성한다" 🎯

🎯 학습 목표

  • display: flex로 부모 상자를 flex 컨테이너로 만들고, 그 안의 자식들이 자동으로 한 줄에 나란히 놓이는 원리를 이해합니다.
  • 주축(main axis)과 교차축(cross axis)이라는 Flexbox의 두 방향을 구분하고, flex-direction으로 줄의 방향을 바꿉니다.
  • justify-content로 주축(가로) 방향의 간격과 정렬을 정합니다.
  • align-items로 교차축(세로) 방향의 정렬을 맞춥니다.
  • gap으로 항목 사이 간격을 margin 없이 깔끔하게 벌립니다.
  • justify-content·align-items를 둘 다 center로 주고 min-height를 더해, 로그인 폼을 화면 정중앙에 띄웁니다.
  • flex-grow·flex-shrink·flex-basis로 자식들이 남는 공간을 나눠 갖게 해서, 댓글 입력칸은 늘어나고 게시 버튼은 고정되게 만듭니다.
  • flex-wrap으로 항목이 한 줄에 다 안 들어갈 때 다음 줄로 넘기고, 해시태그 칩을 자연스럽게 흘립니다.
  • 지금까지 배운 flex 규칙을 종합해 좋아요·댓글·공유 아이콘을 한 줄에 정렬하고, 게시물을 카드 모양으로 조립합니다.

오늘도 외우려 하지 마세요. Flexbox는 특히 "값 하나 바꾸고 저장하면 정렬이 확 달라지는" 재미가 큰 도구예요. justify-content의 값을 flex-start에서 space-between으로 바꾸는 순간 항목들이 쫙 펼쳐지는 걸 직접 보면, 표로 외우는 것보다 훨씬 빨리 손에 들어와요. 자, 서랍에 정렬 규칙을 정하러 가볼까요?


Step 1: "display: flex 첫 등장 — 부모에 한 줄, 자식이 줄을 선다"

Flexbox의 시작은 딱 한 줄이에요. 정렬하고 싶은 요소들의 부모 상자display: flex를 주는 거죠. 지난 시간 우리는 메뉴 항목 <li> 하나하나에 display: inline-block을 줘서 가로로 눕혔어요. 오늘은 그 <li>들을 감싸는 부모, 즉 <ul class="nav-menu">display: flex를 줘볼게요.

/* instagram-clone-frontend/css/layout.css */
.nav-menu {
  display: flex;
}

이 한 줄을 저장하는 순간 두 가지 일이 벌어져요. 첫째, .nav-menuflex 컨테이너(flex container), 우리말로 "정렬을 지휘하는 부모 상자"가 돼요. 둘째, 그 부모의 직계 자식들 — 여기서는 메뉴 항목 <li> 네 개 — 가 자동으로 flex 아이템(flex item), "정렬당하는 자식 상자"가 되면서 한 줄에 나란히 늘어서요.

자식에게 따로 inline-block 같은 걸 줄 필요가 없어요. 부모가 flex인 순간 자식들은 알아서 가로로 줄을 서거든요.

 display: inline-block (지난 시간)        display: flex (오늘)

 [홈] [탐색] [피드] [프로필]              [홈][탐색][피드][프로필]
  ↑ 자식마다 inline-block 지정             ↑ 부모에 flex 한 줄
  ↑ 간격·정렬은 들쭉날쭉                    ↑ 자식이 알아서 한 줄로 정렬

여기서 꼭 기억할 한 가지. display: flex는 부모에게 준다는 거예요. 정렬하고 싶은 자식들한테 주는 게 아니라, 그 자식들을 감싸는 부모 상자에 줘요. "서랍(부모)에 규칙을 정하면 양말(자식)이 줄을 선다"는 비유 그대로죠.

🙋 직접 해보세요 — "flex를 켜고 끄면 뭐가 달라질까?"

피드 페이지를 열고 개발자 도구(F12) Elements 탭에서 메뉴를 감싸는 <ul class="nav-menu">를 클릭하세요. 오른쪽 Styles 패널에서 display: flex의 체크박스를 껐다 켰다 해보세요. 끄면 항목들이 다시 위아래로 쌓이고(블록의 본능이죠), 켜면 한 줄로 쫙 늘어서요. display 한 줄이 자식 네 개의 배치를 통째로 바꾸는 걸 눈으로 확인해보세요.

💡 튜터의 결론: Flexbox는 정렬하려는 요소들의 부모 상자display: flex를 주는 것에서 시작해요. 그 순간 부모는 flex 컨테이너가 되고, 자식들은 flex 아이템이 되어 한 줄에 나란히 놓여요. 그런데 "한 줄"이라고 했는데, 정확히 어느 방향 한 줄일까요? 가로일까요 세로일까요? 다음 Step에서 Flexbox의 두 방향을 짚어봅니다.


Step 2: "주축과 교차축 — Flexbox에는 방향이 둘 있다"

Flexbox를 이해하는 열쇠는 축(axis) 두 개예요. 방금 메뉴 항목들이 가로로 늘어섰죠? 이 "항목이 늘어서는 방향"을 주축(main axis), 우리말로 "기본 방향"이라고 불러요. 그리고 그 주축에 수직인 방향교차축(cross axis), "엇갈리는 방향"이라고 해요.

기본값에서 주축은 가로(왼쪽→오른쪽)예요. 그래서 자식들이 가로로 늘어선 거죠. 교차축은 그에 수직이니 세로(위→아래)가 되고요.

            주축 (main axis) — 항목이 늘어서는 방향
            ──────────────────────────────────▶
          ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
   교차축  │ 홈  │ │탐색 │ │피드 │ │프로필│
  (cross) └─────┘ └─────┘ └─────┘ └─────┘
    │
    ▼  주축에 수직인 방향

이 두 축을 왜 굳이 구분하냐면, 앞으로 배울 정렬 속성이 축마다 다르기 때문이에요. 주축 방향 정렬은 justify-content가 맡고, 교차축 방향 정렬은 align-items가 맡아요. 이름이 헷갈릴 텐데, 지금은 "하나는 주축, 하나는 교차축 담당" 정도로만 잡아두면 돼요. 다음 두 Step에서 각각 만나봅니다.

주축의 방향을 바꾸는 flex-direction

주축이 항상 가로인 건 아니에요. flex-direction이라는 속성으로 주축의 방향을 바꿀 수 있어요. 기본값은 row(가로 한 줄)이고, column을 주면 주축이 세로로 회전해요. 그러면 자식들이 위에서 아래로 쌓이죠.

 flex-direction: row (기본)          flex-direction: column

 [홈][탐색][피드][프로필]              [홈]
  주축 = 가로 ▶                        [탐색]
                                       [피드]
                                       [프로필]
                                        주축 = 세로 ▼

여기서 중요한 반전이 하나 있어요. flex-direction: column을 주면 주축이 세로가 되니까, 주축 담당인 justify-content도 세로를 정렬하게 돼요. 이 성질을 Step 6에서 로그인 폼을 화면 정중앙에 띄울 때 결정적으로 써먹을 거예요. 지금은 "주축은 flex-direction으로 돌릴 수 있고, 축이 돌면 정렬 방향도 같이 돈다"만 기억해두세요.

💡 튜터의 결론: Flexbox에는 항목이 늘어서는 주축과 그에 수직인 교차축, 두 방향이 있어요. 기본은 주축이 가로(row)지만 flex-direction: column으로 세로로 돌릴 수 있고, 축이 돌면 정렬 속성이 가리키는 방향도 같이 돌아요. 이제 주축부터 정렬해볼게요.


Step 3: "justify-content — 주축 방향으로 간격과 정렬을 정한다"

justify-content주축 방향(기본은 가로)으로 자식들을 어떻게 배치할지 정해요. 값이 여러 개인데, 하나씩 그림으로 보면 단번에 이해돼요.

 flex-start    [홈][탐색][피드][프로필]············  (왼쪽으로 모음, 기본값)
 flex-end      ············[홈][탐색][피드][프로필]  (오른쪽으로 모음)
 center        ······[홈][탐색][피드][프로필]······  (가운데로 모음)
 space-between [홈]····[탐색]····[피드]····[프로필]  (양끝 붙이고 사이 균등)
 space-around  ·[홈]··[탐색]··[피드]··[프로필]·    (항목마다 양옆 균등)
 space-evenly  ··[홈]··[탐색]··[피드]··[프로필]··(모든 간격 똑같이)

우리 메뉴 바는 오른쪽에 모으고 싶어요(로고는 왼쪽, 메뉴는 오른쪽이 흔한 배치죠). 그래서 justify-content: flex-end를 줍니다.

.nav-menu {
  display: flex;
  justify-content: flex-end;
}

space-between은 특히 자주 써요. 첫 항목은 왼쪽 끝, 마지막 항목은 오른쪽 끝에 딱 붙이고 남는 공간을 사이사이에 똑같이 나눠주거든요. 게시물 헤더에서 "작성자 이름은 왼쪽, 더보기 버튼은 오른쪽"처럼 양끝으로 벌릴 때 떠올리면 좋아요(Step 9에서 다시 만나요).

🙋 직접 해보세요 — "여섯 값을 모두 바꿔보기"

.nav-menu를 개발자 도구에서 선택하고 justify-content 값을 flex-startcenterspace-betweenspace-aroundspace-evenly 순서로 하나씩 바꿔보세요. 메뉴 네 항목이 왼쪽에 모였다가, 가운데로 왔다가, 양끝으로 쫙 펼쳐졌다가 하는 걸 볼 수 있어요. 위 그림과 실제 화면을 나란히 비교하면 각 값의 성격이 손에 잡혀요.

💡 튜터의 결론: justify-content는 주축(가로) 방향으로 자식들을 모으거나 펼쳐요. flex-start/flex-end/center로 한쪽에 모으고, space-between/space-around/space-evenly로 간격을 균등하게 벌려요. 메뉴는 flex-end로 오른쪽에 모았어요. 그런데 가로는 맞췄는데 위아래는요? 다음 Step에서 교차축을 맞춰봅니다.


Step 4: "align-items — 교차축 방향으로 줄을 맞춘다"

align-items교차축 방향(기본은 세로)으로 자식들을 정렬해요. 메뉴처럼 항목 높이가 엇비슷할 땐 차이가 작지만, 높이가 제각각인 항목들을 한 줄에 놓으면 이 속성의 위력이 확 드러나요.

 align-items: stretch (기본)      align-items: center

 ┌────┐┌────┐┌────┐               
 │홈  ││탐색││피드│  ← 높이를      ┌────┐         ┌────┐
 │    ││    ││    │    꽉 채워      │홈  │ ┌────┐  │피드│  ← 세로
 │    ││    ││    │    늘림        └────┘ │탐색│  └────┘    가운데로
 └────┘└────┘└────┘                      └────┘            맞춤

기본값 stretch는 자식들을 교차축 방향으로 꽉 늘려서 높이를 똑같이 맞춰요. center를 주면 늘이지 않고 세로 가운데로 모으고요. 우리 메뉴는 항목들을 세로 가운데로 가지런히 맞추려고 center를 줍니다.

.nav-menu {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

여기서 두 속성의 짝을 기억해두면 좋아요. justify-content는 주축, align-items는 교차축. 둘 다 center를 주면 가로로도 세로로도 가운데에 놓이는데, 이게 바로 Step 6에서 로그인 폼을 화면 정중앙에 띄우는 핵심 기술이에요.

💡 튜터의 결론: align-items는 교차축(세로) 방향으로 자식들을 정렬해요. 기본 stretch는 높이를 꽉 채우고, center는 세로 가운데로 모아요. justify-content(주축)와 align-items(교차축)를 한 쌍으로 외워두면 정렬이 한결 쉬워져요. 이제 항목 사이 간격만 다듬으면 메뉴가 완성돼요.


Step 5: "gap — 항목 사이 간격을 깔끔하게"

정렬은 맞췄는데 항목들이 너무 다닥다닥 붙어 있으면 답답하죠. 항목 사이에 일정한 간격을 주고 싶을 때, 예전엔 자식마다 margin을 일일이 줬어요. 그런데 그러면 맨 끝 항목에도 불필요한 여백이 남거나, 항목을 추가할 때마다 신경 써야 하는 번거로움이 있었죠.

Flexbox에는 이걸 한 방에 푸는 속성이 있어요. 부모에 gap을 주면 자식들 사이에만 똑같은 간격이 생겨요. 양 끝에는 여백이 안 붙고, 오직 항목과 항목 사이에만요.

 gap 없이              margin 0.5rem (옛 방식)        gap: 0.5rem (flex)

 [홈][탐색][피드]      ·[홈]··[탐색]··[피드]·          [홈]·[탐색]·[피드]
  붙어서 답답           ↑ 양 끝에도 여백이 남음          ↑ 사이에만 간격

여기에 <ul>이 기본으로 갖고 있는 점(•) 표시와 안쪽 여백도 정리해줄게요. 메뉴는 목록이긴 하지만 점은 필요 없으니까요. 이렇게 해서 메뉴 바의 최종 모습이 완성돼요.

.nav-menu {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

gap: 0.5rem이 항목 사이를 띄우고, list-style: none이 점을 없애고, margin: 0padding: 0<ul>의 기본 여백을 지워요. 지난 시간 거칠다고 했던 메뉴가 이제 오른쪽에 가지런히, 일정한 간격으로, 세로 가운데 맞춰 정렬됐어요. 첫 번째 숙제를 해결한 거죠.

🙋 직접 해보세요 — "gap과 margin은 뭐가 다를까?"

.nav-menugap 값을 0.5rem에서 2rem으로 키워보세요. 항목 사이가 쫙 벌어지는데, 맨 왼쪽 항목 앞과 맨 오른쪽 항목 뒤에는 여백이 안 생기는 걸 확인하세요. 만약 같은 효과를 margin으로 내려면 자식마다 margin-right를 주고 마지막 항목만 예외 처리해야 했을 거예요. gap은 그 번거로움을 한 줄로 없애줘요.

💡 튜터의 결론: gap은 flex 아이템 사이에만 일정한 간격을 줘요. 양 끝에는 여백이 안 붙어서, 자식마다 margin을 주던 옛 방식보다 훨씬 깔끔하고 관리하기 쉬워요. 이렇게 display: flex + justify-content + align-items + gap 네 줄로 지난 시간 거칠던 메뉴를 완성했어요. 이제 이 기술을 로그인 폼에 옮겨 더 멋진 걸 해볼게요.


Step 6: "실전 센터링 — 로그인 폼을 화면 정중앙에"

이제 오늘의 하이라이트예요. 지난 시간 로그인 폼을 margin: ... auto로 가로 가운데까지만 옮겼던 거, 기억하시죠? 진짜 인스타그램 로그인 화면은 폼이 화면 정중앙에 떠 있어요. 가로뿐 아니라 세로도 가운데죠. 이걸 Flexbox로 한 방에 해결해볼게요.

먼저 로그인 폼을 감싸는 부모가 필요해요. index.html에서 로그인 영역을 감싸는 <section>auth-section이라는 클래스를 줬어요.

<!-- instagram-clone-frontend/index.html -->
<section class="auth-section">
  <h2>친구들의 일상에 오신 것을 환영합니다</h2>
  <p>로그인하고 <strong>실시간</strong>으로 ...</p>

  <form class="login-form" action="/login" method="post">
    ...
  </form>
</section>

이제 이 auth-section을 flex 컨테이너로 만들어 안의 내용을 정중앙에 모읍니다.

/* instagram-clone-frontend/css/layout.css */
.auth-section {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: calc(100dvh - 6.5rem);
}

한 줄씩 뜯어볼게요. flex-direction: column으로 주축을 세로로 돌렸어요. Step 2에서 예고한 반전이 여기예요. 주축이 세로가 됐으니, 주축 담당인 justify-content: center는 이제 세로 가운데를 맞춰요. 그리고 교차축은 자동으로 가로가 됐으니, align-items: center가로 가운데를 맞추고요. 두 center가 합쳐져 내용이 정확히 화면 한가운데로 모여요.

 flex-direction: column 일 때 축이 90도 돈다

   주축(main) = 세로  ▼          justify-content: center → 세로 가운데
   교차축(cross) = 가로 ▶        align-items: center     → 가로 가운데

        ┌─────────────────────┐
        │                     │
        │   ┌───────────┐     │
        │   │ 로그인 폼   │ ← 정확히 한가운데
        │   └───────────┘     │
        │                     │
        └─────────────────────┘

마지막 줄 min-height: calc(100dvh - 6.5rem)이 빠지면 안 돼요. justify-content: center가 세로 가운데로 모으려면 부모가 세로로 충분한 높이를 갖고 있어야 하거든요. 높이가 내용만큼밖에 안 되면 "가운데로 모을 여백" 자체가 없으니까요. 그래서 부모 높이를 화면 높이만큼 확보해줘요.

여기 dvh는 지난 시간 잠깐 본 단위예요. vh는 화면 높이의 1%인데, 휴대폰에서 주소창이 나타났다 사라졌다 하면 높이가 출렁여요. dvh(dynamic viewport height, 동적 화면 높이)는 그 변화를 반영해서 항상 "지금 실제로 보이는 화면 높이"를 가리켜요. 100dvh는 화면 전체 높이죠. 거기서 상단 고정 헤더 높이(6.5rem)를 빼서, 헤더에 가리지 않는 딱 남은 공간만큼만 높이를 잡았어요.

저장하고 로그인 페이지를 열어보면, 폼이 화면 정중앙에 둥실 떠 있어요. 두 번째 숙제도 해결했어요. 가로만 맞췄던 margin auto를 넘어, 가로·세로 모두 가운데로 보낸 거죠.

🙋 직접 해보세요 — "min-height를 지우면 어떻게 될까?"

.auth-section에서 min-height 줄을 잠깐 꺼보세요. 폼이 정중앙에서 화면 위쪽으로 슬쩍 올라올 거예요. justify-content: center가 세로 가운데로 모으려 해도, 부모 높이가 내용만큼밖에 안 되니 "가운데로 보낼 세로 여백"이 없어서 그래요. 다시 켜면 화면 높이만큼 공간이 생기면서 폼이 한가운데로 내려와요. "정렬은 여백이 있어야 보인다"는 걸 직접 확인하는 실험이에요.

💡 튜터의 결론: flex-direction: column으로 주축을 세로로 돌리면, justify-content는 세로를, align-items는 가로를 맡아요. 둘 다 center를 주고 min-height로 부모에 화면 높이를 확보하면, 요소가 화면 정중앙에 떠요. 이게 Flexbox 센터링의 정석이에요. 외울 만한 가치가 있는 네 줄이죠.


Step 7: "flex-grow·shrink·basis — 남는 공간을 나눠 갖기"

지금까지는 자식들을 "어디에 모을지"를 정했어요. 이번엔 자식들이 "공간을 얼마나 차지할지"를 정해볼게요. 게시물 아래 댓글 입력칸을 떠올려보세요. 입력칸은 가로로 길게 늘어나고, 옆의 "게시" 버튼은 자기 크기만 딱 차지하죠. 이걸 Flexbox로 만들어요.

먼저 댓글 폼을 flex 컨테이너로 만들어요. 그러면 입력칸과 버튼이 한 줄에 나란히 서요.

/* instagram-clone-frontend/css/layout.css */
.comment-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

이제 입력칸이 남는 공간을 다 차지하게 해볼게요. 여기서 flex라는 속성이 나와요. flex는 사실 세 속성을 한 번에 적는 줄임 표현이에요.

 flex: <grow> <shrink> <basis>;
        ↑      ↑        ↑
        늘어날  줄어들    기본
        비율    비율      크기
  • flex-grow(늘어날 비율): 남는 공간이 있을 때 얼마나 늘어날지. 0이면 안 늘어나고, 1 이상이면 비율대로 늘어나요.
  • flex-shrink(줄어들 비율): 공간이 부족할 때 얼마나 줄어들지. 0이면 안 줄어들어요.
  • flex-basis(기본 크기): 늘이거나 줄이기 전의 출발 너비. auto면 내용 크기에서 출발해요.

입력칸에는 flex: 1 1 auto를 줘요. "남는 공간이 있으면 늘어나고(grow 1), 부족하면 줄어들고(shrink 1), 출발은 내용 크기(basis auto)"라는 뜻이에요.

.comment-form textarea {
  flex: 1 1 auto;
}

.comment-form button {
  flex: 0 0 auto;
}

버튼에는 flex: 0 0 auto를 줬어요. "늘어나지도(grow 0) 줄어들지도(shrink 0) 말고, 자기 내용 크기 그대로 있어"라는 뜻이죠. 그래서 입력칸만 쭉 늘어나고 버튼은 옆에서 자기 자리를 지켜요.

 ┌──────────────────────────────────────┐ ┌──────┐
 │ 댓글을 입력하세요...                     │ │ 게시  │
 └──────────────────────────────────────┘ └──────┘
   ↑ flex: 1 1 auto — 남는 공간 다 차지       ↑ flex: 0 0 auto
                                              자기 크기 고정

flex-grow의 진짜 힘은 비율이에요. 만약 자식 둘에 각각 flex-grow: 1flex-grow: 2를 주면, 남는 공간을 1:2로 나눠 가져요. 칸을 정확한 픽셀로 계산하지 않아도, "이 칸은 저 칸의 두 배 너비"처럼 비율로 유연하게 짤 수 있는 거죠.

🙋 직접 해보세요 — "버튼도 늘어나게 해볼까?"

.comment-form buttonflex 값을 0 0 auto에서 1 1 auto로 바꿔보세요. 이제 입력칸과 버튼이 남는 공간을 똑같이 1:1로 나눠 가지면서, 버튼이 입력칸만큼 넓어져요. 반대로 입력칸을 0 0 auto로 바꾸면 입력칸이 내용 크기로 쪼그라들고요. flex-grow 숫자만 바꿔서 누가 공간을 차지할지 정하는 감각을 익혀보세요.

💡 튜터의 결론: flex: grow shrink basis는 자식이 공간을 어떻게 나눠 가질지 정해요. flex: 1 1 auto는 남는 공간을 차지하며 늘어나고, flex: 0 0 auto는 자기 크기를 지켜요. 픽셀로 일일이 계산하는 대신 비율로 유연하게 너비를 짜는 게 Flexbox의 큰 장점이에요. 댓글 입력칸이 화면 너비에 맞춰 알아서 늘어나는 이유가 이거였어요.


Step 8: "flex-wrap — 한 줄에 안 들어가면 다음 줄로"

flex 아이템은 기본적으로 무조건 한 줄에 욱여넣어요. 항목이 많아서 공간이 부족하면, 각 항목을 찌그러뜨려서라도 한 줄을 유지하려 하죠. 그런데 항목이 많을 땐 다음 줄로 자연스럽게 넘기는 게 나을 때가 있어요. 게시물의 해시태그가 딱 그래요. #sunset #travel #제주도 #협재해변 ... 처럼 태그가 많으면 한 줄에 다 안 들어가니까요.

이때 부모에 flex-wrap: wrap을 주면 돼요. "한 줄에 안 들어가는 항목은 다음 줄로 넘겨"라는 뜻이에요. 해시태그 목록 <ul class="tag-list">에 적용해볼게요.

/* instagram-clone-frontend/css/layout.css */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 0.4rem;
  margin: 0.5rem 0;
  padding: 0;
  list-style: none;
}
 flex-wrap: nowrap (기본)              flex-wrap: wrap

 [#sunset][#travel][#제주도][#협...    [#sunset] [#travel] [#제주도]
  ↑ 한 줄에 욱여넣어 찌그러지거나        [#협재해변] [#노을맛집]
    넘쳐버림                            [#여행스타그램] [#감성사진]
                                        ↑ 안 들어가면 다음 줄로

여기 align-content: flex-start도 같이 줬어요. 이건 여러 줄이 생겼을 때 그 줄 뭉치를 교차축(세로) 방향 어디에 둘지 정해요. flex-start는 위쪽부터 차곡차곡 쌓으라는 뜻이고요. 주의할 점은, align-content줄이 두 줄 이상일 때만 효과가 있어요. 한 줄일 땐 아무 일도 안 해요. 그래서 flex-wrap: wrap과 거의 늘 짝으로 다녀요.

여기서 한 가지 정리하고 갈게요. 이름이 비슷해서 헷갈리는 두 속성이 있어요.

 align-items   : 한 줄 안에서 각 항목을 교차축으로 정렬   (줄이 하나든 여럿이든)
 align-content : 여러 줄 뭉치 전체를 교차축으로 정렬       (줄이 둘 이상일 때만)

align-items는 "한 줄 안 항목들의 세로 정렬", align-content는 "여러 줄 전체의 세로 배치"예요. 지금은 "wrap으로 여러 줄이 생기면 align-content가 그 줄들을 배치한다" 정도만 잡아두면 충분해요.

🙋 직접 해보세요 — "wrap을 끄면 해시태그가 어떻게 될까?"

.tag-listflex-wrap 값을 wrap에서 nowrap으로 바꿔보세요. 일곱 개의 해시태그가 다음 줄로 못 넘어가고 한 줄에 욱여넣어지면서, 칩들이 찌그러지거나 카드 밖으로 삐져나올 거예요. 다시 wrap으로 돌리면 안 들어가는 태그가 다음 줄로 가지런히 내려와요. 항목 개수가 들쭉날쭉한 해시태그·필터 버튼 같은 곳에 wrap이 왜 필요한지 느껴보세요.

💡 튜터의 결론: flex 아이템은 기본적으로 한 줄을 고집하지만, flex-wrap: wrap을 주면 안 들어가는 항목을 다음 줄로 넘겨요. 개수가 유동적인 해시태그·태그 버튼에 잘 어울려요. 여러 줄이 생겼을 때 그 줄 뭉치의 세로 배치는 align-content가 맡고요. 이제 오늘 배운 걸 다 모아 진짜 게시물 카드를 조립해볼게요.


Step 9: "피드 카드 조립 — 좋아요·댓글·공유를 한 줄로"

마지막이에요. 오늘 배운 flex 기술을 다 모아 게시물을 진짜 인스타그램 카드처럼 만들어볼게요. 그러면서 마지막 숙제 — 좋아요·댓글·공유 아이콘 한 줄 정렬 — 도 해결합니다.

게시물 헤더 — 한쪽 끝으로 밀어내는 margin-left: auto

먼저 카드 맨 위, 작성자 정보 줄이에요. feed.html 첫 게시물에 헤더를 넣었어요. 프로필 사진, 작성자 이름, 그리고 오른쪽 끝에 더보기(⋯) 버튼이죠.

<!-- instagram-clone-frontend/feed.html -->
<header class="post-header">
  <img class="post-avatar" src="..." alt="jaehoon 프로필 사진" width="32" height="32">
  <strong class="post-author">jaehoon</strong>
  <button type="button" class="post-more" popovertarget="postMenu" aria-label="더보기">⋯</button>
</header>

이 셋을 한 줄에 놓되, 더보기 버튼만 오른쪽 끝으로 보내고 싶어요. 여기서 아주 유용한 한 줄짜리 기술이 나와요. margin-left: auto예요.

/* instagram-clone-frontend/css/layout.css */
.post-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.post-header .post-more {
  margin-left: auto;
}

flex 아이템에 margin-left: auto를 주면, 그 항목의 왼쪽 여백이 남는 공간을 전부 먹어버려요. 그래서 그 항목은 오른쪽 끝으로 쭉 밀려나죠. 아바타·이름은 왼쪽에 모이고 더보기 버튼만 오른쪽 끝으로 가는 이유가 이거예요. "한 항목만 반대쪽 끝으로 보내고 싶을 때" 쓰는 단골 기술이에요.

액션 바 — 같은 기술로 저장 버튼만 오른쪽에

좋아요·댓글·공유·저장 버튼도 똑같은 패턴이에요. 인스타그램을 보면 좋아요·댓글·공유는 왼쪽에 모여 있고, 저장(북마크)만 오른쪽 끝에 떨어져 있죠.

<div class="post-actions">
  <button type="button" class="icon-btn" aria-label="좋아요">♥</button>
  <button type="button" class="icon-btn" aria-label="댓글">💬</button>
  <button type="button" class="icon-btn" command="show-modal" commandfor="shareDialog" aria-label="공유">↗</button>
  <button type="button" class="icon-btn icon-btn-save" aria-label="저장">🔖</button>
</div>
.post-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.post-actions .icon-btn-save {
  margin-left: auto;
}

좋아요·댓글·공유 세 버튼은 gap 간격으로 왼쪽에 모이고, 저장 버튼만 margin-left: auto로 오른쪽 끝으로 가요. 세 번째 숙제, 좋아요·댓글 아이콘 한 줄 정렬을 해결한 거죠.

 ┌──────────────────────────────────────────────┐
 │  ♥   💬   ↗                              🔖   │
 │  └─ gap으로 왼쪽에 모임 ─┘     margin-left:auto ─┘│
 └──────────────────────────────────────────────┘

선택자 함정 — header 하나가 다른 header까지 잡는다

여기서 실제로 마주친 함정을 하나 공유할게요. 게시물 헤더를 <header class="post-header">로 만들었더니, 지난 시간 상단 바를 고정하려고 줬던 규칙이 말썽을 부렸어요.

/* 지난 시간의 규칙 — 무차별 header 선택자 */
header {
  position: fixed;
}

header는 태그 선택자라서, 페이지 맨 위 헤더뿐 아니라 게시물 안에 새로 넣은 <header class="post-header">까지 전부 골라요. 그 바람에 모든 게시물 헤더가 화면 꼭대기에 고정돼서 서로 겹쳐버렸죠. 해결은 간단해요. 고정하려는 헤더를 딱 집어내도록 선택자 범위를 좁히면 돼요.

body > header {
  position: fixed;
  ...
}

body > header는 "body의 바로 아래 자식인 header"만 골라요. 게시물 헤더는 article 안에 있으니 body의 직계 자식이 아니라서 안 잡히죠. 페이지 맨 위 헤더만 정확히 고정되고요. 태그 선택자를 넓게 쓰면 의도치 않은 요소까지 잡힐 수 있다는 걸 보여주는 좋은 사례예요. 클래스를 새로 추가할 때는 "혹시 기존 선택자가 이것까지 잡지 않나?" 한 번 의심해보는 습관이 도움이 돼요.

카드의 생김새 — components.css

마지막으로 게시물을 카드처럼 보이게 만들어요. 흰 배경, 얇은 테두리, 둥근 모서리요. 오늘 새로 만든 components.css에 카드의 생김새를 모아둡니다.

/* instagram-clone-frontend/css/components.css */
article {
  max-width: 32rem;
  margin: 1rem auto;
  border: 1px solid #dbdbdb;
  border-radius: 8px;
  background-color: #ffffff;
  overflow: hidden;
}

article figure img,
article figure video {
  width: 100%;
  height: auto;
  display: block;
}

max-width: 32rem으로 카드 너비를 제한하고, margin: 1rem auto로 가로 가운데에 놓았어요(지난 시간 배운 margin auto 센터링이죠). 사진은 width: 100%로 카드 너비를 꽉 채우고요. 여기에 아바타를 동그랗게 깎고, 해시태그를 알약 모양 칩으로 만드는 스타일도 components.css에 함께 담았어요.

이렇게 layout.css는 "어디에 어떻게 배치할지"(flex 정렬)를, components.css는 "어떻게 생겼는지"(카드·버튼·칩 모양)를 나눠 맡아요. 역할이 갈리니 나중에 "정렬을 고치고 싶으면 layout, 색·모양을 고치고 싶으면 components"처럼 찾아갈 곳이 분명해져요.

🙋 직접 해보세요 — "저장 버튼의 margin-left: auto를 지우면?"

.post-actions .icon-btn-savemargin-left: auto를 잠깐 꺼보세요. 오른쪽 끝에 있던 저장 버튼이 좋아요·댓글·공유 옆에 바짝 붙어버려요. margin-left: auto가 "왼쪽 여백으로 남는 공간을 다 먹어 항목을 오른쪽으로 민다"는 걸 눈으로 확인할 수 있어요. 다시 켜면 저장 버튼이 오른쪽 끝으로 돌아가고요.

💡 튜터의 결론: display: flexgapmargin-left: auto를 더하면, 한 줄 안에서 "왼쪽에 모으고 한 항목만 오른쪽 끝으로" 같은 배치를 손쉽게 만들어요. 게시물 헤더와 액션 바가 다 같은 패턴이었죠. 배치는 layout.css, 생김새는 components.css로 역할을 나누면 코드를 관리하기 훨씬 편해지고요. 오늘 배운 flex 기술만으로 진짜 인스타그램 같은 카드가 완성됐어요.


마무리

오늘 배운 것 한눈에 정리

오늘은 "여러 요소를 한 줄에 놓고, 간격을 벌리고, 가운데로 모으는" 정렬 전문 도구 Flexbox를 익혔어요. 시작은 늘 부모에 display: flex 한 줄이었죠.

🎯 하나, flex 컨테이너와 아이템 — 정렬하려는 요소들의 부모display: flex를 주면, 그 부모가 컨테이너가 되고 자식들이 아이템이 되어 한 줄에 나란히 서요.

🎯 , 주축과 교차축 — 항목이 늘어서는 방향이 주축, 그에 수직인 방향이 교차축이에요. flex-direction: column으로 주축을 세로로 돌리면 정렬 방향도 같이 돌아요.

🎯 , 두 정렬 속성 — justify-content는 주축, align-items는 교차축을 맡아요. 한 쌍으로 외워두면 정렬이 쉬워져요.

🎯 , gapflexgap은 항목 사이에만 간격을 주고, flex: grow shrink basis는 자식이 공간을 어떻게 나눠 가질지 정해요. flex: 1 1 auto는 늘어나고, flex: 0 0 auto는 자기 크기를 지켜요.

🎯 다섯, flex-wrapmargin-left: autowrap은 안 들어가는 항목을 다음 줄로 넘기고, margin-left: auto는 한 항목만 반대쪽 끝으로 밀어내요.

🎯 여섯, 인스타 연결 — 거칠던 메뉴를 가지런히 정렬하고, 로그인 폼을 화면 정중앙에 띄우고, 좋아요·댓글·공유를 한 줄로 모으고, 게시물을 카드로 조립했어요. 지난 시간 미뤄둔 세 숙제를 전부 해결했죠.

Flexbox가 잘하는 일과 빠듯한 일

Flexbox는 "한 줄(또는 한 칸) 정렬"의 끝판왕이에요. 메뉴 한 줄, 액션 바 한 줄, 폼 가운데 정렬처럼 한 방향으로 흐르는 배치에 강하죠. 그런데 인스타그램 프로필 페이지를 떠올려보세요. 게시물 사진이 가로 3개씩, 세로로 여러 줄, 바둑판처럼 격자로 깔려 있어요. 이렇게 가로와 세로를 동시에 칸칸이 맞추는 격자는 Flexbox 하나로는 살짝 빠듯해요. 한 방향 정렬엔 강하지만, 두 방향 격자는 다른 도구가 더 잘하거든요.

다음 시간 예고

다음 시간엔 가로·세로 격자를 한 번에 짜는 CSS Grid(그리드)를 배웁니다. Flexbox가 "한 줄 정렬"이라면, Grid는 엑셀 표처럼 행과 열로 칸을 나눠 배치하는 도구예요. 프로필 페이지의 게시물을 3열 격자로 쫙 깔고, grid-template으로 칸의 크기를, repeatfr 단위로 "똑같은 칸 3개"를 한 줄로 정의할 거예요. 오늘 배운 Flexbox와 Grid를 상황에 맞게 골라 쓰는 감각까지 잡아봅니다.

오늘 한 줄 정렬을 손에 넣었으니, 다음 시간엔 격자 정렬로 인스타그램의 마지막 큰 레이아웃을 완성해봅시다!


과제

[구현] 푸터 메뉴를 Flexbox로 가로 정렬하기

오늘 상단 메뉴(nav-menu)를 flex로 가로 정렬했죠. 그런데 페이지 맨 아래 <footer> 안의 메뉴(소개/고객센터/개인정보처리방침/약관/GitHub)는 아직 세로로 쌓여 있어요. 같은 기술로 이 푸터 메뉴도 가로로 정렬해보세요. layout.css에 직접 규칙을 추가하면 됩니다.

요구 사항:

  • 푸터 안의 <ul>을 골라(예: footer nav ul) display: flex를 주고 가로로 눕히기.
  • gap으로 항목 사이 간격 주기. list-style: nonemargin: 0, padding: 0으로 점과 기본 여백 정리하기.
  • justify-content로 항목들을 가운데(center)에 모으거나 양끝으로 펼쳐(space-between) 보고, 둘 중 더 어울리는 걸 고르기.

저장하고 페이지 맨 아래를 보면서, 세로로 쌓였던 푸터 메뉴가 한 줄로 정렬되는지 확인하세요. 상단 메뉴에 쓴 패턴을 그대로 옮기는 연습이에요.

[구현·응용] 스토리 바 만들기 — 가로 한 줄 아바타

인스타그램 피드 맨 위엔 친구들의 "스토리"가 동그란 프로필 사진으로 가로 한 줄에 늘어서 있죠. 이걸 Flexbox로 만들어봐요. feed.html의 피드 맨 위(<h2>피드</h2> 아래)에 스토리 바를 새로 추가하세요.

요구 사항:

  • <ul class="story-bar">를 만들고 그 안에 <li>로 친구 5~6명을 넣기. 각 <li> 안에는 동그란 아바타 이미지(https://picsum.photos/seed/원하는단어/64/64)와 이름을 넣기.
  • layout.css에서 .story-bardisplay: flexgap을 줘서 가로 한 줄로 늘어놓기. list-style: none/margin/padding 정리도 함께.
  • 각 아바타가 찌그러지지 않도록, 아바타 <li>flex-shrink: 0을 주기(공간이 부족해도 줄어들지 말라는 뜻). flex: 0 0 auto로 줘도 됩니다.

저장하고 확인하면서, 친구들이 가로로 가지런히 늘어서는지 / 칸이 좁아도 아바타가 안 찌그러지는지 둘 다 보세요. 오늘 배운 flex-shrink(또는 flex 단축)의 0이 왜 필요한지 직접 느껴보는 과제예요.

[탐구] justify-content 여섯 값 비교 정리하기

오늘 justify-content의 값을 그림으로만 봤어요. 이번엔 직접 다 바꿔보고 표로 정리해보세요. 메뉴(.nav-menu)나 액션 바(.post-actions)를 개발자 도구에서 고른 뒤, justify-content 값을 flex-start/flex-end/center/space-between/space-around/space-evenly 여섯 가지로 하나씩 바꿔보세요.

정리할 내용:

  1. 각 값에서 항목들이 어디에 모이는지(왼쪽/오른쪽/가운데/양끝 등) 한 줄로 적기.
  2. space-between/space-around/space-evenly 세 값의 간격 차이를 직접 비교하고, 무엇이 어떻게 다른지 적기.
  3. "메뉴 바엔 어떤 값, 좋아요·댓글 액션 바엔 어떤 값이 어울릴까?"를 이유와 함께 한 줄 의견으로 덧붙이기.

여섯 값을 눈으로 다 본 뒤 표로 정리하면, 다음에 어떤 정렬이 필요할 때 망설임 없이 골라 쓸 수 있어요.


생각해볼 주제

1. Flexbox로 프로필 사진 3열 격자를 짜면 왜 빠듯할까?

오늘 Flexbox로 한 줄 정렬을 멋지게 해냈어요. 그래서 "그럼 프로필 페이지의 사진 격자(가로 3개씩 여러 줄)도 flex-wrap: wrap으로 만들면 되지 않나?" 싶을 수 있죠. 실제로 시도해보면 얼추 비슷하게는 돼요.

하지만 화면 너비가 달라지거나 사진 개수가 3의 배수가 아닐 때, 마지막 줄이 어색하게 흐트러지거나 칸 너비가 안 맞는 일이 생겨요. Flexbox는 "한 방향 흐름"을 기준으로 생각하는 도구라, 가로와 세로의 칸을 동시에 똑 떨어지게 맞추는 일엔 손이 많이 가거든요. 한 방향 정렬(Flexbox)과 두 방향 격자가 각각 어떤 배치에 강할지, 그리고 왜 격자엔 다른 도구가 필요할지 생각해보세요.

2. margin-left: autojustify-content: space-between은 뭐가 다를까?

오늘 한 항목만 오른쪽 끝으로 보낼 때 margin-left: auto를 썼어요. 그런데 justify-content: space-between도 첫 항목은 왼쪽, 마지막 항목은 오른쪽으로 보내죠. 둘은 비슷해 보이지만 동작 방식이 달라요.

space-between모든 항목 사이를 균등하게 벌리고, margin-left: auto특정 한 항목만 콕 집어 반대쪽으로 밀어요. 항목이 세 개일 때 두 방식의 결과가 어떻게 달라질지 머릿속으로 그려보세요. 액션 바처럼 "왼쪽 그룹은 모으고 한 개만 오른쪽으로"일 땐 어느 쪽이 맞을까요? 반대로 항목 전체를 양끝으로 쫙 펼치고 싶을 땐요? 두 도구가 각각 어떤 상황에 어울리는지 구분해보세요.

3. flex 아이템에 width를 주는 것과 flex-basis를 주는 건 무엇이 다를까?

flex 컨테이너 안의 자식에 너비를 정하는 방법이 두 가지로 보여요. 익숙한 width를 줄 수도 있고, 오늘 배운 flex-basis(또는 flex의 세 번째 값)를 줄 수도 있죠. 그런데 flex 컨테이너 안에서는 이 둘이 똑같이 동작하지 않아요.

flex-growflex-shrink가 끼면, 자식은 width로 고정한 값에서도 늘어나거나 줄어들 수 있거든요. "고정된 너비"라고 생각했는데 화면 크기에 따라 변하는 경험을 하게 될 수도 있어요. 일반 배치에서 쓰던 width와 flex 안에서의 flex-basis가 어떻게 다르게 동작할지, 그리고 "이 칸은 무슨 일이 있어도 200px 고정"을 원할 때 어떤 속성 조합이 필요할지 생각해보세요.

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안이에요. 정답이 하나만 있는 건 아니에요. 간격 값이나 정렬 방식은 취향대로 골라도 좋아요. 중요한 건 부모에 display: flex를 주고, justify-content·align-items·gap으로 자식들을 의도대로 정렬했는가 예요.


🎯 [과제 1 예시답안] 푸터 메뉴를 Flexbox로 가로 정렬하기

핵심 접근

이 과제는 Step 1~5에서 상단 메뉴(nav-menu)에 쓴 패턴을 그대로 푸터로 옮기는 거예요. 푸터 메뉴도 똑같은 <ul> 구조라, "부모에 display: flex, 점·여백 정리, gap으로 간격, justify-content로 정렬"이라는 같은 네 가지를 적용하면 돼요. 새 기술이 아니라 이미 배운 패턴을 다른 곳에 재사용하는 연습이에요.

예시 구현

layout.css에 푸터 메뉴 규칙을 추가해요.

/* instagram-clone-frontend/css/layout.css 에 추가 */

/* ===== 푸터 메뉴 가로 정렬 (Flexbox) ===== */
footer nav ul {
  display: flex;
  justify-content: center;
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

display: flex로 푸터 안 <ul>을 컨테이너로 만들면 항목들이 한 줄로 늘어서요. justify-content: center로 가운데에 모으고, gap: 1rem으로 항목 사이를 띄웠어요. list-style: nonemargin: 0·padding: 0으로 점과 기본 여백을 정리했고요. 상단 메뉴와 똑같은 네 가지죠.

저장하고 페이지 맨 아래를 보면, 세로로 쌓여 있던 소개·고객센터·개인정보처리방침·약관·GitHub가 한 줄로 가지런히 정렬돼요.

채점 포인트

항목 확인 내용
flex 컨테이너 푸터 안 <ul>display: flex를 줬는가
점·여백 정리 list-style: none + margin: 0 + padding: 0으로 기본 목록 스타일을 지웠는가
간격 gap으로 항목 사이를 띄웠는가
정렬 선택 justify-contentcenter 또는 space-between을 의도에 맞게 골랐는가

흔한 실수

  • 자식 <li>에 flex를 줌display: flex는 정렬하려는 항목들의 부모(<ul>)에 줘야 해요. <li>에 주면 그 안의 내용이 정렬될 뿐, <li>들끼리는 안 늘어서요.
  • 점·여백을 안 지움list-style: none을 빠뜨리면 항목 앞에 점(•)이 남고, padding: 0을 빠뜨리면 <ul> 기본 안쪽 여백 때문에 메뉴가 왼쪽으로 치우쳐 보여요.

🎯 [과제 2 예시답안] 스토리 바 만들기 — 가로 한 줄 아바타

핵심 접근

스토리 바도 결국 "여러 항목을 가로 한 줄로 늘어놓기"라, display: flex + gap이면 기본 모양이 나와요. 이 과제의 진짜 포인트는 flex-shrink: 0이에요. flex 아이템은 공간이 부족하면 기본적으로 줄어드는데(shrink: 1), 동그란 아바타가 줄어들면 찌그러진 타원이 돼버려요. 그래서 "줄어들지 마"라는 flex-shrink: 0을 줘서 아바타 크기를 지키는 게 핵심이에요.

예시 구현

먼저 feed.html<h2>피드</h2> 아래에 스토리 바를 추가해요.

<!-- instagram-clone-frontend/feed.html 의 <h2>피드</h2> 아래에 추가 -->
<ul class="story-bar">
  <li>
    <img src="https://picsum.photos/seed/story1/64/64" alt="minji 스토리" width="56" height="56">
    <span>minji</span>
  </li>
  <li>
    <img src="https://picsum.photos/seed/story2/64/64" alt="seungwoo 스토리" width="56" height="56">
    <span>seungwoo</span>
  </li>
  <li>
    <img src="https://picsum.photos/seed/story3/64/64" alt="haeun 스토리" width="56" height="56">
    <span>haeun</span>
  </li>
  <li>
    <img src="https://picsum.photos/seed/story4/64/64" alt="jiwon 스토리" width="56" height="56">
    <span>jiwon</span>
  </li>
  <li>
    <img src="https://picsum.photos/seed/story5/64/64" alt="doyun 스토리" width="56" height="56">
    <span>doyun</span>
  </li>
</ul>

그리고 layout.css에 가로 정렬 규칙을 추가해요.

/* instagram-clone-frontend/css/layout.css 에 추가 */

/* ===== 스토리 바 — 가로 한 줄 아바타 ===== */
.story-bar {
  display: flex;
  gap: 1rem;
  margin: 0.5rem 0;
  padding: 0;
  list-style: none;
}

.story-bar > li {
  flex: 0 0 auto;
  text-align: center;
}

display: flexgap: 1rem으로 친구들을 가로 한 줄에 늘어놓았어요. 핵심은 .story-bar > liflex: 0 0 auto예요. 세 값이 "늘어나지 마(grow 0), 줄어들지 마(shrink 0), 출발은 내용 크기(basis auto)"라서, 공간이 좁아져도 아바타가 안 찌그러지고 자기 크기를 지켜요. flex-shrink: 0만 따로 줘도 같은 효과예요.

아바타를 동그랗게 깎고 싶으면 components.cssborder-radius: 50%를 더하면 되는데, 이건 지난 시간 배운 박스 모델이니 선택이에요.

채점 포인트

항목 확인 내용
flex 가로 정렬 .story-bardisplay: flexgap을 줘서 가로 한 줄로 늘어놓았는가
점·여백 정리 list-style: none + margin/padding 정리를 했는가
찌그러짐 방지 아바타 <li>flex-shrink: 0(또는 flex: 0 0 auto)을 줬는가
마크업 아바타 이미지와 이름을 의미 있게 담았는가

흔한 실수

  • flex-shrink: 0을 빠뜨림 — 친구를 많이 넣으면 한 줄에 다 넣으려고 아바타가 조금씩 줄어들어요. 동그란 사진이 살짝 찌그러진 타원이 되는데, 원인이 shrink라는 걸 모르면 한참 헤매요.
  • flex-wrap: wrap을 줘버림 — 스토리 바는 한 줄을 유지해야 자연스러워요. wrap을 주면 친구가 많을 때 다음 줄로 내려가 버려서 의도와 달라져요(실무에선 가로 스크롤로 처리하는데, 그건 나중에 배워요).

🎯 [과제 3 예시답안] justify-content 여섯 값 비교 정리하기

핵심 접근

이 과제는 코드를 새로 짜기보다, 여섯 값을 직접 바꿔보며 차이를 언어로 정리하는 게 목적이에요. 특히 헷갈리는 space-between/space-around/space-evenly 세 값의 간격 차이를 자기 말로 설명할 수 있으면 성공이에요.

예시 정리

항목 세 개 [A] [B] [C]를 기준으로 정리하면 이래요.

항목이 모이는 곳 한 줄 설명
flex-start 왼쪽 기본값. 다 왼쪽에 붙고 오른쪽이 빔
flex-end 오른쪽 다 오른쪽에 붙고 왼쪽이 빔
center 가운데 항목 뭉치가 가운데로 모임
space-between 양끝 A는 왼끝, C는 오른끝, 사이만 균등하게 벌어짐
space-around 균등(끝 절반) 항목마다 양옆에 같은 여백. 끝 여백은 사이 여백의 절반
space-evenly 완전 균등 끝 여백과 사이 여백이 전부 똑같음

space-* 값의 차이는 끝쪽 여백에 있어요.

 space-between [A]······[B]······[C]      끝 여백 0, 사이만 벌림
 space-around  ··[A]····[B]····[C]··      끝 여백 = 사이 여백의 절반
 space-evenly  ···[A]···[B]···[C]···      끝 여백 = 사이 여백 (전부 동일)

의견 예시: 상단 메뉴는 오른쪽에 모으는 게 흔하니 flex-end가 어울리고, 만약 메뉴를 화면에 꽉 채워 펼치고 싶다면 space-between이 좋아요. 좋아요·댓글·공유 액션 바는 왼쪽에 모으고 저장만 따로 떼니, justify-content보다 margin-left: auto(생각해볼 주제 2)가 더 맞아요.

채점 포인트

항목 확인 내용
여섯 값 관찰 여섯 값을 실제로 바꿔보고 각각의 위치를 적었는가
space-* 구분 space 값의 끝 여백 차이를 설명했는가
적용 의견 메뉴/액션 바에 어떤 값이 어울릴지 이유와 함께 적었는가

흔한 실수

  • space-aroundspace-evenly를 같다고 봄 — 둘 다 "균등"처럼 보이지만, around는 끝 여백이 사이 여백의 절반이라 양 끝이 살짝 좁아요. 항목을 화면 끝까지 균일하게 펼치려면 evenly가 맞아요.

💭 [생각해볼 주제 예시답안]

1. Flexbox로 프로필 사진 3열 격자를 짜면 왜 빠듯할까?

flex-wrap: wrap으로 사진을 3개씩 줄바꿈하면 얼추 격자처럼 보여요. 하지만 문제는 칸 너비를 똑 떨어지게 맞추기가 어렵다는 거예요.

Flexbox는 "한 줄에 흐르다가 넘치면 다음 줄로" 방식이라, 각 항목의 너비를 flex-basis로 직접 계산해 줘야 해요. 화면 너비가 바뀌면 그 계산이 어긋나고, 사진 개수가 3의 배수가 아니면 마지막 줄에 한두 개만 덩그러니 남아 너비가 안 맞아 보여요. 또 가로 간격(gap)을 빼고 정확히 1/3씩 나누려면 calc() 같은 계산이 필요하고요.

근본 원인은 Flexbox가 한 방향(주축) 흐름을 기준으로 설계됐다는 거예요. 가로 정렬은 잘하지만, 가로와 세로의 칸을 동시에 격자로 맞추는 건 설계 목적 밖이에요. 이래서 두 방향 격자에는 다음 시간 배울 CSS Grid가 따로 있어요. Grid는 "행과 열"을 먼저 정의하고 그 칸에 항목을 넣는 방식이라, 3열 격자를 repeat(3, 1fr) 한 줄로 똑 떨어지게 만들 수 있거든요.

🎯 면접관을 홀리는 핵심 멘트

"Flexbox는 1차원(한 줄) 정렬, Grid는 2차원(행·열) 격자 도구예요. 한 방향으로 흐르는 메뉴나 액션 바는 Flexbox, 가로·세로를 동시에 맞추는 사진 격자나 페이지 전체 레이아웃은 Grid를 써요. flex-wrap으로 격자를 흉내 낼 순 있지만, 칸 너비를 똑 떨어지게 맞추는 건 Grid의 일이에요."

2. margin-left: autojustify-content: space-between은 뭐가 다를까?

핵심 차이는 "전체를 벌리느냐, 한 항목만 미느냐"예요. justify-content: space-between은 모든 항목 사이 간격을 똑같이 벌려요. 항목이 세 개면 [A]···[B]···[C]처럼 셋이 양끝과 가운데로 쫙 퍼지죠. 반면 margin-left: auto는 그 항목 하나의 왼쪽 여백이 남는 공간을 다 먹어서, 그 항목만 오른쪽으로 밀어요. 나머지는 원래대로 왼쪽에 모여 있고요.

그래서 액션 바처럼 "좋아요·댓글·공유는 왼쪽 그룹으로 모으고, 저장만 오른쪽 끝으로"일 땐 margin-left: auto가 정확해요. space-between을 쓰면 좋아요·댓글·공유까지 다 흩어져서 그룹이 깨지거든요.

반대로 "항목 전체를 화면 양끝까지 고르게 펼쳐"일 땐 space-between이 맞고요. "그룹은 유지하고 하나만 떼기"는 margin: auto, "전체를 균등하게 펼치기"는 justify-content 로 기억하면 돼요.

🎯 면접관을 홀리는 핵심 멘트

"justify-content는 컨테이너가 모든 아이템을 한꺼번에 배치하는 거고, margin: auto는 특정 아이템 하나가 남는 공간을 흡수해 반대쪽으로 밀려나는 거예요. '왼쪽 그룹 + 오른쪽 하나' 같은 비대칭 배치엔 margin-left: auto가, 전체 균등 분배엔 space-between이 맞아요."

3. flex 아이템에 width를 주는 것과 flex-basis를 주는 건 무엇이 다를까?

겉보기엔 둘 다 "너비를 정한다"지만, flex 컨테이너 안에서는 동작이 달라요. flex-basis늘이고 줄이기 전의 출발 너비예요. 거기에 flex-grow가 1 이상이면 남는 공간을 받아 더 커지고, flex-shrink가 1 이상이면 공간이 부족할 때 더 작아져요. 즉 flex-basis로 준 값은 "고정"이 아니라 "출발점"이에요.

width도 flex 안에서는 비슷하게 흔들려요. width: 200px를 줘도 flex-shrink가 살아 있으면(기본값 1), 공간이 부족할 때 200px 아래로 줄어들 수 있거든요.

"분명 200px로 고정했는데 왜 줄어들지?" 하는 흔한 혼란이 여기서 와요. 진짜로 "무슨 일이 있어도 200px"를 원하면 flex-shrink: 0을 함께 줘서 줄어들지 못하게 막아야 해요(flex: 0 0 200px). 과제 2의 스토리 아바타가 바로 이 경우였죠.

정리하면, 일반 배치의 width는 그 자체로 고정에 가깝지만, flex 컨테이너 안에서는 grow/shrink가 끼어들어 widthflex-basis든 유동적으로 변할 수 있어요. 크기를 확실히 못 박으려면 shrink: 0이 짝꿍이에요.

🎯 면접관을 홀리는 핵심 멘트

"flex 아이템의 최종 크기는 flex-basis(출발 너비)에서 flex-grow(늘림)와 flex-shrink(줄임)가 더해져 결정돼요. width를 줘도 flex-shrink: 1이 기본이라 줄어들 수 있어서, 진짜 고정이 필요하면 flex: 0 0 <크기>처럼 shrink를 0으로 막아야 해요."

더 배우려면

실무 프로젝트까지 가고 싶다면

팀스파르타 백엔드 부트캠프에서 인스타그램 클론을 풀스택으로 완성합니다.