모던 CSS 레이아웃 — Grid, Flexbox, Container Query

언제 Grid이고, 언제 Flexbox인가?

Flexbox와 Grid는 모두 CSS 레이아웃 도구지만, 각각 강점이 다릅니다. Flexbox는 **한 방향(가로 또는 세로)**의 배치에 강하고, Grid는 **두 방향(가로 + 세로)**의 배치에 강합니다.

책장에 비유하면, Flexbox는 한 줄 선반에 책을 나란히 놓는 것이고, Grid는 격자형 수납장 전체를 설계하는 것입니다.

기준FlexboxGrid
차원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 구조를 시각적으로 확인할 수 있습니다.

이 글이 도움이 되었나요?