BlogFlow | 블로그

프론트엔드 개발과 IT 기술을 중심으로 실무 경험과 학습을 기록합니다.

웹퍼블리셔

media query로 버티면 복잡해지는 반응형, container query가 더 맞는 경우

2026.03.27·약 14분

이 글에서 정리하는 내용

저는 웹퍼블리싱 실무에서 media query만으로 버티면 CSS 분기가 왜 점점 복잡해지는지, 그리고 어떤 순간에 판단 기준을 컨테이너 쿼리로 옮겨야 하는지를 정리해보겠습니다. 이 글을 끝까지 보면 페이지 전체 반응형은 media query로, 재사용 컴포넌트 반응형은 부모 박스 기준으로 나누는 기준이 훨씬 분명해집니다. 특히 카드, 배너, 위젯처럼 같은 UI를 여러 자리에서 재사용하는 프로젝트일수록 이 차이가 크게 드러납니다. 여기에 더해 실제 적용 순서, 자주 막히는 이유, 관련 유닛과 style query, scroll-state query까지 함께 정리해 두면 실무에서 다시 찾아보기 쉬운 기준서가 됩니다.

media query와 컨테이너 쿼리의 기준 차이

ChatGPT Image 2026년 3월 27일 오전 10 19 26

저는 이 둘의 차이를 문법보다 기준점으로 먼저 이해하는 편이 실무에서 훨씬 도움이 된다고 봅니다. media query는 브라우저 viewport를 기준으로 스타일을 바꾸고, 컨테이너 쿼리는 컴포넌트를 감싸는 부모 컨테이너의 크기나 상태를 기준으로 스타일을 바꿉니다. 그래서 화면은 충분히 넓어도, 어떤 카드가 사이드바처럼 좁은 영역에 들어가면 viewport 기준 분기만으로는 원하는 대응이 어려워질 수 있습니다.

같은 컴포넌트라도 위치에 따라 다르게 보여야 할 수 있습니다

.card {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

@media (max-width: 768px) {
  .card {
    grid-template-columns: 1fr;
  }
}

이 방식은 화면 전체가 좁아질 때는 잘 동작합니다. 하지만 저는 메인 영역에서는 2열 카드가 필요하고, 같은 화면 안의 사이드바에서는 같은 카드를 1열로 바꾸고 싶을 때 media query만으로는 한계를 자주 느낍니다. 이때 기준을 viewport가 아니라 카드가 놓인 부모 박스로 옮기는 것이 핵심입니다. 특히 공통 컴포넌트를 여러 페이지와 여러 칼럼에서 재사용하는 구조일수록 이 차이가 크게 체감됩니다.

media query가 더 적절한 상황

모든 반응형을 부모 기준 방식으로 바꿀 필요는 없습니다. 저는 사이트 전체 구조가 화면 크기에 따라 바뀌는 문제라면 여전히 media query가 더 자연스럽다고 판단합니다. 예를 들어 모바일에서 헤더 전체 네비게이션이 햄버거 메뉴로 바뀌거나, 페이지 바깥 공통 여백이 줄어들거나, 3단 레이아웃이 1단으로 바뀌는 상황은 viewport 기준이 더 맞습니다. 반대로 개별 카드, 박스, 배너 내부 레이아웃이 달라져야 하는 경우라면 컨테이너 쿼리를 먼저 검토하는 편이 낫습니다.

페이지 전체 구조는 여전히 media query가 편합니다

.layout {
  display: grid;
  grid-template-columns: 240px 1fr 320px;
}

@media (max-width: 1024px) {
  .layout {
    grid-template-columns: 1fr;
  }
}

저는 이런 경우를 보면 화면이 좁아졌기 때문에 전체 레이아웃이 바뀌는 것이므로 media query가 더 읽기 쉽고 유지보수도 쉽다고 봅니다. 즉, 페이지 전체 규칙이면 media query, 컴포넌트 자체 규칙이면 부모 기준 분기라는 기준으로 먼저 나누면 판단이 빨라집니다. 이렇게 역할을 분리해 두면 CSS가 전역 규칙과 컴포넌트 규칙으로 깔끔하게 나뉘는 장점도 있습니다.

상황 더 적절한 선택
헤더, 전체 컬럼, 전역 여백처럼 페이지 구조가 바뀜 media query
카드, 배너, 위젯처럼 같은 컴포넌트가 놓인 자리마다 달라짐 컨테이너 쿼리

컨테이너 쿼리를 써야 하는 순간

.card-list {
  container-type: inline-size;
}

.card {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 16px;
}

@container (max-width: 520px) {
  .card {
    grid-template-columns: 1fr;
  }
}

저는 여기서 이 방식의 장점이 가장 선명하게 드러난다고 생각합니다. 위 코드는 화면 전체가 아니라 .card-list의 가로 폭이 520px 이하일 때만 카드 구조를 바꿉니다. 즉, 같은 카드 컴포넌트라도 메인 3열 그리드 안에서는 가로형으로 보이고, 사이드 영역이나 좁은 위젯 박스 안에서는 세로형으로 자연스럽게 바뀔 수 있습니다. 또 이 규칙은 containment context가 있는 가장 가까운 조상을 기준으로 찾기 때문에, 어떤 부모를 컨테이너로 선언했는지에 따라 결과가 달라질 수 있습니다.

실무에서 특히 잘 맞는 경우

<section class="dashboard-box">
  <div class="stats-card">...</div>
</section>

<aside class="sidebar-box">
  <div class="stats-card">...</div>
</aside>

저는 관리자 페이지, 카드 리스트, 프로모션 배너, 쇼핑몰 상품 요약 박스처럼 재사용 컴포넌트가 많은 화면에서 이 방식이 특히 유리하다고 봅니다. 같은 stats-card라도 대시보드 본문에서는 넓게, 사이드바에서는 압축형으로 보여야 할 때가 많기 때문입니다. 이런 구조를 media query만으로 분기하면 특정 페이지 예외가 계속 늘어나고, 결국 컴포넌트보다 페이지 의존적인 CSS가 되어버리기 쉽습니다. 반대로 이 방식을 사용하면 컴포넌트가 어디에 배치되었는지에 따라 스스로 모양을 조정하는 구조에 더 가까워집니다.

이런 신호가 보이면 컨테이너 쿼리를 검토합니다

저는 아래와 같은 신호가 반복되면 컨테이너 쿼리를 고려합니다. 같은 컴포넌트인데 페이지마다 별도 클래스가 계속 붙는 경우, 특정 영역에서만 카드 썸네일 방향이나 버튼 정렬이 달라지는 경우, 같은 viewport에서도 본문 칼럼과 사이드바 칼럼에서 모습이 달라져야 하는 경우, 슬라이드 내부와 일반 리스트 내부에서 배치 규칙이 달라지는 경우입니다. 이런 경우는 대개 화면 전체 문제가 아니라 컴포넌트가 들어간 부모 폭 문제이기 때문에 컨테이너 쿼리로 옮기는 편이 더 깔끔합니다.

컨테이너 쿼리 사용 방법과 심화 활용

ChatGPT Image 2026년 3월 27일 오전 10 19 20

제가 처음 이 기능을 쓸 때 가장 많이 만난 문제는 @container 문법 자체보다, 부모에 컨테이너 선언을 하지 않아서 전혀 동작하지 않는 경우였습니다. 이 기능은 그냥 작성한다고 실행되지 않고, 먼저 기준이 될 부모 요소를 쿼리 가능한 컨테이너로 지정해야 합니다. 실무에서는 보통 가로 폭 기준 반응형이 많기 때문에 inline-size를 먼저 쓰는 경우가 많습니다. 대부분의 한국어 사이트는 기본 writing-mode가 가로쓰기이므로 inline-size를 거의 width처럼 받아들여도 되지만, 개념상으로는 쓰기 방향을 고려한 인라인 축 기준이라는 점까지 알고 있으면 문서를 읽을 때 덜 헷갈립니다.

기본 사용 순서는 생각보다 단순합니다

<div class="product-group">
  <article class="product-item">
    <h3>상품명</h3>
    <p>설명 문구</p>
  </article>
</div>

저는 이 기능을 쓸 때 먼저 어떤 부모를 기준으로 삼을지부터 정합니다. 그 다음 그 부모에 container-type을 선언하고, 마지막에 자식 컴포넌트에 @container 규칙을 붙입니다. 순서를 거꾸로 생각하면 왜 적용이 안 되는지 찾기 어려워집니다. 결국 핵심은 자식에 쿼리를 쓰는 것이 아니라, 부모에게 먼저 질의 가능한 맥락을 만들어 주는 것입니다.

부모 컨테이너를 먼저 선언해야 합니다

.product-group {
  container: product-group / inline-size;
}

.product-item {
  display: grid;
  gap: 12px;
}

@container product-group (min-width: 700px) {
  .product-item {
    grid-template-columns: 160px 1fr;
    align-items: start;
  }
}

저는 이름이 있는 컨테이너를 쓰면 구조가 복잡한 프로젝트에서 어느 부모를 기준으로 삼는지 파악하기 쉬워진다고 느낍니다. 다만 모든 요소를 무조건 컨테이너로 만들 필요는 없습니다. 실제로 레이아웃 분기 기준이 되는 래퍼만 지정하는 편이 관리하기 좋고, 기존 프로젝트에도 필요한 컴포넌트부터 부분 도입하는 방식이 안전합니다. 처음부터 전부 교체하려 하기보다 카드, 위젯, 배너처럼 효과가 큰 컴포넌트 한두 개에서 시작하는 편이 실무적으로 훨씬 안정적입니다.

컨테이너 쿼리 유닛은 크기 값을 더 유연하게 만듭니다

.notice-box {
  container-type: inline-size;
}

.notice-box .title {
  font-size: clamp(18px, 2cqi, 28px);
}

.notice-box .desc {
  font-size: clamp(14px, 1.4cqi, 18px);
  padding-inline: 4cqw;
}

이 부분은 실무에서 생각보다 유용합니다. 저는 보통 breakpoint에 맞춰 글자 크기와 여백을 여러 번 끊어 주었는데, 관련 유닛을 쓰면 컨테이너 자체 크기에 비례해 더 자연스럽게 조절할 수 있습니다. 예를 들어 cqw는 컨테이너 width의 비율이고, cqi는 컨테이너 inline-size의 비율입니다. 그래서 카드가 어느 칼럼에 들어가든 같은 공식으로 타이포그래피와 여백을 조절하기가 좋아집니다.

style query는 부모 스타일 값에 따라 자식을 바꿀 때 유용합니다

.theme-card {
  --card-tone: dark;
}

@container style(--card-tone: dark) {
  .theme-card__title,
  .theme-card__desc {
    color: #ffffff;
  }
}

저는 이 기능을 테마 분기나 디자인 시스템 보조 수단으로 이해하는 편이 좋다고 봅니다. 크기가 아니라 부모의 스타일 값이나 커스텀 프로퍼티를 기준으로 자식 스타일을 바꿀 수 있기 때문입니다. 예를 들어 카드 래퍼에 지정한 사용자 정의 속성 값에 따라 텍스트 톤, 버튼 대비, 뱃지 스타일을 바꾸는 식으로 활용할 수 있습니다. 다만 초반에는 크기 기반 컨테이너 쿼리부터 익히고, 그 다음 style query로 넘어가는 편이 부담이 적습니다.

scroll-state query는 스크롤 상태에 반응하는 UI에 연결할 수 있습니다

.section-nav {
  container-type: scroll-state;
}

@container scroll-state(stuck: top) {
  .section-nav {
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
  }
}

이 기능은 아직 모든 화면에 바로 넣기보다, 목적이 분명할 때 쓰는 편이 좋습니다. 저는 sticky 상태가 되었을 때 상단 네비게이션의 그림자를 주거나, scroll snap에 맞춰 현재 카드의 강조 상태를 바꾸는 식의 UI에 연결하는 그림으로 이해하면 쉽다고 봅니다. 즉, 단순 반응형 레이아웃 도구라기보다 스크롤 맥락에 반응하는 선언형 스타일링 도구에 가깝습니다.

정리

저는 반응형 작업을 할 때 먼저 질문을 하나 던집니다. 이 변화가 화면 전체 크기 때문에 생기는가, 아니면 컴포넌트가 들어간 자리의 폭 때문에 생기는가입니다. 전자라면 media query가 맞고, 후자라면 부모 기준 분기가 훨씬 자연스럽습니다. 여기에 더해 크기 비례 값을 다루는 관련 유닛, 부모 스타일 값을 기준으로 삼는 style query, 스크롤 상태를 읽는 scroll-state query까지 이해해 두면 단순 소개 수준을 넘어 실제 설계 판단까지 연결할 수 있습니다. 결국 핵심은 둘 중 하나를 버리는 것이 아니라, 기준점을 정확히 나눠서 쓰는 것입니다.

많이 받는 질문

Q. 이 방식이 media query를 완전히 대체하나요?
아닙니다. 저는 페이지 전체 구조, 전역 여백, 헤더 전환처럼 viewport 기준 반응형은 media query로 두고, 재사용 컴포넌트 내부 분기만 부모 기준 방식으로 나누는 방식을 권장합니다.

Q. 왜 @container를 썼는데 동작하지 않나요?
대부분 부모 요소에 container-type 또는 container 단축 속성을 지정하지 않았기 때문입니다. 이 기능은 기준이 되는 부모 컨테이너가 먼저 선언되어 있어야 합니다.

Q. 실무에서는 size와 inline-size 중 무엇을 더 자주 쓰나요?
저는 보통 가로 폭 변화에 맞춰 레이아웃을 조정하는 경우가 많아서 inline-size를 먼저 고려합니다. 특별히 가로와 세로를 모두 기준으로 삼아야 하는 상황이 아니라면 inline-size가 더 실용적인 편입니다.

Q. 이름 있는 컨테이너는 언제 필요한가요?
가까운 조상 컨테이너가 여러 개일 때, 어떤 부모를 기준으로 삼을지 더 명확하게 지정하고 싶다면 이름 있는 컨테이너가 유용합니다. 컴포넌트가 중첩된 관리자 화면이나 복잡한 콘텐츠 레이아웃에서 특히 도움이 됩니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기