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

반응형 화면을 만들 때 가장 익숙한 방법은 viewport 폭을 기준으로 하는 미디어 쿼리입니다. 이 방식은 전체 페이지 레이아웃을 바꿀 때 유용하지만, 실제 서비스 화면에서는 같은 카드 컴포넌트가 메인 영역, 사이드바, 모달, 관리자 대시보드 위젯처럼 서로 다른 폭의 컨테이너 안에 들어갑니다. 이때 화면 전체 폭은 넓어도 카드가 들어간 영역은 좁을 수 있고, 반대로 모바일에서도 특정 패널은 충분히 넓을 수 있습니다. viewport 기준만 사용하면 컴포넌트가 놓인 실제 공간을 반영하지 못해 글자가 과하게 줄바꿈되거나 버튼이 밀리고, 불필요한 변형 클래스를 계속 추가하게 됩니다.

CSS 컨테이너 쿼리는 화면이 아니라 부모 컨테이너의 크기를 기준으로 스타일을 바꿉니다. 컴포넌트가 자기 주변 공간을 보고 레이아웃을 결정하므로 재사용성이 좋아집니다. 특히 상품 카드, 게시글 목록, 통계 위젯, 검색 결과, 관리자 테이블의 보조 패널처럼 여러 화면에 반복되는 UI에서 효과가 큽니다. 핵심은 모든 요소에 무작정 적용하는 것이 아니라, 레이아웃 판단의 기준이 되는 바깥 래퍼를 컨테이너로 지정하고 그 안의 자식 요소만 컨테이너 쿼리로 조정하는 것입니다.

기본 구조 설계

컨테이너 쿼리를 쓰려면 기준 요소에 container-type을 지정합니다. 일반적인 반응형 컴포넌트는 가로 폭만 판단하면 되므로 inline-size가 가장 무난합니다. 이름을 붙이면 여러 컨테이너가 중첩됐을 때 어떤 컨테이너를 기준으로 삼을지 명확하게 지정할 수 있습니다. 아래 예제는 같은 article-card가 좁은 영역에서는 세로형, 넓은 영역에서는 이미지와 본문이 나란히 놓이는 형태로 바뀌는 구성입니다.

.article-card-shell {
  container-type: inline-size;
  container-name: article-card;
}

.article-card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  padding: 16px;
  border: 1px solid #d7dde5;
  border-radius: 8px;
  background: #fff;
}

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

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

@container article-card (min-width: 520px) {
  .article-card {
    grid-template-columns: 180px minmax(0, 1fr);
    align-items: start;
  }

  .article-card__media {
    aspect-ratio: 4 / 3;
  }

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

이 코드는 화면 전체가 아니라 .article-card-shell의 실제 폭이 520px 이상일 때만 가로형 레이아웃으로 전환합니다. 따라서 같은 컴포넌트를 3단 그리드, 넓은 상세 페이지, 좁은 사이드 패널에 넣어도 각 위치에 맞게 동작합니다. 중요한 점은 컨테이너로 지정한 요소 자체를 컨테이너 쿼리 안에서 직접 바꾸는 방식보다, 그 안의 실제 컴포넌트 요소를 바꾸는 방식이 예측하기 쉽다는 것입니다.

운영에서 자주 생기는 문제

첫 번째 문제는 컨테이너 지정 위치입니다. 너무 바깥쪽 페이지 섹션에 지정하면 컴포넌트 단위 반응형의 장점이 줄어들고, 너무 안쪽에 지정하면 기준 폭이 의도보다 작아져 조건이 실행되지 않습니다. 보통 목록 아이템 하나, 카드 하나, 위젯 하나를 감싸는 셸 요소가 적절합니다. 두 번째 문제는 고정 폭과 최소 폭입니다. 자식 요소에 min-width가 크게 잡혀 있으면 컨테이너가 좁아져도 내부가 넘칠 수 있습니다. 그리드나 플렉스 자식에는 필요에 따라 min-width: 0을 지정해 긴 제목과 설명이 부모 안에서 줄어들 수 있게 해야 합니다.

세 번째 문제는 버튼과 긴 텍스트입니다. 컨테이너 쿼리로 열 수를 바꾸더라도 버튼 라벨, 가격, 날짜, 상태 배지 같은 텍스트가 긴 경우에는 작은 폭에서 쉽게 깨집니다. 버튼 영역은 flex-wrap을 기본값으로 두고, 좁은 조건에서는 버튼을 100% 폭으로 내리는 규칙을 별도로 두는 편이 안정적입니다. 또한 제목에는 줄 수 제한을 걸더라도 본문이나 오류 메시지처럼 의미 전달이 필요한 텍스트에는 무리한 말줄임을 남발하지 않는 것이 좋습니다.

미디어 쿼리와 함께 쓰는 기준

컨테이너 쿼리가 미디어 쿼리를 완전히 대체하는 것은 아닙니다. 페이지 전체 내비게이션, 헤더의 고정 여부, 좌우 사이드바 표시, 전체 그리드 열 수처럼 화면 구조를 바꾸는 일은 미디어 쿼리가 더 적합합니다. 반면 카드 내부의 이미지 비율, 버튼 정렬, 제목 크기, 보조 정보 배치처럼 컴포넌트 내부가 실제로 받은 공간에 반응해야 하는 일은 컨테이너 쿼리가 더 적합합니다. 기준을 섞을 때는 페이지 레벨은 미디어 쿼리, 컴포넌트 레벨은 컨테이너 쿼리로 나누면 유지보수가 쉬워집니다.

  • 페이지 골격 변경: 미디어 쿼리를 우선 사용합니다.
  • 재사용 컴포넌트 내부 변경: 컨테이너 쿼리를 우선 사용합니다.
  • 카드, 위젯, 검색 결과처럼 여러 위치에 들어가는 UI는 컨테이너 이름을 붙입니다.
  • 긴 텍스트와 버튼 영역은 줄바꿈, 최소 폭, 넘침 처리를 함께 점검합니다.

적용 전 점검 체크리스트

기존 프로젝트에 컨테이너 쿼리를 도입할 때는 한 번에 모든 반응형 코드를 바꾸기보다 반복 사용되는 컴포넌트 하나를 고르는 것이 좋습니다. 먼저 해당 컴포넌트가 실제 서비스에서 어떤 폭으로 나타나는지 개발자 도구나 스토리북 뷰포트로 확인합니다. 그 다음 좁음, 보통, 넓음의 기준 폭을 2개 이하로 정하고, 각 구간에서 꼭 바뀌어야 하는 속성만 수정합니다. 조건이 많아질수록 유지보수가 어려워지므로, 글꼴 크기와 여백을 과도하게 세분화하기보다 레이아웃 구조와 버튼 배치처럼 사용성에 직접 영향을 주는 부분부터 바꾸는 편이 좋습니다.

마지막으로 실제 데이터로 검증해야 합니다. 짧은 제목만 넣은 샘플 화면에서는 문제가 없어 보여도 긴 상품명, 여러 줄 주소, 권한 이름, 오류 메시지, 다국어 텍스트가 들어오면 레이아웃이 달라집니다. 컨테이너 쿼리는 컴포넌트를 더 똑똑하게 만드는 도구이지만, 안정적인 반응형 UI는 기준 컨테이너 지정, 최소 폭 처리, 실제 데이터 검증이 함께 맞아야 완성됩니다. 적용 순서는 기준 컨테이너를 정하고, 가장 불편한 폭을 재현한 뒤, 필요한 전환점만 추가하고, 실제 데이터로 넘침과 버튼 배치를 확인하는 흐름으로 잡으면 됩니다.