DevToolBoxFREE
BlogAdvertise

CSS Container Queries 완전 가이드 2026

13분by DevToolBox

CSS 컨테이너 쿼리는 미디어 쿼리 이후 반응형 디자인에서 가장 중요한 발전입니다. 뷰포트 크기가 아닌 부모 컨테이너의 크기에 응답하는 컴포넌트를 구축할 수 있습니다.

컨테이너 쿼리란?

컨테이너 쿼리는 뷰포트가 아닌 포함 요소의 크기에 기반하여 스타일을 적용합니다.

/* The Problem: A card component in different contexts */

/* In main content (800px wide) → horizontal layout */
/* In sidebar (300px wide) → vertical layout */
/* In footer (200px wide) → minimal layout */

/* With media queries, you cannot distinguish these contexts
   because they all exist at the same viewport width.

   With container queries, each container context triggers
   different styles automatically. */

컨테이너 쿼리 vs 미디어 쿼리

미디어 쿼리는 뷰포트 크기에만 응답합니다. 컨테이너 쿼리는 이 근본적 제한을 해결합니다.

FeatureMedia QueriesContainer Queries
Responds toViewport sizeContainer size
ScopeGlobal (page-level)Local (component-level)
ReusabilityBreaks in different contextsWorks in any context
Use casePage layoutComponent layout
Unitsvw, vh, vmin, vmaxcqw, cqh, cqi, cqb
Browser supportUniversalAll modern browsers (2023+)

컨테인먼트 이해

컨테이너 쿼리에는 컨테인먼트가 필요합니다.

컨테인먼트 유형

/* container-type values */

/* inline-size: Containment on the inline axis only (width) */
/* Most common — use this in most cases */
.card-wrapper {
  container-type: inline-size;
}

/* size: Containment on both inline and block axes (width + height) */
/* Use when you need to query both dimensions */
.widget-wrapper {
  container-type: size;
}

/* normal (default): No containment — cannot be queried */
.regular-div {
  container-type: normal; /* This is the default */
}

/* IMPORTANT: container-type: size prevents auto-height behavior.
   The element will not grow to fit its content on the block axis.
   Use inline-size unless you specifically need height queries. */

컨테이너 쿼리 구문

기본 구문은 container-type으로 컨테이너를 정의하고 @container로 쿼리합니다.

/* Step 1: Define a container */
.card-container {
  container-type: inline-size;
}

/* Step 2: Write container queries */
.card {
  /* Default styles (smallest) */
  display: grid;
  gap: 12px;
  padding: 16px;
}

.card__image {
  width: 100%;
  aspect-ratio: 16/9;
  border-radius: 8px;
  object-fit: cover;
}

.card__title {
  font-size: 1rem;
  font-weight: 700;
}

/* When container is at least 400px wide */
@container (min-width: 400px) {
  .card {
    grid-template-columns: 200px 1fr;
    align-items: center;
  }
  .card__title {
    font-size: 1.25rem;
  }
}

/* When container is at least 600px wide */
@container (min-width: 600px) {
  .card {
    grid-template-columns: 280px 1fr;
    gap: 24px;
    padding: 24px;
  }
  .card__title {
    font-size: 1.5rem;
  }
  .card__description {
    display: block; /* Show description only in wide containers */
  }
}

명명된 컨테이너

특정 조상을 대상으로 컨테이너에 이름을 지정할 수 있습니다.

/* Name containers to target specific ancestors */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* Query a specific named container */
@container sidebar (max-width: 300px) {
  .widget {
    font-size: 0.875rem;
  }
}

@container main (min-width: 800px) {
  .article-card {
    grid-template-columns: 1fr 1fr;
  }
}

/* Without a name, @container queries the NEAREST ancestor
   with containment. Named queries skip to the named container. */

container 축약형

container-type과 container-name을 하나로 결합합니다.

/* Shorthand: container: <name> / <type> */
.sidebar {
  container: sidebar / inline-size;
}

/* Equivalent to: */
.sidebar {
  container-name: sidebar;
  container-type: inline-size;
}

/* Multiple names (for querying by either name) */
.widget-area {
  container: widget-area panel / inline-size;
}

컨테이너 쿼리 단위

컨테이너 크기에 상대적인 새로운 CSS 단위가 도입되었습니다.

UnitDescriptionEquivalent
cqw1% of container widthSimilar to vw for viewport
cqh1% of container heightSimilar to vh for viewport
cqi1% of container inline sizeWidth in horizontal writing
cqb1% of container block sizeHeight in horizontal writing
cqminSmaller of cqi and cqbSimilar to vmin
cqmaxLarger of cqi and cqbSimilar to vmax
/* Fluid typography with container query units */
.card-title {
  /* Font size scales with container width */
  /* Minimum 1rem, maximum 2rem, scales at 5cqi */
  font-size: clamp(1rem, 5cqi, 2rem);
}

.card-padding {
  /* Responsive padding based on container */
  padding: clamp(12px, 4cqi, 32px);
}

.hero-text {
  /* Large text that scales with its container */
  font-size: clamp(2rem, 8cqi, 5rem);
  line-height: 1.1;
}

실제 예제

반응형 카드 컴포넌트

컨테이너 공간에 적응하는 카드 컴포넌트.

/* Responsive card component */
.card-container {
  container: card / inline-size;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border-radius: 12px;
  background: var(--card-bg);
  border: 1px solid var(--border-color);
}

.card__image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 8px;
}

.card__meta {
  display: none; /* Hidden in small containers */
}

.card__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* Medium card (400px+) — side-by-side layout */
@container card (min-width: 400px) {
  .card {
    flex-direction: row;
    align-items: flex-start;
  }
  .card__image {
    width: 180px;
    flex-shrink: 0;
    aspect-ratio: 1;
  }
  .card__meta {
    display: flex;
    gap: 12px;
    font-size: 0.875rem;
    color: var(--text-muted);
  }
}

/* Large card (700px+) — featured layout */
@container card (min-width: 700px) {
  .card {
    flex-direction: column;
    padding: 0;
    overflow: hidden;
  }
  .card__image {
    width: 100%;
    height: 300px;
    border-radius: 0;
  }
  .card__content {
    padding: 24px;
  }
  .card__title {
    font-size: 1.75rem;
  }
}

반응형 내비게이션

컨테이너 너비에 따라 레이아웃을 전환하는 내비게이션.

/* Navigation that adapts to its container */
.nav-container {
  container: nav / inline-size;
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.nav__item {
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 0.875rem;
}

.nav__item-icon {
  display: none;
}

/* Horizontal layout when container is wide enough */
@container nav (min-width: 500px) {
  .nav {
    flex-direction: row;
    gap: 8px;
  }
  .nav__item {
    white-space: nowrap;
  }
}

/* Show icons when there is more space */
@container nav (min-width: 700px) {
  .nav__item-icon {
    display: inline-flex;
    margin-right: 6px;
  }
  .nav__item {
    padding: 10px 16px;
  }
}

적응형 그리드 아이템

그리드 열 너비에 따라 레이아웃을 변경.

/* Grid items that adapt to their column width */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 24px;
}

/* Each grid item is a container */
.grid__item {
  container: grid-item / inline-size;
}

.product {
  text-align: center;
  padding: 16px;
}

.product__price {
  font-size: 1.5rem;
  font-weight: 700;
}

.product__details {
  display: none;
}

@container grid-item (min-width: 350px) {
  .product {
    display: grid;
    grid-template-columns: 120px 1fr;
    text-align: left;
    gap: 16px;
  }
  .product__details {
    display: block;
  }
}

스타일 컨테이너 쿼리

컨테이너의 계산된 스타일 값을 쿼리할 수 있습니다.

/* Style container queries — query computed style values */

/* Define a container with a custom property */
.theme-container {
  container-type: normal; /* No size containment needed for style queries */
  --theme: light;
}

.theme-container.dark {
  --theme: dark;
}

/* Query the custom property value */
@container style(--theme: dark) {
  .card {
    background: #1a1a2e;
    color: #e0e0e0;
    border-color: #333;
  }
  .button {
    background: #4a90d9;
    color: white;
  }
}

@container style(--theme: light) {
  .card {
    background: #ffffff;
    color: #333;
    border-color: #e0e0e0;
  }
}

/* Practical use: component variants via CSS properties */
.alert-container {
  --variant: info;
}

@container style(--variant: error) {
  .alert {
    background: #fef2f2;
    border-left: 4px solid #ef4444;
    color: #991b1b;
  }
}

@container style(--variant: success) {
  .alert {
    background: #f0fdf4;
    border-left: 4px solid #22c55e;
    color: #166534;
  }
}

중첩된 컨테이너 쿼리

여러 조상 컨테이너에 기반한 복잡한 반응형 동작.

/* Nested container queries */
.page {
  container: page / inline-size;
}

.sidebar {
  container: sidebar / inline-size;
}

/* Different behavior based on multiple container sizes */
@container page (min-width: 1200px) {
  .sidebar {
    width: 350px;
  }
}

@container sidebar (max-width: 250px) {
  .sidebar-widget {
    font-size: 0.875rem;
    padding: 8px;
  }
  .sidebar-widget__title {
    font-size: 1rem;
  }
}

@container sidebar (min-width: 300px) {
  .sidebar-widget {
    padding: 16px;
  }
  .sidebar-widget__chart {
    display: block;
  }
}

디자인 패턴

고유 반응형 디자인

CSS Grid auto-fit과 결합합니다.

/* Intrinsic design: Grid + Container Queries */
.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
}

.auto-grid > * {
  container-type: inline-size;
}

/* Items automatically adapt as the grid reflows */
/* No breakpoints needed — the grid column width drives the layout */
@container (min-width: 400px) {
  .feature-card {
    grid-template-columns: auto 1fr;
  }
}

@container (min-width: 500px) {
  .feature-card__description {
    font-size: 1.125rem;
  }
}

대시보드 위젯 패턴

그리드 셀에 적응하는 위젯 패턴.

/* Dashboard widget pattern */
.dashboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

.dashboard__cell {
  container: widget / inline-size;
}

.widget {
  padding: 16px;
  border-radius: 12px;
  background: var(--widget-bg);
}

.widget__chart {
  height: 150px;
}

.widget__table {
  display: none; /* Hidden in small widgets */
}

@container widget (min-width: 400px) {
  .widget__chart {
    height: 200px;
  }
  .widget__stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
  }
}

@container widget (min-width: 600px) {
  .widget {
    padding: 24px;
  }
  .widget__table {
    display: table;
  }
  .widget__chart {
    height: 280px;
  }
}

브라우저 지원

2026년 기준 우수한 브라우저 지원.

폴백 전략

@supports로 우아한 폴백을 제공.

/* Graceful fallback with @supports */
.card-container {
  /* Fallback: use media queries */
}

@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
}

/* Fallback styles using media queries */
@media (min-width: 768px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}

/* Container query override (when supported) */
@container (min-width: 400px) {
  .card {
    grid-template-columns: 200px 1fr;
  }
}

성능 고려사항

컨테인먼트 이해가 중요합니다.

  • 새로운 스태킹 컨텍스트를 생성.
  • 불필요한 중첩을 피함.
  • 너비만 필요하면 inline-size 사용.
  • 추가 레이아웃 패스 없음.
  • 명명된 컨테이너로 제어.

모범 사례

  1. 컴포넌트에 컨테이너 쿼리, 페이지에 미디어 쿼리.
  2. inline-size부터 시작.
  3. 컨테이너에 이름 지정.
  4. 컨테이너 쿼리 단위 사용.
  5. Grid와 Flexbox와 결합.
  6. 모바일 퍼스트로 설계.
  7. @supports로 폴백.

미디어 쿼리에서 마이그레이션

모든 미디어 쿼리를 대체할 필요는 없습니다.

/* Decision Guide: Container Query vs Media Query */

/* USE CONTAINER QUERIES when:
   ✅ Component is reused in different layout contexts
   ✅ Component goes in sidebars, modals, grids, etc.
   ✅ You need component-level responsive behavior
   ✅ The component's layout depends on available space

   USE MEDIA QUERIES when:
   ✅ Changing the overall page layout
   ✅ Showing/hiding entire sections
   ✅ Adjusting global typography or spacing
   ✅ Changing navigation from desktop to mobile
   ✅ Print stylesheets

   USE BOTH when:
   ✅ Page layout switches at viewport breakpoints (media query)
   ✅ Components within the layout adapt to their containers (container query)
*/

결론

CSS 컨테이너 쿼리는 반응형 디자인에 대한 사고 방식을 근본적으로 변경합니다. 현대 CSS 레이아웃 기법과 결합하여 더 모듈화되고 유지보수 가능한 디자인 시스템을 구축하세요.

FAQ

컨테이너 쿼리와 미디어 쿼리의 차이는?

미디어 쿼리는 뷰포트, 컨테이너 쿼리는 부모 컨테이너에 응답합니다.

모든 부모 요소에 container-type이 필요한가요?

아닙니다. 쿼리 컨테이너로 사용할 요소에만 설정합니다.

Grid나 Flexbox와 함께 사용할 수 있나요?

네, 완벽하게 작동합니다.

inline-size와 size의 차이는?

inline-size는 인라인 축만, size는 두 축 모두에 컨테인먼트를 생성합니다.

도움이 되었나요?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy