블로그 목록
프론트엔드

CSS @scope로 스타일 격리하기 — 자식만 콕 집어 스타일 주기

컴포넌트 하나에 스타일을 주다가, 그 스타일이 안쪽 다른 컴포넌트까지 물들이는 경험은 다들 한 번쯤 해봤을 거다. 카드 컴포넌트에 .card a { color: red } 걸었더니, 그 카드 안에 박아둔 다른 컴포넌트의 링크까지 빨개지는 그런 상황.

지금까지는 이걸 막으려고 클래스 이름을 길게 짓거나(card__link), CSS Modules를 쓰거나, :not()으로 예외를 하나씩 발라냈다. @scope를 쓰면 이걸 CSS 문법 차원에서 깔끔하게 끊을 수 있다.

기본 사용법

@scope (시작점) 안에 규칙을 넣으면, 그 시작점 요소 하위에만 스타일이 적용된다.

@scope (.card) {
  a {
    color: crimson;
  }
}

.card 안의 a만 잡힌다. 여기까지는 .card a랑 똑같아 보이는데, 진짜 차이는 하한선(lower boundary)을 정할 수 있다는 점이다.

도넛 스코프 — 안쪽은 빼기

@scope (시작점) to (끝점) 형태로 쓰면, 시작점과 끝점 사이의 요소만 스타일이 걸린다. 끝점 안쪽은 스코프에서 빠진다. 도넛처럼 가운데가 뚫린다고 해서 도넛 스코프라고 부른다.

@scope (.card) to (.card-content) {
  a {
    color: crimson;
  }
}

이렇게 하면 .card 안의 링크는 빨개지지만, .card-content 아래에 있는 링크는 건드리지 않는다. 카드 헤더나 푸터의 링크만 스타일 주고, 본문에 들어오는 사용자 콘텐츠는 원본 그대로 두고 싶을 때 딱 맞는다.

:scope로 시작점 자신 가리키기

스코프의 루트 요소 자체를 스타일링하려면 :scope를 쓴다.

@scope (.card) {
  :scope {
    border: 1px solid #ddd;
    border-radius: 8px;
  }
}

HTML <style>에 바로 쓰기

시작점을 생략하고 <style> 태그를 컴포넌트 마크업 안에 그대로 넣으면, 그 <style>의 부모가 자동으로 스코프 루트가 된다.

<div class="widget">
  <style>
    @scope {
      p { margin: 0; }
    }
  </style>
  <p>이 문단만 스코프 안</p>
</div>

빌드 도구 없이 컴포넌트 단위로 스타일을 붙일 수 있어서, 서버 컴포넌트나 HTML 조각을 다룰 때 유용하다.

한 가지 주의할 점은 우선순위다. @scope 안의 규칙은 셀렉터 명시도(specificity)를 올리지 않는다. .card a와 스코프 안의 a는 명시도가 다르니, 기존 CSS와 섞어 쓸 때 어느 쪽이 이기는지 한 번은 확인하고 넘어가는 게 좋다.


Chrome, Safari에 이미 들어와 있고 Firefox도 준비 중이다. CSS Modules 같은 도구 없이 "여기부터 여기까지만"을 표현할 수 있다는 게 핵심이다. 특히 도넛 스코프는 지금까지 CSS만으로는 표현하기 까다로웠던 패턴이라, 사용자 콘텐츠가 섞이는 영역에서 진가를 발휘한다.