1. 소개
이 섹션은 규범적이지 않습니다.
이 모듈은 스타일 규칙을 다른 스타일 규칙 안에 중첩하는 것을 지원하는 내용을 설명하며, 내부 규칙의 선택자가 외부 규칙에 의해 매치된 요소들을 참조할 수 있게 합니다. 이 기능은 관련된 스타일들을 CSS 문서 내의 단일 구조로 집계할 수 있게 하여, 가독성과 유지보수성을 향상시킵니다.
테스트
중첩에 대한 일반 테스트
- block-skipping.html (라이브 테스트) (소스)
- delete-other-rule-crash.html (라이브 테스트) (소스)
- double-parent-pseudo-in-placeholder-crash.html (라이브 테스트) (소스)
- has-nesting.html (라이브 테스트) (소스)
- invalidation-001.html (라이브 테스트) (소스)
- invalidation-002.html (라이브 테스트) (소스)
- invalidation-003.html (라이브 테스트) (소스)
- invalidation-004.html (라이브 테스트) (소스)
- nested-error-recovery.html (라이브 테스트) (소스)
- nesting-basic.html (라이브 테스트) (소스)
- nesting-revert-rule.tentative.html (라이브 테스트) (소스)
- parent-pseudo-in-placeholder-crash.html (라이브 테스트) (소스)
- pseudo-part-crash.html (라이브 테스트) (소스)
- pseudo-where-crash.html (라이브 테스트) (소스)
- supports-is-consistent.html (라이브 테스트) (소스)
1.1. 모듈 상호작용
이 모듈은 [CSS21] 파서 모델을 확장하는 새로운 파서 규칙을 도입합니다. 또한 [SELECTORS-4] 모듈을 확장하는 선택자들을 도입합니다. 일부 IDL 및 알고리듬은 [CSSOM-1] 모듈에서 정의된 것들을 확장하거나 수정합니다.
1.2. 값
이 명세는 새로운 속성이나 값을 정의하지 않습니다.
2. 해설
이 섹션은 규범적이지 않습니다.
좀 더 간결하게 작성하고 싶은 CSS가 있다고 상상해 보세요.
.foo{ color : green; } .foo .bar{ font-size : 1.4 rem ; }
중첩(Nesting)을 사용하면, 다음과 같이 작성할 수 있습니다:
.foo{ color : green; .bar{ font-size : 1.4 rem ; } }
Sass나 다른 CSS 전처리기에서 스타일을 중첩해 본 적이 있다. 매우 익숙하게 느껴질 것입니다.
부모 스타일 규칙 안에 어떤 규칙이든 중첩할 수 있습니다:
main{ div{ ...} .bar{ ...} #baz{ ...} :has ( p) { ...} ::backdrop{ ...} [ lang|="zh" ] { ...} *{ ...} }
기본적으로 자식 규칙의 선택자는 부모 규칙과 후손 결합자(descendant combinator)로 연결되는 것으로 가정되지만, 중첩된 선택자를 임의의 결합자(combinator)로 시작하여 이를 변경할 수 있습니다:
main{ + article{ ...} > p{ ...} ~ main{ ...} }
새로운 & 선택자는 부모 선택자에 의해 매치된 요소들을 명시적으로 참조할 수 있게 해주므로, 이전 예제들은 다음과 같이 작성될 수 있습니다:
main{ & + article{ ...} & > p{ ...} & ~ main{ ...} }
그러나 중첩된 선택자 내에서 &를 다른 위치에 배치하여 부모와 자식 규칙 간의 다른 유형의 관계를 나타낼 수 있습니다. 예를 들어, 이 CSS:
ul{ padding-left : 1 em ; } .component ul{ padding-left : 0 ; }
중첩을 사용하면 다음과 같이 다시 쓸 수 있습니다:
ul{ padding-left : 1 em ; .component &{ padding-left : 0 ; } }
다시 말해, & 는 “중첩 선택자를 여기에 넣고 싶다”는 것을 표시하는 방법을 제공합니다.
선택자 사이에 공백을 원하지 않을 때도 유용합니다. 예를 들어:
a{ color : blue; &:hover{ color : lightblue; } }
이 코드는 와 동일한 결과를 냅니다.
&가 없다면
가 되어—a와 :hover 사이의 공백에 유의하세요—호버
링크에 스타일이 적용되지 않습니다.
여러 수준으로 중첩할 수 있습니다—이미 중첩된 CSS 안에 또 중첩된 CSS를 넣는 식으로—원하는 만큼 계층을 쌓을 수 있습니다. 중첩은 Container Queries, Supports Queries, Media Queries 및/또는 Cascade Layers와 자유롭게 혼합할 수 있습니다. (거의) 모든 것이 모든 것 안에 들어갈 수 있습니다.
3. 스타일 규칙 중첩
스타일 규칙은 다른 스타일 규칙 안에 중첩될 수 있습니다. 이러한 중첩 스타일 규칙은 일반 스타일 규칙과 정확히 동일하게 동작하여 선택자를 통해 요소에 속성을 연관시키지만, 부모 규칙의 선택자 컨텍스트를 "상속"하여, 부모의 선택자를 반복하지 않고도, 여러 번에 걸쳐 부모의 선택자를 확장할 수 있습니다.
중첩 스타일 규칙은 일반 스타일 규칙과 정확히 같지만, 상대 선택자(relative selectors)를 사용할 수 있다는 점이 다릅니다. 상대 선택자는 암묵적으로 부모 규칙에 의해 매치된 요소들을 기준으로 합니다.
테스트
.foo{ color : red; a{ color : blue; } }
유효하며, 다음과 동일합니다:
.foo{ color : red; } .foo a{ color : blue; }
중첩 규칙은 또한 nesting selector를 사용하여 부모 규칙이 매치한 요소를 직접 참조할 수도 있고, 상대 선택자 문법을 사용하여 "descendant" 이외의 관계를 지정할 수도 있습니다.
.foo{ color : red; &:hover{ color : blue; } } /* equivalent to: */ .foo{ color : red; } .foo:hover{ color : blue; }
.foo{ color : red; + .bar{ color : blue; } } /* equivalent to: */ .foo{ color : red; } .foo + .bar{ color : blue; }
3.1. 구문
이제 스타일 규칙의 내용은 기존의 선언들 외에도 중첩 스타일 규칙 및 at-rule을 허용합니다.
중첩 스타일 규칙 은 비중첩 규칙과 다음과 같은 점에서 다릅니다:
-
하나의 중첩 스타일 규칙은 서두(prelude)로 <relative-selector-list>를 허용합니다 (단순히 <selector-list>가 아니라). 모든 상대 선택자는 nesting selector가 나타내는 요소들을 기준으로 합니다.
-
만약 <relative-selector-list>의 선택자가 결합자(combinator)로 시작하지 않지만 nesting selector를 포함한다면, 그것은 비-상대 선택자로 해석됩니다.
중첩 스타일 규칙이 어떻게 파싱되는지에 대한 정확한 세부 사항은 [CSS-SYNTAX-3]에 정의되어 있습니다.
잘못된 중첩 스타일 규칙은 그 내용과 함께 무시되며, 부모 규칙을 무효화하지는 않습니다.
상대 선택자를 포함한 중첩 규칙은 암묵적인 nesting selector의 특이성(specificity)을 포함합니다. 예를 들어, .foo { > .bar {...}}와 .foo { & > .bar {...}}는 내부 규칙에 대해 동일한 특이성을 가집니다.
예를 들어, 한 컴포넌트가 클래스 .foo를 사용하고, 중첩된 컴포넌트가 .fooBar를 사용한다면, Sass에서는 다음과 같이 작성할 수 있습니다:
.foo{ color : blue; &Bar{ color : red; } } /* In Sass, this is equivalent to .foo { color: blue; } .fooBar { color: red; } */
이는 CSS에서는 허용되지 않으며, 중첩은 문법 변환이 아니라 부모 선택자가 실제로 매치하는 요소들에 대해 매칭하는 것이기 때문입니다.
또한 선택자 &Bar는 애초에 CSS에서 유효하지 않습니다, 왜냐하면 Bar 부분은 타입 선택자(type selector)이기 때문에 복합 선택자에서 반드시 맨 앞에 와야 하기 때문입니다. (즉, Bar&로 작성되어야 합니다.) 다행히도 CSS 중첩과 전처리기 문법 간에는 충돌이 없습니다.
참고: 이 표현은 :is(:unknown(&), .bar)와 같은 경우를 포착하기 위해 명시적으로 서술되어 있습니다, 여기서 알려지지 않은 선택자(unknown selector)는(알려지지 않았기 때문에 그 인자가 선택자로 파싱되는 것이 의도되었는지 여부를 알 수 없습니다) 선택자 내부에서 유일하게 &를 포함하는 경우가 있을 수 있습니다. 그런 선택자는 최신 브라우저에서만 지원되는 완전히 유효한 선택자일 수 있고, 우리는 파싱이 관련 없는 버전 문제에 의존하기를 원치 않으므로, 그것을 여전히 nesting selector를 포함하는 것으로 취급합니다.
만약 <forgiving-selector-list>의 항목이 nesting selector를 포함하지만 유효하지 않다면, 그 항목은 버려지지 않고 정확히 그대로 보존됩니다. (이것은 선택자의 매칭 동작을 변경하지는 않습니다—유효하지 않은 선택자는 여전히 아무것도 매치하지 않습니다—단지 선택자의 직렬화에만 영향을 줍니다.)
앞의 단락은 & 자체를 Selectors로 이동시킬 때 Selectors로 옮겨져야 합니다; 여기서는 편의상 임시로 수정해 두었습니다.
3.2. 예제
/* & can be used on its own */ .foo{ color : blue; & > .bar{ color : red; } > .baz{ color : green; } } /* equivalent to .foo { color: blue; } .foo > .bar { color: red; } .foo > .baz { color: green; } */ /* or in a compound selector, refining the parent’s selector */ .foo{ color : blue; &.bar{ color : red; } } /* equivalent to .foo { color: blue; } .foo.bar { color: red; } */ /* multiple selectors in the list are all relative to the parent */ .foo, .bar{ color : blue; + .baz, &.qux{ color : red; } } /* equivalent to .foo, .bar { color: blue; } :is(.foo, .bar) + .baz, :is(.foo, .bar).qux { color: red; } */ /* & can be used multiple times in a single selector */ .foo{ color : blue; & .bar & .baz & .qux{ color : red; } } /* equivalent to .foo { color: blue; } .foo .bar .foo .baz .foo .qux { color: red; } */ /* & doesn’t have to be at the beginning of the selector */ .foo{ color : red; .parent &{ color : blue; } } /* equivalent to .foo { color: red; } .parent .foo { color: blue; } */ .foo{ color : red; :not ( &) { color : blue; } } /* equivalent to .foo { color: red; } :not(.foo) { color: blue; } */ /* But if you use a relative selector , an initial & is implied automatically */ .foo{ color : red; + .bar + &{ color : blue; } } /* equivalent to .foo { color: red; } .foo + .bar + .foo { color: blue; } */ /* Somewhat silly, but & can be used all on its own, as well. */ .foo{ color : blue; &{ padding : 2 ch ; } } /* equivalent to .foo { color: blue; } .foo { padding: 2ch; } // or .foo { color: blue; padding: 2ch; } */ /* Again, silly, but can even be doubled up. */ .foo{ color : blue; &&{ padding : 2 ch ; } } /* equivalent to .foo { color: blue; } .foo.foo { padding: 2ch; } */ /* The parent selector can be arbitrarily complicated */ .error, #404{ &:hover > .baz{ color : red; } } /* equivalent to :is(.error, #404):hover > .baz { color: red; } */ .ancestor .el{ .other-ancestor &{ color : red; } } /* equivalent to .other-ancestor :is(.ancestor .el) { color: red; } /* As can the nested selector */ .foo{ &:is ( .bar, &.baz) { color : red; } } /* equivalent to .foo :is(.bar, .foo.baz) { color: red; } */ /* Multiple levels of nesting "stack up" the selectors */ figure{ margin : 0 ; > figcaption{ background : hsl ( 0 0 % 0 % /50 % ); > p{ font-size : .9 rem ; } } } /* equivalent to figure { margin: 0; } figure > figcaption { background: hsl(0 0% 0% / 50%); } figure > figcaption > p { font-size: .9rem; } */ /* Example usage with Cascade Layers */ @layer base{ html{ block-size : 100 % ; body{ min-block-size : 100 % ; } } } /* equivalent to @layer base { html { block-size: 100%; } html body { min-block-size: 100%; } } */ /* Example nesting Cascade Layers */ @layer base{ html{ block-size : 100 % ; @layer support{ body{ min-block-size : 100 % ; } } } } /* equivalent to @layer base { html { block-size: 100%; } } @layer base.support { html body { min-block-size: 100%; } } */ /* Example usage with Scoping */ @scope ( .card) to( > header) { :scope{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; > header{ border-block-end : 1 px solid white; } } } /* equivalent to @scope (.card) to (> header) { :scope { inline-size: 40ch; aspect-ratio: 3/4; } :scope > header { border-block-end: 1px solid white; } } */ /* Example nesting Scoping */ .card{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; @scope ( &) to( > header > *) { :scope > header{ border-block-end : 1 px solid white; } } } /* equivalent to .card { inline-size: 40ch; aspect-ratio: 3/4; } @scope (.card) to (> header > *) { :scope > header { border-block-end: 1px solid white; } } */
3.3. 다른 At-규칙의 중첩
이 명세는 중첩 스타일 규칙 외에도, 스타일 규칙 안에 중첩 그룹 규칙을 허용합니다: 본문에 스타일 규칙들을 포함하는 모든 at-규칙은, 달리 명시되지 않는 한, 스타일 규칙 안에 중첩될 수 있습니다.
이와 같이 중첩될 때, 중첩 그룹 규칙 블록의 내용은 <block-contents>로 파싱되며, <rule-list>로 파싱되지 않습니다:
-
스타일 규칙들은 중첩 스타일 규칙이며, 그들의 nesting selector는 가장 가까운 조상 스타일 규칙으로부터 정의를 가져옵니다.
-
속성들은 직접 사용할 수 있으며, 이는 마치 중첩 선언 규칙 안에 중첩된 것처럼 동작합니다.
테스트
이러한 중첩 그룹 규칙의 의미와 동작은, 달리 명시되지 않는 한, 다른 점이 없습니다.
/* Properties can be directly used */ .foo{ display : grid; @media ( orientation: landscape) { grid-auto-flow : column; } } /* equivalent to: */ .foo{ display : grid; } @media ( orientation: landscape) { .foo{ grid-auto-flow : column} } /* and also equivalent to the unnested: */ .foo{ display : grid; } @media ( orientation: landscape) { .foo{ grid-auto-flow : column; } } /* Conditionals can be further nested */ .foo{ display : grid; @media ( orientation: landscape) { grid-auto-flow : column; @media ( min-width >1024 px ) { max-inline-size : 1024 px ; } } } /* equivalent to */ .foo{ display : grid; } @media ( orientation: landscape) { .foo{ grid-auto-flow : column; } } @media ( orientation: landscape) and( min-width >1024 px ) { .foo{ max-inline-size : 1024 px ; } } /* Example nesting Cascade Layers */ html{ @layer base{ block-size : 100 % ; @layer support{ & body{ min-block-size : 100 % ; } } } } /* equivalent to */ @layer base{ html{ block-size : 100 % ; } } @layer base.support{ html body{ min-block-size : 100 % ; } } /* Example nesting Scoping */ .card{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; @scope ( &) { :scope{ border : 1 px solid white; } } } /* equivalent to */ .card{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; } @scope ( .card) { :scope{ border-block-end : 1 px solid white; } }
연속적으로 직접 중첩된 속성들의 연속은 자동으로 중첩 선언 규칙으로 래핑됩니다. (이것은 CSSOM에서 관찰할 수 있습니다.)
3.3.1. 중첩된 @scope 규칙
@scope 규칙이 중첩 그룹 규칙일 때, <scope-start> 선택자 안의 &는 가장 가까운 조상 스타일 규칙에 의해 매치된 요소들을 가리킵니다.
.parent{ color : blue; @scope ( & > .scope) to( & .limit) { & .content{ color : red; } } }
다음과 동등합니다:
.parent{ color : blue; } @scope ( .parent > .scope) to( :where ( :scope) .limit) { :where ( :scope) .content{ color : red; } }
3.4. 중첩 규칙과 선언의 혼합
하나의 스타일 규칙이 선언들 및 중첩 스타일 규칙 또는 중첩 그룹 규칙을 동시에 포함할 때, 이 세 가지는 임의로 혼합될 수 있습니다. 규칙들 이후나 그 사이에 오는 선언들은 암묵적으로 중첩 선언 규칙으로 래핑되어, 다른 규칙들에 대한 순서를 보존합니다.
article{ color : green; &{ color : blue; } color: red; } /* equivalent to */ article{ color : green; } :is ( article) { color : blue; } article{ color : red; } /* NOT equivalent to */ article{ color : green; } article{ color : red; } :is ( article) { color : blue; }
출현 순서 결정을 위해, 중첩 스타일 규칙과 중첩 그룹 규칙은 부모 규칙보다 나중에 오는 것으로 간주됩니다.
article{ color : blue; &{ color : red; } }
두 선언은 동일한 특이성(0,0,1)을 가지지만, 중첩 규칙은 부모 규칙보다 나중에 오는 것으로 간주되므로, color: red 선언이 캐스케이드에서 이깁니다.
반면, 다음 예에서는:
article{ color : blue; :where ( &) { color : red; } }
:where() 의사클래스는 nesting selector의 특이성을 0으로 낮추므로, color: red 선언은 이제 특이성이 (0,0,0)이 되어, "출현 순서"가 고려되기 전에 color: blue 선언에 패배합니다.
참고: 선언과 중첩 규칙을 자유롭게 섞을 수는 있지만, 이후의 속성들이 소스 텍스트에 나타나지 않는 중첩 선언 규칙으로 자동 래핑되기 때문에, 읽기 어렵고 다소 혼란스러울 수 있습니다. 가독성을 위해, 작성자들은 중첩 규칙보다 먼저 스타일 규칙 내의 모든 속성을 먼저 배치하는 것이 권장됩니다. (이것은 또한 구형 사용자 에이전트에서 다소 더 잘 동작하는 경향이 있습니다: 파싱과 오류 복구 방식의 특성상, 중첩 규칙 후에 나타나는 속성들이 건너뛰어질 수 있습니다.)
4. 중첩 선택자: & 선택자
중첩 스타일 규칙을 사용할 때, 작성자는 부모 규칙에 의해 매치된 요소들을 참조할 수 있어야 합니다; 결국 이것이 중첩의 전부입니다. 이를 위해 이 명세는 새로운 선택자인 중첩 선택자를 정의하며, & (U+0026 AMPERSAND)로 표기합니다.
중첩 스타일 규칙의 선택자에서 사용될 때, 중첩 선택자는 부모 규칙에 의해 매치된 요소들을 나타냅니다. 다른 어떤 문맥에서 사용될 때에는, 해당 문맥에서의 :scope과 동일한 요소들을 나타냅니다 (달리 정의되지 않는 한).
a, b{ & c{
는 다음과 동일합니다
:is ( a, b) c{ color : blue; }
중첩 선택자는 의사요소(pseudo-elements)를 나타낼 수 없습니다 (:is() 의사클래스의 동작과 동일합니다).
.foo, .foo::before, .foo::after{ color : red; }
&는 .foo에 의해 매치된 요소들만 나타냅니다; 다시 말해, 이는 다음과 동등합니다:
.foo, .foo::before, .foo::after{ color : red; } .foo:hover{ color : blue; }
이 제한을 완화하고 싶지만, :is()와 &가 같은 기반 메커니즘을 사용하도록 동시에 처리해야 합니다. (이슈: Issue 7433)
특이성(specificity)은 부모 스타일 규칙의 선택자 목록에 있는 복합 선택자들 중 가장 큰 특이성과 동일합니다 (:is()의 동작과 동일), 또는 그런 선택자 목록이 없으면 0입니다.
#a, b{ & c{ color : blue; } } .foo c{ color : red; }
다음과 같은 DOM 구조에서
< b class = foo > < c > Blue text</ c > </ b >
텍스트는 빨간색이 아니라 파란색이 됩니다. &의 특이성은 #a([1,0,0])와 b([0,0,1])의 더 큰 값인 [1,0,0]이고, 따라서 & c 선택자의 전체 특이성은 [1,0,1]이 되어 .foo c([0,1,1])보다 큽니다.
주목할 점은, 이것은 중첩을 수동으로 확장해서 비중첩 규칙으로 만들었을 때와는 다른 결과라는 것입니다. 수동으로 확장하면 color: blue 선언은 b c 선택자([0,0,2])에 의해 매치될 것이고, #a c([1,0,1])에 의해 매치되지는 않을 것입니다.
왜 특이성이 비중첩 규칙과 다른가?
중첩 선택자는 의도적으로 :is() 의사클래스와 같은 특이성 규칙을 사용합니다. :is()는 인수들 중 가장 큰 특이성을 사용하며, 실제로 어떤 선택자가 매치되었는지 추적하지 않습니다.
이것은 성능상의 이유로 필요합니다; 선택자가 여러 가능한 특이성을 가지는 경우, 얼마나 정확하게 매칭되었는지에 따라, 선택자 매칭이 훨씬 복잡하고 느려집니다.
하지만 그건 질문을 회피하는 셈이죠: 왜 우리는 &를 :is() 기준으로 정의할까요? Nesting과 유사한 기능의 일부 비브라우저 구현은 결코 :is()로 변환하지 않으며, 주로 :is()가 도입되기 이전에 만들어졌기 때문입니다. 대신, 그들은 직접 변환(desugar)합니다; 하지만 이것은 자체적으로 중대한 문제를 동반하며, 일부 (비교적 흔한) 경우에는 가능성의 기하급수적 폭발 때문에 엄청난 선택자가 우발적으로 생성될 수 있습니다.
.a1, .a2, .a3{ .b1, .b2, .b3{ .c1, .c2, .c3{ ...; } } }
여기서 세 수준의 중첩, 각 수준에 세 개의 선택자가 있으면 27개의 디슈거된 선택자가 생성됩니다. 선택자 목록을 더 늘리거나 중첩 수준을 더 추가하거나 규칙을 더 복잡하게 만들면, 비교적 작은 규칙이 수 메가바이트에 달하는 선택자로 확장될 수 있습니다.
일부 CSS 도구는 휴리스틱으로 일부 변형을 버리며 최악의 경우를 피하지만, 사용자 에이전트가 사용할 수 있는 선택지는 아닙니다.
:is()로 디슈거하면 이러한 문제를 완전히 제거할 수 있지만, 특이성이 약간 덜 유용해지는 대가를 치러야 하고, 이는 합리적인 절충으로 판단되었습니다.
중첩 선택자는 부모 규칙에 의해 매치된 경우, 피처리스(featureless) 요소들도 매치할 수 있습니다.
중첩 선택자가 복합 선택자 내에서 위치하는 것은 동작에 차이를 만들지 않습니다 (즉, &.foo와 .foo&는 동일한 요소를 매치합니다), 다만 기존 규칙인 타입 선택자가 존재한다면 복합 선택자에서 반드시 맨 앞에 위치해야 한다는 규칙은 계속 적용됩니다 (즉, &div는 불법이며 대신 div&로 작성해야 합니다).
테스트
- contextually-invalid-selectors-001.html (live test) (source)
- contextually-invalid-selectors-002.html (live test) (source)
- contextually-invalid-selectors-003.html (live test) (source)
- host-nesting-001.html (live test) (source)
- host-nesting-002.html (live test) (source)
- host-nesting-003.html (live test) (source)
- host-nesting-004.html (live test) (source)
- host-nesting-005.html (live test) (source)
- nesting-type-selector.html (live test) (source)
5. 중첩 선언 규칙
약간 기술적인 이유로, 스타일 규칙의 내용 시작 부분에 나타나는 속성들과 다른 규칙들과 뒤섞여 나타나는 속성들을 구별할 수 있어야 합니다.
.foo{ color : red; @media ( ...) { ...} background: blue; }
우리는 color: red와
background: blue를 약간 다르게 취급해야 합니다.
특히 CSSOM에서, color: red는 스타일 규칙의 style
속성에 노출되는 반면,
background: blue는 대신
cssRules
목록에 표시되어야 합니다.
이를 위해, CSS 파서는 이러한 속성들을 자동으로 특수한 자식 규칙으로 래핑합니다. 그러나 만약 이들을 스타일 규칙으로 & 선택자를 사용해 래핑한다면, 다소 불운한 동작이 발생할 수 있습니다:
.foo, .foo::before{ color : red; &{ background : blue; } }
중첩된 규칙은 background 속성을 .foo::before 요소에 적용하지 않습니다, 왜냐하면 &가 의사요소를 표현할 수 없기 때문입니다.
마찬가지로, 중첩된 비-스타일 규칙의 자식 선언들은 어떤 식으로든 규칙들로 노출되어야 합니다.
이러한 규칙들(예: @media)은 원래 style
속성이 없었기 때문에 이러한 문제가 발생합니다.
이 모든 문제를 해결하기 위해, 대신 연속적으로 직접 중첩된 속성들을 중첩 선언 규칙(nested declarations rule)로 감쌉니다.
달리 명시되지 않는 한, 중첩 선언 규칙은 중첩 스타일 규칙이며, 다른 스타일 규칙과 동일하게 동작합니다. 그것은 부모 스타일 규칙과 정확히 동일한 요소들과 의사요소들을 매치하며, 동일한 특이성 동작을 가집니다. (이는 & 선택자를 가진 스타일 규칙과 유사하지만, 위에서 설명한 것처럼 약간 더 강력합니다.)
왜 중첩 선언 규칙이 존재하는가?
원래 이 명세는 스타일 규칙 내의 모든 선언을 함께 그룹화하여, 원래 위치에서 앞으로 "이동"한 것처럼 동작하도록 했습니다. 또한 중첩 그룹 규칙 내의 원시 선언들을 암시적으로 스타일 규칙으로 래핑했으며, 이때 & 선택자를 사용했습니다.
우리가 중첩 선언 규칙으로 전환한 데에는 두 가지 주요 이유가 있습니다.
첫째, & {...} 규칙을 사용하여 선언을 암묵적으로 중첩 그룹 규칙(nested group rule)으로 감싸는 것은 동작을 변경하기도 했습니다. 이 주석 다음의 예제에서 보여지듯, 부모 스타일 규칙이 의사 요소(pseudo-elements)를 포함하는 경우 문제가 발생하며, 그렇지 않은 경우에도, 중첩 선언의 특이성(specificity) 동작을 잠재적으로 변경할 수 있습니다. 중첩 선언 규칙(nested declarations rule)로 전환하면 이러한 문제를 피할 수 있으며, 중첩된 @media/기타의 동작이 *비*중첩 @media/기타와 동일하게 됩니다.
둘째, 향후 CSS 기능(특히 믹스인)과 관련된 몇 가지 세부사항은, 중첩된 선언들이 자동으로 스타일 규칙의 앞부분으로 옮겨지면 올바르게 작동하지 않습니다. 우리는 다른 규칙들과의 상대적 순서를 유지해야 하며, 이를 CSSOM에서 표현 가능하게 만들려면 어딘가에 래핑되어야 합니다. 일반적인 & {...} 규칙을 사용하면 앞의 단락과 같은 문제가 발생하므로, 중첩 선언 규칙을 사용하면 부작용 없이 이를 처리할 수 있습니다.
.foo, .foo::before, .foo::after{ color : black; @media ( prefers-color-scheme: dark) { &{ color : white; } } }
다크 모드 페이지에서는 .foo 요소의 텍스트 색상이 흰색으로 변경되지만, ::before 및 ::after 의사요소들은 검은색으로 남아있습니다. 이는 & 선택자가 의사요소를 표현할 수 없기 때문입니다.
하지만 대신 다음과 같이 작성되었다면:
.foo, .foo::before, .foo::after{ color : black; @media ( prefers-color-scheme: dark) { color : white; } }
그러면 color: white는 암시적으로 중첩 선언 규칙으로 래핑되고, 이는 부모 스타일 규칙과 정확히 동일하게 매치되므로, 요소와 그 의사요소들 모두 다크 모드에서 흰색 텍스트를 가지게 됩니다.
.foo{ color : black; @media ( ...) { ...} background: silver; }
만약 .foo 규칙의 CSSOM 객체를 검사하면,
그 style
속성에는 오직 하나의 선언만 포함되어 있을 것입니다:
color: black 선언입니다.
background: silver 선언은 대신 암시적으로 생성된 중첩 선언 자식 규칙에서 발견될 것이며,
위치는 fooRule입니다.
테스트
- mixed-declarations-rules.html (visual test) (source)
- nested-declarations-cssom-whitespace.html (live test) (source)
- nested-declarations-cssom.html (live test) (source)
- nested-declarations-matching.html (live test) (source)
6. CSSOM
참고: [CSSOM-1] 은 이제
CSSStyleRule
이 자식 규칙을 가질 수 있다고 정의합니다.
중첩 스타일 규칙(relative selector)에서 상대 선택자를 직렬화할 때, 선택자는 절대화되어야 하며, 암묵적인 중첩 선택자가 삽입되어야 합니다.
6.1. The CSSNestedDeclarations
Interface
The CSSNestedDeclarations
인터페이스는 중첩
선언 규칙을 나타냅니다.
[Exposed =Window ]interface :CSSNestedDeclarations CSSRule { [SameObject ,PutForwards =cssText ]readonly attribute CSSStyleProperties style ; };
style 속성은
규칙에 대해 CSSStyleProperties
객체를 반환해야 하며,
다음과 같은 속성들을 가집니다:
- computed flag
-
설정되지 않음
- readonly flag
-
설정되지 않음
- declarations
-
규칙에 선언된 선언들로, 지정된 순서로 정렬됩니다.
- parent CSS rule
- owner node
-
Null
The CSSNestedDeclarations
규칙은 그 선언 블록이 직접 직렬화된 것처럼 직렬화됩니다.
참고: 이는 예를 들어 insertRule
등을 사용하여 생성할 수 있는 여러 인접한 중첩 선언 규칙이
직렬화되고 다시 파싱될 때 하나의 규칙으로 병합된다는 것을 의미합니다.
개인정보 고려사항
이 명세에 대해 보고된 새로운 개인정보 관련 고려사항은 없습니다.
보안 고려사항
이 명세에 대해 보고된 새로운 보안 관련 고려사항은 없습니다.
7. 변경사항
2023-02-14 부 Working Draft 이후의 주요 변경사항:
-
<scope-start> 선택자가 @scope 규칙의 부모 규칙으로 더 이상 동작하지 않습니다. (Issue 9740)
-
중첩 선택자(nesting selector)가 피처리스(featureless) 요소를 매치할 수 있도록 허용된다는 점을 명확히 했습니다.
-
&div을 다시 유효하지 않도록 변경했습니다; 이제 Syntax가 "무한 선독"을 지원하기 때문에 이를 허용할 필요가 없습니다. 또한 이렇게 하면 전처리기와의 충돌을 피할 수 있습니다. (Issue 8662)
-
CSSOM이 이제 CSSStyleRule이 CSSGroupingRule의 하위클래스임을 정의하므로, cssRules 속성과 관련 기계장치의 수동 정의를 제거했습니다. (Issue 8940)
-
암묵적인 중첩 선택자가 특이성에 미치는 영향을 명확히 했습니다. (Issue 9069)
-
규칙과 뒤섞인 선언들(또는 중첩 그룹 규칙의 모든 선언)은 이제 자동으로
규칙으로 래핑됩니다. (Issue 8738)@nest -
를 중첩 선언 규칙으로 대체했습니다. (Issue 10234)@nest -
웹 플랫폼 테스트(Web Platform Tests) 커버리지를 추가했습니다.