Reference/At-rules/@scope

김동현·2026년 3월 28일

mdn 학습 번역 - CSS

목록 보기
171/190

@scope

Baseline 2025 | Newly available

Chrome, Edge, Firefox, Safari에서 지원돼요.

2025년 12월부터 이 기능은 최신 기기와 브라우저 버전에서 작동해요. 이 기능은 구형 기기나 브라우저에서는 작동하지 않을 수 있어요.

@scope CSS at-rule을 사용하면 특정 DOM 서브트리에서 요소를 선택할 수 있어요. 재정의하기 어려운 지나치게 구체적인 선택자를 작성하지 않고도 요소를 정확하게 타겟팅할 수 있고, 선택자를 DOM 구조에 너무 밀접하게 결합하지 않아도 돼요.

JavaScript에서 @scope는 CSS 객체 모델 인터페이스 CSSScopeRule을 통해 접근할 수 있어요.

구문 (Syntax)

@scope 앳 규칙(@-rule)은 하나 이상의 규칙 집합(이를 범위 지정 스타일 규칙이라고 불러요)을 포함하고, 선택된 요소들에 이 규칙들을 적용할 범위를 정의해줘요. @scope는 두 가지 방식으로 사용할 수 있어요.

  1. CSS 내부의 독립적인 블록으로 사용하는 경우예요. 이럴 때는 서두에 범위 루트(scope root)와 선택 사항인 범위 제한(scope limit) 선택자를 포함해요. 이 선택자들이 범위의 상한선과 하한선을 정의하게 되죠.

    @scope (scope root) to (scope limit) {
      /* … */
    }
  2. HTML 내부의 <style> 요소 안에 인라인 스타일로 포함하는 경우예요. 이때는 서두 부분을 생략하고, 안에 들어있는 규칙 집합은 자동으로 <style> 요소를 감싸고 있는 부모 요소로 범위가 지정돼요.

    <parent-element>
      <style>
        @scope {
          /* rulesets */
        }
      </style>
    </parent-element>

또한, 인라인 @scope를 범위 제한 선택자와 결합해서 @scope to (scope limit) { ... }와 같은 형식으로 사용하는 것도 가능하답니다.


설명 (Description)

복잡한 웹 문서는 헤더, 푸터, 뉴스 기사, 지도, 미디어 플레이어, 광고 등 다양한 컴포넌트를 포함할 수 있어요. 문서가 복잡해질수록 이런 컴포넌트들의 스타일을 효과적으로 관리하는 게 큰 고민거리가 되는데, 스타일의 범위를 잘 지정하면 이런 복잡함을 관리하는 데 큰 도움이 돼요. 아래와 같은 DOM 트리를 한번 생각해 볼까요?

body
└─ article.feature
    ├─ section.article-hero
    │  ├─ h2
    │  └─ img
    │
    ├─ section.article-body
    │  ├─ h3
    │  ├─ p
    │  ├─ img
    │  ├─ p
    │  └─ figure
    │      ├─ img
    │      └─ figcaption
    │
    └─ footer
        ├─ p
        └─ img

만약 article-body라는 클래스를 가진 <section> 안에 있는 <img> 요소를 선택하고 싶다면, 보통 이렇게 할 거예요.

  • .feature > .article-body > img 같은 선택자를 쓰는 거죠. 하지만 이건 명시도(specificity)가 높아서 나중에 덮어쓰기 어렵고, DOM 구조와 너무 강하게 결합되어 있어요. 나중에 마크업 구조가 바뀌면 CSS도 다시 써야 할 수도 있거든요.
  • 좀 덜 구체적으로 .article-body img라고 쓸 수도 있겠죠. 하지만 이건 section 안에 있는 모든 이미지를 다 선택해 버린다는 단점이 있어요.

이럴 때 @scope가 아주 유용해요! 선택자가 요소를 타겟팅할 수 있는 정확한 범위를 정의할 수 있게 해주거든요. 예를 들어, 위 문제를 아래와 같은 독립적인 @scope 블록으로 해결할 수 있어요.

@scope (.article-body) to (figure) {
  img {
    border: 5px solid black;
    background-color: goldenrod;
  }
}

여기서 .article-body 범위 루트 선택자는 규칙 집합이 적용될 DOM 트리 범위의 상한선을 정의하고, figure 범위 제한 선택자는 하한선을 정의해요. 그 결과, article-body 클래스를 가진 <section> 안에 있으면서 <figure> 요소 안에는 들어있지 않은 <img> 요소들만 선택되는 거죠.

참고: 상한선과 하한선이 있는 이런 방식의 범위 지정을 흔히 도넛 스코프(donut scope)라고 불러요.

범위의 상한선은 포함(inclusive)되고 하한선은 제외(exclusive)되는 게 기본이에요. 이 동작을 바꾸고 싶다면 범용 자식 선택자(*)와 결합하면 돼요. 예를 들어, @scope (scope root) to (scope limit > *)는 양쪽 경계를 모두 포함하고, @scope (scope root > *) to (scope limit)는 양쪽 경계를 모두 제외해요. 한편 @scope (scope root > *) to (scope limit > *)는 상한선은 제외하고 하한선은 포함하게 된답니다.

만약 article-body 클래스를 가진 <section> 안의 모든 이미지를 선택하고 싶다면, 범위 제한을 생략하면 돼요.

@scope (.article-body) {
  img {
    border: 5px solid black;
    background-color: goldenrod;
  }
}

아니면 article-body 클래스를 가진 <section> 내부에 인라인으로 @scope 블록을 넣을 수도 있어요.

<section class="article-body">
  <style>
    @scope {
      img {
        border: 5px solid black;
        background-color: goldenrod;
      }
    }
  </style>

  </section>

참고: 한 가지 꼭 알아두어야 할 점이 있어요. @scope가 특정 DOM 하위 트리에만 선택자가 적용되도록 격리해 주긴 하지만, 적용된 스타일 자체가 그 하위 트리 안에 완전히 갇히는 건 아니에요. 상속에서 이 점이 가장 두드러지는데, 자식에게 상속되는 속성들(예를 들어 colorfont-family)은 설정된 범위 제한을 넘어서도 여전히 상속된답니다.


@scope 블록 내부의 :scope 의사 클래스

@scope 블록 안에서 :scope 의사 클래스를 사용하면 범위 루트에 직접 스타일을 아주 편하게 적용할 수 있어요. 이렇게요.

@scope (.feature) {
  :scope {
    background: rebeccapurple;
    color: antiquewhite;
    font-family: sans-serif;
  }
}

@scope 블록 내에서의 :scope에 관해 고려할 점들은 다음과 같아요.

  • :scope는 클래스 수준의 명시도를 추가해요 (자세한 내용은 Specificity in @scope 섹션을 참고하세요).

  • 범위 제한에서도 :scope를 사용해서 범위 제한과 루트 사이의 특정한 관계를 지정할 수 있어요. 예를 들면 이렇죠.

    /* figure가 :scope의 직계 자식일 때만 제한으로 작동해요 */
    @scope (.article-body) to (:scope > figure) {
      /* … */
    }
  • 범위 제한에서 :scope를 사용해 범위 루트 외부의 요소를 참조할 수도 있어요. 예를 들어볼까요?

    /* :scope가 .feature 안에 있을 때만 figure가 제한으로 작동해요 */
    @scope (.article-body) to (.feature :scope figure) {
      /* … */
    }
  • 범위가 지정된 스타일 규칙은 해당 하위 트리를 벗어날 수 없어요. :scope + p 같은 선택은 하위 트리 외부를 선택하는 것이기 때문에 유효하지 않아요.

  • 범위 루트와 제한을 선택자 리스트로 정의하는 것도 당연히 가능해요. 이 경우 여러 개의 범위가 정의되죠. 아래 예시에서는 article-heroarticle-body 클래스를 가진 <section> 내부의 모든 <img>에 스타일이 적용되지만, <figure> 안에 중첩된 경우는 제외돼요.

    @scope (.article-hero, .article-body) to (figure) {
      img {
        border: 5px solid black;
        background-color: goldenrod;
      }
    }

@scope에서의 명시도 (Specificity)

@scope 규칙 내부에서, 일반 선택자와 & 중첩 선택자는 마치 선택자 앞에 :where(:scope)가 붙은 것처럼 작동해요.
:where()명시도(specificity)가 0이기 때문에, 일반 선택자와 &는 추가적인 가중치를 주지 않아요. 명시도 가중치는 선택자의 나머지 부분에 의해 결정되죠.
예를 들어, & img 선택자의 명시도는 :where(:scope) img의 명시도(0-0-1)와 같아요.

경고: @scope 블록 내부에서 &의 명시도가 처리되는 방식은 브라우저 엔진과 버전에 따라 다를 수 있어요. 자세한 내용은 브라우저 호환성(#browser_compatibility)을 확인해 보세요.

아래 코드 블록의 두 경우 모두, 명시도는 오직 img에서만 나와요.

@scope (.article-body) {
  /* img의 명시도는 예상대로 0-0-1이에요 */
  img {
    /* … */
  }

  /* & img 역시 명시도가 0-0-1이에요 */
  & img {
    /* … */
  }
}

반면, :scope를 명시적으로 사용하면 범위 루트를 선택하게 되고 클래스 수준의 명시도(0-1-0)가 추가돼요. :scope의사 클래스(pseudo-class)이기 때문이죠.
아래 코드 블록에서 :scope img의 명시도는 0-1-1이 된답니다.

@scope (.article-body) {
  /* :scope img의 명시도는 0-1-0 + 0-0-1 = 0-1-1 이에요 */
  :scope img {
    /* … */
  }
}

@scope 충돌이 해결되는 방법

@scopeCSS 캐스케이드(cascade)에 새로운 기준인 스코핑 근접성(scoping proximity)을 추가해요. 이건 두 범위의 스타일이 충돌할 때, DOM 트리 계층 구조상 범위 루트까지의 단계(hop) 수가 더 적은 스타일이 적용된다는 원칙이에요. 이게 무슨 뜻인지 예시를 통해 살펴볼게요.

서로 다른 테마를 가진 카드들이 서로 중첩되어 있는 아래 HTML 조각을 보세요.

<div class="light-theme">
  <p>Light theme text</p>
  <div class="dark-theme">
    <p>Dark theme text</p>
    <div class="light-theme">
      <p>Light theme text</p>
    </div>
  </div>
</div>

만약 테마 CSS를 아래와 같이 작성했다면 문제가 생길 거예요.

.light-theme {
  background: #cccccc;
}

.dark-theme {
  background: #333333;
}

.light-theme p {
  color: black;
}

.dark-theme p {
  color: white;
}

가장 안쪽에 있는 문단은 밝은 테마 카드 안에 있으니까 검은색이어야 하죠. 하지만 이 문단은 .light-theme p.dark-theme p 둘 다의 타겟이 돼요. .dark-theme p 규칙이 소스 순서상 나중에 나오기 때문에 이게 적용되어 버리고, 결국 문단은 엉뚱하게 흰색으로 나오게 돼요.

이걸 고치려면 @scope를 다음과 같이 사용할 수 있어요.

@scope (.light-theme) {
  :scope {
    background: #cccccc;
  }
  p {
    color: black;
  }
}

@scope (.dark-theme) {
  :scope {
    background: #333333;
  }
  p {
    color: white;
  }
}

이제 가장 안쪽 문단이 올바르게 검은색으로 표시돼요! 왜냐하면 이 문단은 .light-theme 범위 루트로부터는 DOM 트리 계층상 단 한 단계 떨어져 있지만, .dark-theme 범위 루트로부터는 두 단계 떨어져 있거든요. 따라서 "더 가까운" 밝은 테마 스타일이 승리하게 되는 거죠.

참고: 스코핑 근접성은 소스 순서보다 우선하지만, 중요도(!important), 레이어(layers), 명시도(specificity)와 같은 다른 더 높은 우선순위 기준들보다는 하위 단계에 있답니다.


형식적인 구문 (Formal syntax)

@scope = 
  @scope <scope-boundaries> { <block-contents> }  

<scope-boundaries> = 
  [ ( <scope-start> ) ]? [ to ( <scope-end> ) ]?  

<scope-start> = 
  <selector-list>  

<scope-end> = 
  <selector-list>  

<selector-list> = 
  <complex-selector-list>  

<complex-selector-list> = 
  <complex-selector>#  

<complex-selector> = 
  <complex-selector-unit> [ <combinator>? <complex-selector-unit> ]* <complex-selector-unit> = 
  [ <compound-selector>? <pseudo-compound-selector>* ]!  

<combinator> = 
  '>'          |
  '+'          |
  '~'          |
  [ '|' '|' ]  

<compound-selector> = 
  [ <type-selector>? <subclass-selector>* ]!  

<pseudo-compound-selector> = 
  <pseudo-element-selector> <pseudo-class-selector>* <type-selector> = 
  <wq-name>          |
  <ns-prefix>? '*'  

<subclass-selector> = 
  <id-selector>             |
  <class-selector>         |
  <attribute-selector>     |
  <pseudo-class-selector>  

<pseudo-element-selector> = 
  : <pseudo-class-selector>         |
  <legacy-pseudo-element-selector>  

<pseudo-class-selector> = 
  : <ident-token>                   |
  : <function-token> <any-value> )  

<wq-name> = 
  <ns-prefix>? <ident-token>  

<ns-prefix> = 
  [ <ident-token> | '*' ]? '|'  

<id-selector> = 
  <hash-token>  

<class-selector> = 
  '.' <ident-token>  

<attribute-selector> = 
  '[' <wq-name> ']'                                    |
  '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']'  

<legacy-pseudo-element-selector> = 
  : [ before | after | first-line | first-letter ]  

<attr-matcher> = 
  [ '~' | '|' | '^' | '$' | '*' ]? '='  

<attr-modifier> = 
  i  |
  s  

이 구문은 CSS Cascading and Inheritance Level 6, Selectors Level 4에 따른 최신 표준을 반영하고 있어요. 모든 브라우저가 모든 부분을 구현한 것은 아닐 수 있으니, 지원 정보는 브라우저 호환성(#browser_compatibility) 섹션을 확인해 주세요.

예제 (Examples)

범위 루트 내부의 기본적인 스타일

이 예제에서는 두 개의 별도 @scope 블록을 사용해서 각각 .light-scheme.dark-scheme 클래스를 가진 요소 내부의 링크를 매칭해 볼 거예요. 여기서 :scope가 어떻게 범위 루트 자체를 선택하고 스타일을 입히는지 눈여겨보세요. 이 예제에서 범위 루트는 해당 클래스들이 적용된 <div> 요소들이에요.

HTML

<div class="light-scheme">
  <p>
    MDN은 
    <a href="[https://developer.mozilla.org/en-US/docs/Web/HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)">HTML</a>,
    <a href="[https://developer.mozilla.org/en-US/docs/Web/CSS](https://developer.mozilla.org/en-US/docs/Web/CSS)">CSS</a>, 그리고
    <a href="[https://developer.mozilla.org/en-US/docs/Web/JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)">JavaScript</a>에 대한 수많은 정보를 담고 있습니다.
  </p>
</div>

<div class="dark-scheme">
  <p>
    MDN은 
    <a href="[https://developer.mozilla.org/en-US/docs/Web/HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)">HTML</a>,
    <a href="[https://developer.mozilla.org/en-US/docs/Web/CSS](https://developer.mozilla.org/en-US/docs/Web/CSS)">CSS</a>, 그리고
    <a href="[https://developer.mozilla.org/en-US/docs/Web/JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)">JavaScript</a>에 대한 수많은 정보를 담고 있습니다.
  </p>
</div>

CSS

div {
  padding: 10px;
}

@scope (.light-scheme) {
  :scope {
    background-color: plum;
  }

  a {
    color: darkmagenta;
  }
}

@scope (.dark-scheme) {
  :scope {
    background-color: darkmagenta;
    color: antiquewhite;
  }

  a {
    color: plum;
  }
}

결과 (Result)

위의 코드는 다음과 같이 렌더링됩니다.

MDN Playground에서 예제 실행하기


범위 루트와 범위 제한 (Scope roots and scope limits)

이번 예제에서는 아까 설명 섹션에서 이야기했던 DOM 구조와 일치하는 HTML 조각을 사용해 볼 거예요. 일반적인 기사 요약 형태죠. 여기서 눈여겨볼 핵심은 구조의 여러 단계에 걸쳐 중첩되어 있는 <img> 요소들이에요.

이 예제의 목적은 범위 루트와 제한을 사용해서, 계층 구조의 꼭대기부터 시작하되 <figure> 요소 내부의 이미지는 포함하지 않는(그 직전까지만 적용되는) 스타일을 입히는 거예요. 결과적으로 도넛 스코프를 만드는 거죠.

HTML

<article class="feature">
  <section class="article-hero">
    <h2>Article heading</h2>
    <img alt="image" src="" />
  </section>

  <section class="article-body">
    <h3>Article subheading</h3>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod
      consectetur leo, nec eleifend quam volutpat vitae. Duis quis felis at
      augue imperdiet aliquam. Morbi at felis et massa mattis lacinia. Cras
      pharetra velit nisi, ac efficitur magna luctus nec.
    </p>

    <img alt="image" src="" />

    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

    <figure>
      <img alt="image" src="" />
      <figcaption>My infographic</figcaption>
    </figure>
  </section>

  <footer>
    <p>Written by Chris Mills.</p>
    <img alt="image" src="" />
  </footer>
</article>

CSS

* {
  box-sizing: border-box;
}

article {
  margin-bottom: 20px;
  padding: 10px;
  border: 2px solid gray;
}

.article-hero,
.article-body,
article footer {
  padding: 20px;
  margin: 10px;
  border: 2px solid lightgray;
}

img {
  height: 100px;
  width: 100%;
  display: block;
  background-color: lightgray;
  color: black;
  padding: 10px;
}

/* 범위가 지정된 CSS */

@scope (.feature) {
  :scope {
    background: rebeccapurple;
    color: antiquewhite;
    font-family: sans-serif;
  }

  figure {
    background-color: white;
    border: 2px solid black;
    color: black;
    padding: 10px;
  }
}

/* 도넛 스코프 */

@scope (.feature) to (figure) {
  img {
    border: 5px solid black;
    background-color: goldenrod;
  }
}

결과 (Result)

렌더링된 코드를 보면, <figure> 요소 안에 있는 이미지("My infographic"이라고 이름 붙은 것)를 제외한 모든 <img> 요소에 두꺼운 테두리와 황금색 배경색 스타일이 입혀진 것을 확인할 수 있어요.


명세 (Specifications)

명세 (Specification)
CSS Cascading and Inheritance Level 6 # scoped-styles

브라우저 호환성 (Browser compatibility)

css.at-rules.scope

기능ChromeEdgeFirefoxOperaSafariChrome AndroidFirefox AndroidOpera AndroidSafari iOSSamsung InternetWebView AndroidWebView iOS
@scope11811814610417.41181467917.42511817.4

css.selectors.nesting.at-scope

기능ChromeEdgeFirefoxOperaSafariChrome AndroidFirefox AndroidOpera AndroidSafari iOSSamsung InternetWebView AndroidWebView iOS
@scope 블록에서 &:where(:scope)로 처리됨143143142지원 안 함26.21431429426.2지원 안 함14326.2

함께 보기 (See also)

profile
프론트에_가까운_풀스택_개발자

0개의 댓글