블로그 목록
frontend

CSS 스타일 쿼리로 커스텀 프로퍼티에 반응하기 — @container style()로 변형(variant) 만들기

카드 컴포넌트에 variant="danger" 같은 prop을 하나 추가할 때마다 마음이 무거웠다. 부모에서 상태를 내려주고, 자식은 그 값을 받아 클래스를 조합하고, 배경·테두리·아이콘 색을 각각 갈아끼운다. 상태 하나 늘 때마다 이 연결고리 전체를 손봐야 한다.

컨테이너 크기에 반응하는 사이즈 컨테이너 쿼리는 이제 익숙한데, 사실 컨테이너 쿼리에는 스타일 쿼리라는 다른 축이 하나 더 있다. 컨테이너의 커스텀 프로퍼티 값을 보고 자식 스타일을 분기하는 방식이다.

문제: 상태를 색으로 옮기는 배선

핵심은 이거다. "상태"를 CSS 변수 하나에 담아 컨테이너에 심어두면, 자식들은 그 변수를 읽어서 알아서 반응한다. JS로 클래스를 조합할 필요가 없다.

<article class="card" style="--tone: danger">
  <h3 class="card-title">결제 실패</h3>
  <p>카드 정보를 다시 확인해 주세요.</p>
</article>
.card {
  container-name: card;
  border: 1px solid #ddd;
  border-radius: 12px;
  padding: 16px;
}

/* --tone 값이 danger일 때만 적용 */
@container card style(--tone: danger) {
  .card {
    border-color: #f0b4b4;
    background: #fdf2f2;
  }
  .card-title {
    color: #c0392b;
  }
}

@container card style(--tone: success) {
  .card {
    border-color: #b4e0b8;
    background: #f2fbf3;
  }
  .card-title {
    color: #1e7e34;
  }
}

style(--tone: danger)는 "이 컨테이너에서 --tone이 danger로 계산되면"이라는 뜻이다. 값만 바꿔주면 배경·테두리·제목 색이 한 번에 따라온다. variant를 늘리고 싶으면 @container 블록 하나만 추가하면 끝이다.

왜 편한가

색을 직접 내려줄 수도 있지 않냐고 할 수 있는데, 결이 다르다. 스타일 쿼리는 의미(danger) 만 전달하고, 그 의미를 어떤 스타일로 풀지는 CSS가 정한다. 컴포넌트를 쓰는 쪽은 --tone만 신경 쓰면 되고, 디자인 변경은 CSS 한 곳에서 끝난다. 조건이 자식 트리 전체에 걸리니까, 아이콘·버튼·테두리가 각각 prop을 받을 필요도 없다.

주의할 점 하나. 지금 안정적으로 쓸 수 있는 건 커스텀 프로퍼티 기반의 style() 쿼리다. style(display: flex)처럼 일반 속성을 조건으로 거는 문법은 아직 표준·구현이 무르익는 중이라, 변수에 상태를 담는 패턴으로 쓰는 게 안전하다.

정리

variant를 prop 배선으로 풀던 걸 커스텀 프로퍼티 하나로 옮기고, 스타일 분기는 @container style()에 맡긴다. 상태를 색으로 번역하는 지겨운 배선이 CSS 안으로 들어오면서, 컴포넌트 API가 눈에 띄게 홀쭉해진다. Chrome·Safari에서 이미 동작하고, 미지원 브라우저에서는 기본 스타일로 떨어지니 점진적 향상으로 붙이기도 좋다.