웹 개발에서 반응형 디자인은 필수죠. 지금까지는 주로 미디어 쿼리 (@media
)를 사용해 브라우저 뷰포트(viewport)의 크기에 따라 레이아웃이나 스타일을 변경해왔습니다. 하지만 미디어 쿼리는 전체 화면 크기에만 반응하기 때문에, 같은 컴포넌트라도 어디에 배치되느냐에 따라 다르게 보이고 싶을 때 한계가 있었습니다.
예를 들어, 똑같은 "상품 카드" 컴포넌트가 넓은 메인 영역에 있을 때와 좁은 사이드바에 있을 때, 보여주고 싶은 정보의 양이나 레이아웃이 다를 수 있습니다. 미디어 쿼리만으로는 이런 세밀한 제어가 어려웠죠.
바로 이 문제를 해결하기 위해 등장한 것이 CSS 컨테이너 쿼리입니다!
CSS 컨테이너 쿼리란?
CSS 컨테이너 쿼리는 특정 요소(컨테이너)의 크기나 스타일에 따라 그 자식 요소들의 스타일을 변경할 수 있게 해주는 강력한 기능입니다. 뷰포트 전체가 아닌, 자신을 감싸고 있는 부모 또는 특정 상위 요소(컨테이너)의 크기를 기준으로 반응형 디자인을 적용할 수 있게 된 것입니다. 이로써 훨씬 더 모듈화되고 재사용 가능한 컴포넌트를 만들 수 있게 되었습니다.
주요 개념 및 사용법
- 컨테이너 정의 (
container-type
,container-name
)container-type
: 컨테이너가 어떤 유형의 쿼리를 지원할지 지정합니다. (예:inline-size
는 너비 기반)container-name
(선택 사항): 특정 컨테이너를 명시적으로 타겟팅하기 위해 이름을 지정할 수 있습니다.
- 먼저, 어떤 요소의 크기를 기준으로 삼을지 "쿼리 컨테이너"를 지정해야 합니다. 이는
container-type
속성을 사용해 정의합니다. - 컨테이너에 반응하는 스타일 적용 (
@container
, 컨테이너 쿼리 단위)@container
규칙:@media
와 비슷하지만, 컨테이너의 크기를 조건으로 사용합니다.- 컨테이너 쿼리 길이 단위 (Container Query Length Units): 컨테이너 크기에 상대적인 단위입니다. (예:
cqi
는 컨테이너 인라인 크기의 1%)
- 컨테이너가 정의되면, 그 컨테이너의 크기에 따라 자식 요소들의 스타일을 변경합니다.
우리의 실제 사용 예시: 반응형 말풍선 폰트
이번 프로젝트에서 저희는 웹툰의 말풍선(SpeechBubbleText
) 컴포넌트 내부 텍스트가 말풍선이 그려지는 컨테이너(WebtoonContainer
)의 너비에 따라 유동적으로 변하길 원했습니다.
- 컨테이너 설정 (
WebtoonContainer.tsx
):
말풍선의 기준이 될WebtoonContainer
컴포넌트에containerType: "inline-size"
스타일을 적용하여 너비 기반 쿼리 컨테이너로 만들었습니다. // WebtoonContainer.tsx export function WebtoonContainer({ children, className = "" }: WebtoonContainerProps) { return ( <div className={`relative max-w-md mx-auto flex flex-col items-center w-full ${className}`} style={{ containerType: "inline-size" }} // ✅ 컨테이너로 지정! > {children} </div> ); }
- Tailwind CSS 설정 (
tailwind.config.js
):@tailwindcss/container-queries
플러그인을 추가하여 Tailwind 내에서 컨테이너 쿼리 단위를 사용할 수 있도록 했습니다.fontSize
테마를 확장하여bubble
이라는 커스텀 폰트 크기를 정의했습니다. 이때clamp()
함수와cqi
단위를 사용하여,WebtoonContainer
너비가 특정 범위 내에서 변할 때 폰트 크기가 최소0.875rem
에서 최대1.25rem
사이로 부드럽게 조절되도록 설정했습니다. (예:bubble: "clamp(0.875rem, 4.46cqi, 1.25rem)"
)- 이 설정으로
.text-bubble
이라는 유틸리티 클래스가 생성됩니다.
// tailwind.config.js // ... (imports) export default { // ... (content) safelist: ['text-bubble'], // ✅ 동적 클래스명 사용을 위한 safelist 설정 theme: { extend: { fontSize: { bubble: "clamp(0.875rem, 4.46cqi, 1.25rem)", // ✅ cqi 단위 사용 }, }, }, plugins: [require("@tailwindcss/container-queries")], // 또는 import 방식 } // ...
- 컴포넌트 적용 (
SpeechBubbleText.tsx
):SpeechBubbleText
컴포넌트는speechBubbleMap
이라는 객체로부터 상황에 맞는 스타일 정보(여기서는fontSizeClass: "text-bubble"
)를 받아와 동적으로.text-bubble
클래스를 적용했습니다.처음에는fontSizeClass
변수를 통해 동적으로 클래스를 적용했을 때 Tailwind가.text-bubble
을 인식하지 못하는 문제가 있었지만,tailwind.config.js
의safelist
에'text-bubble'
을 추가하여 이 문제를 해결했습니다. // SpeechBubbleText.tsx // ... const { text, /*...,*/ fontSizeClass } = speechBubbleMap[imageId](name); // fontSizeClass 값은 "text-bubble" // ... return ( <div className={`... ${fontSizeClass} ...`}> {/* .text-bubble 적용 */} {text} </div> );
이 과정을 통해, WebtoonContainer
의 너비가 변함에 따라 그 안의 SpeechBubbleText
의 글꼴 크기가 의도한 대로 부드럽게 조절되는 반응형 동작을 구현할 수 있었습니다!
컨테이너 쿼리의 장점
- 컴포넌트 독립성 및 재사용성 향상
- 더 세밀한 반응형 제어
- 직관적인 스타일링
주요 사용 사례
- 그리드 시스템 내의 카드 UI
- 다양한 너비의 컬럼에 배치될 수 있는 위젯
- 복잡한 UI에서 특정 패널 크기에 따른 요소 조정
- 우리가 구현한 반응형 타이포그래피
브라우저 지원 현황 (Can I use...)
보내주신 "Can I use" 스크린샷(CSS Container Query Units 기준)을 보면, 글로벌 브라우저 지원율이 92.55% (2025년 5월 현재 기준 매우 높은 수치)로 나타납니다. 이는 컨테이너 쿼리 유닛(cqi
, cqw
등)이 대부분의 최신 브라우저에서 잘 작동한다는 것을 의미합니다.
- 주요 데스크톱 브라우저: Chrome (105+), Edge (105+), Safari (16.0+), Firefox (최신 버전, 예: 110+), Opera (91+) 등에서 안정적으로 지원됩니다.
- 주요 모바일 브라우저: Safari on iOS (16.0+), Chrome for Android (최신 버전, 예: 105+), Samsung Internet (18.0+) 등 역시 지원 범위에 포함됩니다.
결론적으로, (2025년 5월 현재) CSS 컨테이너 쿼리 유닛은 IE와 같은 아주 오래된 브라우저를 제외하고는 대부분의 사용자가 경험할 수 있는, 실무에 충분히 적용 가능한 기능이라고 볼 수 있습니다. 물론, 극소수의 구형 브라우저 사용자를 고려해야 한다면 폴리필(polyfill)을 검토하거나 점진적 향상(progressive enhancement) 전략을 취할 수 있습니다. 하지만 대다수의 현대적인 웹 프로젝트에서는 자신 있게 사용해도 좋은 수준입니다.
마무리하며
CSS 컨테이너 쿼리는 웹 컴포넌트 디자인 방식에 큰 변화를 가져오는 중요한 기술입니다. 이제 대부분의 모던 브라우저에서 잘 지원하고 있으므로, 더욱 유연하고 지능적인 반응형 웹사이트 및 애플리케이션을 구축하는 데 적극적으로 활용해 보시길 바랍니다!
---
clamp 함수 : https://lim-2.tistory.com/139
'개발 > HTML, CSS' 카테고리의 다른 글
CSS clamp() 함수: 유연함과 제어를 동시에! (0) | 2025.05.24 |
---|---|
Tailwind css 동적 클래스 문제 해결 (0) | 2025.03.27 |
zoom & swipe image Viewer 접근성 구현하기: 실전 사례와 코드 예시 (0) | 2025.03.22 |
Tab 인덱스가 작동하지 않은 원인: css의 중요성 (0) | 2025.03.22 |
엘리먼트를 안 보이게 하는 다양한 방식과 각각의 장단점 (0) | 2025.03.22 |