왜 미디어 쿼리만으로는 부족한가

반응형 화면을 만들 때 가장 흔한 기준은 뷰포트 너비입니다. 그러나 운영 화면, 관리자 대시보드, 상품 목록, 문서 포털처럼 같은 컴포넌트가 여러 위치에 재사용되는 서비스에서는 뷰포트만으로 충분하지 않습니다. 예를 들어 동일한 카드 컴포넌트가 메인 영역에서는 3열 그리드 안에 들어가고, 사이드바에서는 좁은 추천 목록으로 들어가며, 모달 안에서는 중간 너비로 표시될 수 있습니다. 이때 뷰포트 기준 미디어 쿼리로만 처리하면 화면 전체는 넓지만 컴포넌트 자신은 좁은 상황을 놓치기 쉽습니다.

CSS 컨테이너 쿼리는 이 문제를 해결하기 위한 실무형 도구입니다. MDN 문서 기준으로 container-type은 요소를 쿼리 컨테이너로 만들며, size 또는 inline-size 같은 컨테이너 문맥을 지정합니다. @container 규칙은 가장 가까운 조건에 맞는 조상 컨테이너의 크기를 기준으로 자식 스타일을 바꿉니다. 특히 inline-size는 가로 쓰기 모드에서 주로 너비 기준으로 동작하므로 카드, 폼, 검색 결과, 테이블 대체 목록 같은 컴포넌트에 적용하기 좋습니다. 컨테이너 쿼리는 2023년 이후 주요 브라우저에서 널리 사용할 수 있는 기능이 되었기 때문에, 오래된 브라우저만 별도로 지원해야 하는 프로젝트가 아니라면 점진적으로 도입할 만합니다.

적용 기준 정하기

컨테이너 쿼리를 아무 곳에나 붙이면 CSS가 복잡해집니다. 먼저 컴포넌트가 어떤 부모 안에 들어가도 자기 폭에 맞게 변해야 하는지 확인해야 합니다. 대표적인 대상은 카드, 위젯, 검색 결과 항목, 가격표, 문의 폼, 파일 목록입니다. 반대로 페이지 전체 헤더, 전역 내비게이션, 푸터처럼 뷰포트와 강하게 묶인 요소는 기존 미디어 쿼리가 더 단순합니다.

  • 컴포넌트 단위로 재사용되는 영역에만 컨테이너를 선언합니다.
  • 너비 조건만 필요하면 container-type: inline-size를 우선 사용합니다.
  • 컨테이너 이름은 컴포넌트 역할 기준으로 짓고, 화면 위치 이름은 피합니다.
  • 기본 스타일은 가장 좁은 상태를 기준으로 만들고, 넓어질 때만 확장합니다.
  • 카드 내부 여백, 이미지 비율, 버튼 배치까지 함께 점검합니다.

카드 컴포넌트 예제

아래 예시는 문서 목록 카드가 좁은 영역에서는 세로형으로 보이고, 480px 이상에서는 이미지와 본문이 나란히 배치되며, 720px 이상에서는 메타 정보와 버튼 영역의 여백을 넓히는 구조입니다. 핵심은 카드 자체가 아니라 카드가 들어 있는 래퍼를 컨테이너로 지정하는 것입니다. 자식 요소는 @container 조건 안에서만 레이아웃을 바꿉니다.

.doc-card-wrap {
  container: doc-card / inline-size;
}

.doc-card {
  display: grid;
  gap: 16px;
  padding: 18px;
  border: 1px solid #d8dee4;
  border-radius: 8px;
  background: #fff;
}

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

.doc-card__title {
  margin: 0 0 8px;
  font-size: 1.125rem;
  line-height: 1.35;
}

.doc-card__summary {
  margin: 0;
  color: #4b5563;
  line-height: 1.65;
}

.doc-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

@container doc-card (min-width: 480px) {
  .doc-card {
    grid-template-columns: 160px 1fr;
    align-items: start;
  }

  .doc-card__thumb {
    aspect-ratio: 4 / 3;
  }
}

@container doc-card (min-width: 720px) {
  .doc-card {
    grid-template-columns: 220px 1fr;
    padding: 24px;
  }

  .doc-card__actions {
    justify-content: flex-end;
  }
}

HTML은 복잡할 필요가 없습니다. 래퍼가 컨테이너 역할을 맡고 실제 카드 마크업은 기존 구조를 유지하면 됩니다. 접근성을 위해 제목은 문서의 제목 계층에 맞는 h 태그를 사용하고, 이미지는 의미가 있으면 대체 텍스트를 넣고 장식이면 비워 둡니다.

<div class="doc-card-wrap">
  <article class="doc-card">
    <img class="doc-card__thumb" src="/images/api-guide.jpg" alt="API 문서 화면 예시">
    <div class="doc-card__body">
      <h3 class="doc-card__title">API 오류 응답 표준화 가이드</h3>
      <p class="doc-card__summary">운영 중인 서비스에서 오류 코드, 메시지, 추적 ID를 일관되게 전달하는 방법을 정리합니다.</p>
      <div class="doc-card__actions">
        <a href="/docs/api-error">문서 보기</a>
      </div>
    </div>
  </article>
</div>

운영 화면에서 자주 생기는 실수

첫 번째 실수는 컨테이너를 카드 자신에게 선언하고 카드 내부에서 자기 크기를 기준으로 자기 스타일을 바꾸려는 것입니다. 컨테이너 쿼리는 컨테이너의 자식에게 적용됩니다. 따라서 보통 카드의 부모 래퍼를 컨테이너로 만들고, 그 안의 카드와 내부 요소를 조정하는 방식이 안전합니다. 두 번째 실수는 모든 컴포넌트에 같은 기준값을 복사하는 것입니다. 480px, 720px 같은 숫자는 시작점일 뿐이며 이미지 비율, 제목 길이, 버튼 개수, 실제 데이터 길이에 따라 조정해야 합니다.

세 번째 실수는 미디어 쿼리와 컨테이너 쿼리의 역할을 섞는 것입니다. 화면 전체의 사이드바 노출, 앱 쉘 구조, 상단 메뉴 접힘은 미디어 쿼리로 처리하고, 카드 내부의 이미지 위치나 폼 필드 배치는 컨테이너 쿼리로 처리하는 식으로 책임을 나누는 것이 좋습니다. 이렇게 하면 페이지 레이아웃이 바뀌어도 컴포넌트 CSS를 다시 쓰는 일이 줄어듭니다.

점진적 도입 방법

이미 운영 중인 사이트라면 새 컴포넌트부터 적용하고, 기존 컴포넌트는 문제가 반복되는 영역부터 바꾸는 것이 안전합니다. 예를 들어 검색 결과 카드가 데스크톱의 좁은 필터 옆 영역에서 깨진다면 그 목록 래퍼에만 컨테이너를 부여합니다. 이후 같은 카드가 추천 영역, 관련 문서, 관리자 미리보기에서도 쓰인다면 공통 컴포넌트 스타일로 올릴 수 있습니다.

@supports (container-type: inline-size) {
  .search-result-item-wrap {
    container: search-result / inline-size;
  }

  @container search-result (max-width: 420px) {
    .search-result-item__meta {
      display: block;
    }

    .search-result-item__button {
      width: 100%;
    }
  }
}

@supports를 사용하면 컨테이너 쿼리를 지원하는 브라우저에만 개선 스타일을 제공할 수 있습니다. 기본 스타일은 지원하지 않는 환경에서도 읽을 수 있는 단일 열 구조로 두는 것이 좋습니다. 이렇게 하면 일부 브라우저에서 고급 레이아웃이 적용되지 않더라도 콘텐츠 접근성은 유지됩니다.

마무리 체크리스트

  • 컨테이너 쿼리는 화면 너비가 아니라 컴포넌트가 놓인 영역의 너비를 기준으로 삼아야 할 때 사용합니다.
  • 기본 CSS는 좁은 상태를 먼저 작성하고, @container min-width 조건으로 넓은 상태를 추가합니다.
  • 컨테이너는 보통 컴포넌트 자신보다 한 단계 바깥 래퍼에 선언합니다.
  • 미디어 쿼리는 페이지 구조, 컨테이너 쿼리는 컴포넌트 내부 구조에 맡기면 유지보수가 쉬워집니다.
  • 실제 데이터 길이, 이미지 비율, 버튼 개수, 관리자 화면의 좁은 패널까지 함께 테스트합니다.