CSS를 작성하면서 가장 골치 아픈 문제 중 하나는 스타일 충돌이다. 클래스 이름이 겹치거나, 부모 컴포넌트의 스타일이 자식에게 의도치 않게 흘러내리는 경험은 누구나 한 번쯤 해봤을 것이다. 이 문제를 해결하기 위해 BEM, CSS Modules, CSS-in-JS 같은 방법론이 등장했다. 그런데 이제 CSS 자체에 네이티브 스코핑이 생겼다. @scope가 바로 그 주인공이다.
@scope 기본 문법
@scope는 특정 DOM 서브트리 안에서만 스타일을 적용하는 CSS at-rule이다. 마치 울타리를 쳐서 "이 안에서만 이 스타일이 유효하다"고 선언하는 것과 같다.
@scope 기본 사용법
@scope (.card) {
h2 {
font-size: 1.5rem;
color: #1a1a1a;
}
p {
line-height: 1.6;
color: #555;
}
}
위 코드에서 h2와 p 스타일은 .card 내부에서만 적용된다. 외부의 h2나 p에는 전혀 영향을 주지 않는다. @scope (selector)에서 소괄호 안의 셀렉터가 scope root가 된다.
하한 제한: to 키워드
@scope의 진정한 힘은 to 키워드로 하한(lower boundary)을 지정할 수 있다는 점이다.
@scope with lower boundary
@scope (.card) to (.card__content) {
/* .card 안이지만 .card__content 바깥의 요소에만 적용 */
color: #333;
font-weight: bold;
}
이 패턴은 scope root(.card)부터 하한(.card__content)까지의 영역에만 스타일을 적용한다. 하한 셀렉터가 매칭하는 요소와 그 하위 요소는 스타일 적용 대상에서 제외된다.
도넛 스코핑(Donut Scoping)
NOTE 잠깐! 이 용어는?
도넛 스코핑: 가운데에 구멍이 뚫린 도넛처럼 외곽 영역에만 스타일을 적용하고, 내부 영역은 건드리지 않는 패턴이다.
도넛 스코핑은 @scope ... to ...의 핵심 활용 사례다. 예를 들어, 테마 래퍼 안의 레이아웃 요소에만 스타일을 적용하고 내부 슬롯 콘텐츠는 건드리지 않을 때 유용하다.
도넛 스코핑 실전 예시
@scope (.theme-dark) to (.slot) {
/* .theme-dark 내부이지만 .slot 외부의 요소에만 적용 */
background: #1a1a2e;
color: #e0e0e0;
border-color: #333;
}
/* .slot 내부는 자체 테마나 기본 스타일을 유지 */
도넛 스코핑 HTML 구조
<div class="theme-dark">
<header>다크 테마 적용됨</header>
<nav>다크 테마 적용됨</nav>
<div class="slot">
<!-- 이 안은 다크 테마 미적용! -->
<p>기본 스타일 유지</p>
</div>
</div>
웹 컴포넌트의 슬롯이나, CMS에서 사용자가 삽입하는 콘텐츠 영역을 보호할 때 특히 효과적이다.
근접성(Proximity) 기반 우선순위
CSS 캐스케이드에서 @scope는 완전히 새로운 우선순위 규칙을 도입한다. 같은 specificity를 가진 두 규칙이 충돌하면, scope root가 더 가까운 쪽이 이긴다.
NOTE 잠깐! 이 용어는?
근접성 우선순위: DOM 트리에서 대상 요소와 scope root 사이의 거리가 가까울수록 우선순위가 높아지는 규칙이다. CSS 캐스케이드의 새로운 계층이다.
근접성 우선순위 예시
@scope (.outer) {
p { color: red; }
}
@scope (.inner) {
p { color: blue; }
}
근접성 우선순위 HTML
<div class="outer">
<div class="inner">
<p>이 텍스트는 blue — .inner가 더 가깝다</p>
</div>
<p>이 텍스트는 red — .outer 직계 자식이다</p>
</div>
이건 상당히 직관적이다. 마치 회사에서 직속 상사의 지시가 CEO의 일반 방침보다 우선하는 것과 비슷하다.
BEM과의 비교
BEM과 @scope를 주요 기준으로 비교하면 다음과 같다.
| 기준 | BEM | @scope |
|---|---|---|
| 네이밍 | .block__element--modifier 강제 |
자유로운 클래스명 사용 가능 |
| 셀렉터 복잡도 | 단일 클래스, 낮은 specificity | 일반 셀렉터 + scope 경계 |
| HTML 결합도 | 높음 — 클래스명이 구조를 반영 | 낮음 — DOM 구조로 스코핑 |
| 학습 비용 | 컨벤션 학습 필요 | CSS 문법 학습 필요 |
| 유지보수 | 리네이밍 시 HTML+CSS 동시 수정 | scope root만 수정하면 됨 |
| 도구 의존성 | 없음 (순수 컨벤션) | 브라우저 지원 필요 |
| 런타임 비용 | 없음 | 없음 (네이티브 CSS) |
| 하한 제한 | 불가능 | to 키워드로 가능 |
BEM은 "약속"이고, @scope는 "규칙"이다. BEM은 팀원이 컨벤션을 어기면 깨지지만, @scope는 브라우저가 강제하기 때문에 깨지지 않는다.
CSS-in-JS, Tailwind와의 비교
CSS-in-JS(styled-components, Emotion 등)는 고유한 클래스명을 자동 생성해서 스코핑 문제를 해결한다. 하지만 런타임 비용이 발생하고, JavaScript 번들 크기가 늘어난다. @scope는 런타임 비용이 제로다.
Tailwind CSS는 유틸리티 클래스 방식으로 충돌을 피한다. 그러나 복잡한 컴포넌트에서 스타일 경계를 명확히 나누기 어렵다. @scope는 DOM 구조를 기반으로 명확한 경계를 설정한다.
@scope vs CSS-in-JS 비교
/* @scope — 런타임 비용 없음, 네이티브 CSS */
@scope (.product-card) {
.title { font-size: 1.2rem; }
.price { color: green; font-weight: bold; }
}
/* CSS-in-JS — JS 런타임에서 클래스 생성 */
/* const Title = styled.h3`font-size: 1.2rem;` */
/* const Price = styled.span`color: green; font-weight: bold;` */
실용적 사례: 복잡한 레이아웃
대시보드처럼 여러 위젯이 중첩되는 레이아웃에서 @scope는 빛을 발한다.
대시보드 위젯 스코핑
@scope (.widget) to (.widget) {
/* 중첩 위젯의 스타일이 내부 위젯으로 새지 않는다 */
.header {
background: var(--widget-bg);
padding: 1rem;
border-bottom: 1px solid #eee;
}
.body {
padding: 1rem;
}
}
@scope (.widget) to (.widget)는 "이 위젯의 스타일은 내부에 중첩된 다른 위젯까지만 적용하고, 그 안쪽은 건드리지 마라"는 의미다. 재귀적 컴포넌트 구조에서 매우 유용하다.
브라우저 지원
2024년 12월 Firefox 146의 지원을 마지막으로 @scope는 Baseline 호환이 되었다. Chrome 118(2023.10), Edge 118(2023.10), Safari 17.4(2024.03)에서 이미 지원하고 있었으므로, 이제 주요 브라우저에서 안심하고 사용할 수 있다.
@supports를 이용한 폴백
@supports (selector(:scope)) {
@scope (.card) {
.title { color: navy; }
}
}
/* 폴백 */
.card .title {
color: navy;
}
마무리
@scope가 BEM을 완전히 대체하느냐고 묻는다면, 단독으로는 아직 아니다. BEM이 제공하는 명확한 네이밍 체계와 팀 내 의사소통 효과는 @scope가 대신할 수 없다. 하지만 스타일 격리라는 본질적 목적에서 @scope는 BEM보다 훨씬 강력하고 확실한 해법이다.
가장 현실적인 접근은 BEM의 네이밍 철학 + @scope의 격리 능력을 조합하는 것이다. 이름은 의미 있게 짓되, 스코핑은 브라우저에게 맡기는 방식이다. 네이티브 CSS가 점점 강력해지고 있다. @scope, @layer, :has(), @container — 도구에 의존하지 않고도 복잡한 스타일링 문제를 해결할 수 있는 시대가 오고 있다.
'노트 > 끄적끄적' 카테고리의 다른 글
| Redux를 서버에 올리면? 당근의 이벤트 소싱 라이브러리 Ventyd (0) | 2026.02.15 |
|---|---|
| 당근페이 백엔드 아키텍처 여정: Layered에서 Clean Architecture까지 (0) | 2026.02.15 |
| CSS contrast-color()가 없어도 괜찮다 — 3가지 대안으로 흉내내기 (0) | 2026.02.15 |
| Cloudflare Markdown for Agents: 웹이 에이전트를 위해 변하고 있다 (0) | 2026.02.15 |