CSS — 나중에 쓴 규칙이 이긴다 · 특정성은 높은데 왜 지는가 · 이론
레이어를 쓰기 시작하면 계산이 한 칸 늘어난다
한 줄 요약
캐스케이드의 정렬 순서에서 레이어는 특정성보다 먼저 갈린다. 그래서 레이어를 쓰기 시작한 시트에서는 특정성이 낮은 규칙이 높은 규칙을 이기는 일이 정상적으로 일어난다.
왜 이게 필요했나
CSS 가 무너지는 경로는 거의 언제나 하나다. 남의 규칙을 이기려고 선택자를 한 칸 길게 쓰고, 다음 사람은 더 길게 쓰고, 결국 !important 가 나온다. 그러면 그 속성은 영원히 !important 로만 다뤄야 한다.
문제의 뿌리는 "이겨야 하는 순서" 와 "특정성" 이 원래 다른 것이라는 데 있다. 초기화 규칙은 언제나 가장 약해야 하고, 유틸리티 클래스는 언제나 가장 세야 한다. 그런데 CSS 에는 그것을 직접 말할 방법이 없어서, 지금까지는 선택자 길이라는 간접적인 수단으로 흉내 냈다.
@layer 는 그 순서를 이름으로 직접 적는 문법이다. [CSS Cascade 5](https://www.w3.org/TR/css-cascade-5/)가 정의하고, [MDN 의 @layer 문서](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer)가 문법을 정리한다.
어떻게 동작하나 — 정렬 순서
명세 6.1절이 정한 기준은 이 순서로 본다. 앞에서 갈리면 뒤는 보지 않는다.
1. 출처와 중요도 (사용자 important > 작성자 important > … )2. 캡슐화 맥락 (섀도 트리)3. 요소에 직접 붙은 스타일 (style 속성)4. 레이어5. 특정성6. 나온 순서!important 는 1번에서 갈리므로 여전히 가장 세다. 그다음이 style 속성, 그다음이 레이어, 그러고 나서야 특정성이다.
두 줄이면 끝난다
레이어 단계의 규칙은 두 줄이다.
- 레이어에 넣지 않은 선언은 암묵적으로 맨 마지막 레이어에 들어간다
- 일반 선언은 뒤 레이어가 이기고, important 선언은 앞 레이어가 이긴다
이 둘을 합치면 처음에는 거꾸로 보이는 결과가 나온다.
@layer reset, components;@layer components { #app .card .title { color: navy; } /* 특정성 1,2,0 */}.title { color: crimson; } /* 특정성 0,1,0, 레이어 밖 */이기는 것은 .title 의 crimson 이다. 레이어 밖은 맨 마지막 레이어이고, 일반 선언에서는 뒤 레이어가 이기기 때문이다. 특정성은 볼 차례가 오지 않는다.
반대로 !important 를 붙이면 순서가 뒤집힌다. reset 레이어의 !important 가 components 레이어의 !important 를 이기고, 레이어 밖의 !important 는 모든 레이어의 !important 에 진다. 명세는 이것이 "important 가 일반 선언의 우선순위를 뒤집는 것과 같은 논리" 라고 설명한다.
순서는 처음 나온 자리에서 정해진다
레이어 순서는 이름이 처음 나온 차례다. 그래서 파일이 여러 개로 나뉘고 번들러가 순서를 바꾸면 레이어 순서도 바뀐다.
그래서 실무에서는 맨 위에 블록 없는 선언형 규칙을 한 줄 둔다.
@layer reset, base, components, utilities;이 한 줄이 파일 전체의 계약이 된다. 그다음부터 어느 파일이 먼저 읽히든 순서는 고정이다.
:where() 와 짝을 이룬다
[:where()](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) 는 특정성이 언제나 0 이다. :where(.card .title) 은 0,0,0 이라 태그 선택자 하나에도 진다.
레이어가 "어느 무리가 이기는가" 를 정한다면, :where() 는 같은 무리 안에서 덮어쓰기 쉬운 기본값을 만든다. 라이브러리가 기본 스타일을 줄 때 이 둘을 함께 쓰면, 쓰는 쪽은 선택자를 길게 쓸 필요가 없어진다.
:is() 와 :not() 은 다르다 — 괄호 안에서 가장 높은 것을 따른다. :is(.card, #app) 은 1,0,0 이다.
현장에서 만나는 모습
레이어를 도입할 때 실제로 부딪히는 것 셋을 적어 둔다.
첫째, 기존 코드를 한꺼번에 레이어에 넣지 못한다. 그럴 때는 남은 코드가 전부 "레이어 밖" 이 되어 모든 레이어보다 세진다. 이것은 사고가 아니라 오히려 안전한 이주 경로다 — 새로 만든 레이어가 기존 화면을 갑자기 덮어쓰지 않는다.
둘째, @import 에 레이어를 붙일 수 있다. @import url(reset.css) layer(reset); 로 남의 CSS 를 통째로 한 레이어에 밀어 넣으면, 그 라이브러리 전체가 내 코드보다 약해진다. 라이브러리를 이기려고 쓰던 !important 가 통째로 사라진다.
셋째, 미디어 쿼리 안에서 레이어를 처음 선언하지 않는다. 명세는 조건이 참일 때만 그 레이어가 순서에 들어간다고 말한다. 화면 폭에 따라 레이어 순서가 달라지는 시트는 재현되지 않는 버그를 만든다. 맨 위의 선언형 규칙 한 줄이 이것도 막아 준다.
다음 실습에서 할 것
네 레이어를 선언하고 규칙을 채운 뒤, 어느 선언이 이기는지 계산기로 직접 확인한다. 레이어 밖이 왜 가장 센지, !important 를 붙이면 왜 뒤집히는지를 예측 파일에 먼저 적고 나서 계산기로 맞춰 본다.