Cascade layers

김동현·2026년 3월 18일

mdn 학습 번역 - CSS

목록 보기
16/190

안녕하세요! 프론트엔드 개발자의 길을 걷고 계신 여러분을 진심으로 환영합니다. 오늘 우리가 다뤄볼 주제는 CSS의 세계에서 비교적 최신 기능이자, 복잡한 프로젝트를 다룰 때 구원자가 되어줄 캐스케이드 레이어(Cascade layers)입니다.

공식 문서가 영어로 되어 있고 내용이 조금 깊어서 당황하셨을 수도 있지만, 걱정하지 마세요! 제가 실무에서 겪었던 뼈저린 경험담과 꿀팁들을 가득 담아 아주 친절하고 이해하기 쉽게 번역과 부연 설명을 해드릴게요. 자, 그럼 시작해볼까요?


캐스케이드 레이어 (Cascade layers)

이 레슨의 목표는 CSS 캐스케이드(CSS cascade)CSS 명시도(CSS specificity)라는 근본적인 개념을 바탕으로 만들어진, 조금 더 고급 기능인 캐스케이드 레이어(cascade layers)를 소개해 드리는 것입니다.

만약 CSS를 처음 배우시는 분이라면, 이 레슨을 진행하는 것이 당장은 크게 와닿지 않고 코스의 다른 부분들보다 약간 학구적으로 느껴질 수도 있어요. 하지만 나중에 프로젝트를 진행하다가 이 녀석을 마주쳤을 때를 대비해, 캐스케이드 레이어가 무엇인지 기본 개념을 알아두는 것은 엄청난 도움이 됩니다. CSS를 더 많이 다뤄보실수록, 캐스케이드 레이어를 이해하고 그 강력한 힘을 활용하는 방법을 아는 것이 여러 외부 라이브러리, 플러그인, 그리고 다른 개발팀이 작성한 CSS가 마구 뒤섞인 코드 베이스를 관리할 때 겪는 끔찍한 고통(디버깅 지옥!)으로부터 여러분을 구출해 줄 거예요.

캐스케이드 레이어는 여러 소스에서 CSS를 가져와서 사용할 때, 즉 CSS 선택자들이 서로 충돌하고 명시도(어느 스타일이 더 우선순위가 높은가) 경쟁이 벌어질 때, 혹은 !important의 사용을 고려해야 할 때 가장 진가를 발휘합니다.

선행 조건 (Prerequisites):캐스케이드(cascade)와 명시도(specificity)를 포함하여 CSS가 어떻게 작동하는지에 대한 전반적인 이해 (먼저 CSS 스타일링 기초충돌 다루기를 공부해 보세요).
학습 목표 (Objective):캐스케이드 레이어가 어떻게 작동하는지 배우기.

요소에 적용되는 각 CSS 속성에는 단 하나의 값만 최종적으로 적용될 수 있습니다. 브라우저의 개발자 도구에서 요소를 검사해 보면, 해당 요소에 적용된 모든 속성 값들을 확인해 볼 수 있죠. 개발자 도구의 "스타일(Styles)" 패널은 검사 중인 요소에 적용된 모든 속성 값을, 일치하는 선택자(selector) 및 CSS 소스 파일과 함께 보여줍니다. 여러 소스 중 가장 우선순위(precedence)가 높은 출처의 선택자가 가진 값이 일치하는 요소에 최종적으로 적용됩니다.

개발자 도구의 스타일 패널에는 최종적으로 적용된 스타일 외에도, 해당 요소와 일치하긴 하지만 캐스케이드, 명시도, 또는 소스 코드의 순서 때문에 밀려나 적용되지 않은 취소선(crossed-out)이 그어진 값들도 표시됩니다. 취소선이 그어진 스타일들은 우선순위가 높은 같은 출처에서 왔지만 명시도가 더 낮거나, 출처와 명시도는 같지만 코드 베이스의 더 윗부분(먼저 선언됨)에서 발견된 경우일 수 있습니다. 최종 적용된 어떤 속성 값이든, 여러 다른 소스에서 와서 취소선이 그어진 수많은 선언들이 밑에 존재할 수 있습니다. 만약 더 높은 명시도를 가진 선택자의 스타일임에도 취소선이 그어져 있다면, 그것은 해당 값이 출처(origin)나 중요도(importance) 면에서 밀렸다는 것을 의미합니다.

일반적으로 사이트의 복잡성이 커지면 스타일시트의 개수도 늘어나며, 이는 스타일시트의 소스 순서(source order)를 더욱 중요하고 복잡하게 만듭니다. 캐스케이드 레이어는 이런 거대한 코드 베이스 전반에 걸쳐 스타일시트를 유지보수하는 것을 단순하게 만들어줍니다. 캐스케이드 레이어는 명시도를 담는 '명시적인 컨테이너'로서, 최종적으로 적용될 CSS 선언들을 더 간단하고 강력하게 제어할 수 있게 해주어 웹 개발자들이 '명시도 전쟁(fighting specificity)'을 치르지 않고도 CSS 섹션들의 우선순위를 정할 수 있게 해줍니다.

💡 강사님의 실무 팁!
"명시도 전쟁"이란 게 뭘까요? 실무에서 Bootstrap이나 Tailwind 같은 라이브러리를 쓰다 보면, 라이브러리가 이미 덮어씌워 둔 스타일을 내 입맛대로 바꾸기 위해 선택자를 억지로 길게 쓰거나 (div.container > ul.list > li.item.active 이런 식으로요!) 최악의 경우 !important를 남발하게 되는 상황을 말해요. 레이어를 사용하면 라이브러리 스타일은 통째로 'base' 레이어에 밀어 넣고, 내 커스텀 스타일을 'components' 레이어에 두어 선택자 길이에 상관없이 무조건 내 스타일이 이기게 만들 수 있답니다!

캐스케이드 레이어를 이해하려면 먼저 CSS 캐스케이드를 잘 이해해야 합니다. 아래 섹션들에서는 중요한 캐스케이드 개념들을 빠르게 복습해 보겠습니다.


캐스케이드 개념 복습하기 (Review of the cascade concept)

CSS의 'C'는 "Cascading(폭포수처럼 위에서 아래로 떨어지는)"을 의미합니다. 이것은 여러 스타일들이 함께 모여 결합되는 방식입니다. 브라우저(사용자 에이전트, user agent)는 모든 요소의 모든 속성에 할당될 값을 결정하기 위해 명확하게 정의된 몇 가지 단계를 거칩니다. 여기서는 이 단계들을 간략히 나열하고, 여러분이 이 레슨을 찾아온 목적이기도 한 4단계 캐스케이드 레이어(Cascade layers)에 대해 더 깊이 파고들어 보겠습니다.

  1. 관련성 (Relevance): 각 요소에 대해 선택자가 일치하는 모든 선언 블록을 찾습니다.
  2. 중요도 (Importance): 규칙들을 일반(normal)인지 중요한(important)지에 따라 정렬합니다. 중요한 스타일이란 !important 플래그가 설정된 스타일을 말합니다.
  3. 출처 (Origin): 중요도로 나뉜 두 개의 양동이 안에서, 작성자(author), 사용자(user), 브라우저 기본(user-agent) 출처를 기준으로 규칙들을 정렬합니다.
  4. 캐스케이드 레이어 (Cascade layers): 6개의 출처-중요도 양동이 안에서, 캐스케이드 레이어를 기준으로 정렬합니다. 일반(normal) 선언의 레이어 순서는 처음 생성된 레이어부터 마지막 레이어 순이며, 그 뒤에 레이어가 없는 일반 스타일이 옵니다. 이 순서는 중요한(important) 스타일에 대해서는 반전되어, 레이어가 없는 중요한 스타일이 가장 낮은 우선순위를 가집니다.
  5. 명시도 (Specificity): 우선순위를 가진 출처 레이어 내에서 서로 경쟁하는 스타일들의 경우, 명시도(specificity)를 기준으로 선언들을 정렬합니다.
  6. 스코프 근접성 (Scoping proximity): 우선순위를 가진 출처 레이어 내에서 두 선택자가 동일한 명시도를 가질 때, 스코프 루트(scope root)까지 DOM 계층 구조를 거슬러 올라가는 홉(hop) 수가 가장 적은 스코프 규칙 내의 속성 값이 승리합니다. 더 자세한 내용과 예제는 @scope 충돌 해결 방법을 참고하세요.
  7. 등장 순서 (Order of appearance): 우선순위를 가진 출처 레이어 내에서 두 선택자가 명시도와 스코프 근접성까지 똑같다면, 가장 마지막에 선언된 속성 값이 승리합니다.

각 단계에서, 이전 단계에서 살아남은(여전히 경쟁 중인) 선언들만이 다음 단계로 넘어가서 "경쟁"합니다. 만약 경쟁 중인 선언이 딱 하나만 남게 되면 그것이 "승리"하며, 이후의 단계들은 무의미해집니다.

출처와 캐스케이드 (Origin and cascade)

캐스케이드 출처 유형에는 세 가지가 있습니다: 브라우저 기본 스타일시트(user-agent), 사용자 스타일시트(user), 그리고 작성자 스타일시트(author)입니다. 브라우저는 각 선언을 출처와 중요도를 기준으로 6개의 출처 양동이로 분류합니다. 우선순위는 총 8단계로 나뉘는데, 앞서 말한 6개의 출처 양동이와 트랜지션 중인 속성, 애니메이션 중인 속성이 포함됩니다. 우선순위 순서는 가장 낮은 순위인 일반 브라우저 기본 스타일부터 시작해서, 현재 적용 중인 애니메이션 스타일, 중요한 브라우저 기본 스타일, 그리고 가장 우선순위가 높은 트랜지션 중인 스타일 순서로 올라갑니다.

  1. 브라우저 기본 일반 스타일 (user-agent normal styles)
  2. 사용자 일반 스타일 (user normal styles)
  3. 작성자 일반 스타일 (author normal styles)
  4. 애니메이션 중인 스타일 (styles being animated)
  5. 작성자 중요 스타일 (author important styles)
  6. 사용자 중요 스타일 (user important styles)
  7. 브라우저 기본 중요 스타일 (user-agent important styles)
  8. 트랜지션 중인 스타일 (styles being transitioned)

"브라우저 기본(user-agent)"은 여러분이 쓰는 웹 브라우저를 뜻합니다. "사용자(user)"는 사이트 방문객을 뜻하고요. "작성자(author)"는 바로 여러분, 개발자입니다. 요소에 직접 <style> 태그를 이용해 인라인으로 선언한 스타일도 작성자 스타일로 간주됩니다. 애니메이션과 트랜지션 스타일을 제외하면, 일반 브라우저 기본 스타일이 가장 낮은 우선순위를 가지고, 중요한 브라우저 기본 스타일이 가장 높은 우선순위를 가집니다.

출처와 명시도 (Origin and specificity)

각 속성에 대해, "승리"하는 선언은 가중치(일반인지 중요인지)를 바탕으로 우선순위를 가진 출처에서 나온 것입니다. 일단 레이어에 대한 것은 잠시 무시해 보면, 가장 높은 우선순위를 가진 출처의 값이 적용되는 셈이죠. 만약 승리한 출처 내에서 하나의 요소에 대해 여러 개의 속성 선언이 경합을 벌인다면, 그때서야 그 경쟁하는 속성 값들을 지정한 선택자들의 명시도(specificity)를 서로 비교하게 됩니다. 명시도는 서로 다른 출처에서 온 선택자들 사이에서는 절대 비교되지 않습니다.

아래 예제에는 두 개의 링크가 있습니다. 첫 번째 링크에는 작성자 스타일이 전혀 적용되어 있지 않으므로, 오직 브라우저 기본 스타일(그리고 만약 설정해둔 게 있다면 개인 사용자 스타일)만 적용됩니다. 두 번째 링크에는 작성자 스타일시트에 있는 선택자의 명시도가 고작 0-0-0임에도 불구하고 작성자가 설정한 text-decorationcolor 속성이 적용됩니다. 작성자 스타일이 "이기는" 이유는, 서로 다른 출처에서 온 스타일들이 충돌할 때, 다른 출처에 있는 명시도가 아무리 높더라도 우선순위를 가진 출처(여기서는 작성자)의 규칙이 그냥 무조건 적용되기 때문입니다.

<p><a href="https://example.org">User agent styles</a></p>
<p><a class="author" href="https://example.org">Author styles</a></p>
:where(a.author) {
  text-decoration: overline;
  color: red;
}

MDN Playground에서 직접 예제 코드 만져보기

이 글을 작성하는 시점을 기준으로 브라우저 기본 스타일시트에서 위 코드와 "경쟁"하는 선택자는 a:any-link인데, 이 녀석의 명시도 가중치는 0-1-1입니다. 비록 이 가중치가 작성자 스타일시트에 있는 0-0-0 선택자보다 크지만, 현재 여러분이 쓰는 브라우저의 선택자가 무엇이든 상관없습니다: 작성자 출처와 브라우저 기본 출처 간에는 명시도 가중치를 아예 비교조차 하지 않기 때문입니다. 명시도 가중치가 어떻게 계산되는지 더 알아보세요.

출처의 우선순위는 선택자의 명시도보다 항상 우선합니다. 요소의 속성이 여러 출처에서 일반 스타일로 중복 선언된 경우, 작성자 스타일시트가 항상 사용자나 브라우저 기본 스타일시트에 선언된 중복 일반 속성들을 덮어씁니다. 만약 스타일이 중요(!important)하다면, 브라우저 기본 스타일시트가 항상 작성자나 사용자 스타일을 이깁니다. 캐스케이드 출처 우선순위 시스템 덕분에 서로 다른 출처 간에 명시도 충돌이 일어나는 일은 아예 발생하지 않습니다.

다음으로 넘어가기 전에 기억해야 할 마지막 한 가지: '등장 순서(order of appearance)'는 우선순위를 가진 출처 내에서 경쟁하는 선언들이 "동일한 명시도"를 가질 때만 유의미해집니다.


캐스케이드 레이어 개요 (Overview of cascade layers)

자, 이제 "캐스케이드 출처 우선순위"는 이해하셨을 텐데, 그렇다면 "캐스케이드 레이어 우선순위"는 대체 뭘까요? 캐스케이드 레이어가 무엇인지, 어떻게 정렬되는지, 그리고 스타일이 어떻게 캐스케이드 레이어에 할당되는지를 다루면서 이 질문에 답해보겠습니다. 일반 레이어(regular layers), 중첩 레이어(nested layers), 그리고 익명 레이어(anonymous layers)에 대해 다룰 텐데요. 우선 캐스케이드 레이어가 무엇이고 어떤 문제를 해결해 주는지부터 논의해 보죠.

캐스케이드 레이어 우선순위 순서 (Cascade layer precedence order)

출처와 중요도를 바탕으로 6단계의 우선순위가 있는 것과 비슷하게, 캐스케이드 레이어를 사용하면 그 어떤 출처 내에서든 하위 출처(sub-origin) 수준의 우선순위를 새로 만들어낼 수 있습니다.

6개의 출처 양동이 각각의 내부에는 여러 개의 캐스케이드 레이어가 존재할 수 있습니다. 레이어가 생성되는 순서는 매우 중요합니다. 생성 순서가 바로 출처 내 레이어 간의 우선순위를 설정하기 때문입니다.

일반(normal) 출처 양동이 안에서, 레이어들은 각 레이어가 생성된 순서대로 정렬됩니다. 우선순위는 첫 번째로 생성된 레이어부터 마지막에 생성된 레이어 순으로 높아지며, 그 뒤를 이어 레이어에 속하지 않은 일반 스타일(unlayered normal styles)이 가장 높은 우선순위를 가집니다.

💡 강사님의 핵심 요약!
"나중에 만들어진 레이어일수록 더 힘이 세다! 그리고 레이어 밖의 일반 코드는 모든 레이어보다 힘이 세다!" 이것만 꼭 기억하세요.

이 순서는 중요한(important, !important가 붙은) 스타일에 대해서는 정반대로 뒤집힙니다. 레이어에 속하지 않은 모든 중요 스타일들은 암시적인 레이어로 묶여, 전환(transition) 중이 아닌 모든 일반 스타일보다 높은 우선순위를 가집니다. 하지만 이 '레이어에 속하지 않은 중요 스타일'은 '레이어에 속한 중요 스타일'보다는 낮은 우선순위를 가집니다. 즉, 같은 출처 내에서는 먼저 선언된 레이어 안의 중요 스타일이 나중에 선언된 레이어 안의 중요 스타일보다 우선순위가 높습니다.

이 튜토리얼의 나머지 부분에서는 "작성자 스타일(author styles)"에만 한정해서 논의를 진행하겠지만, 사용자나 브라우저 기본 스타일시트에도 레이어가 존재할 수 있다는 점을 기억해 두세요.

캐스케이드 레이어가 해결할 수 있는 문제들 (Issues cascade layers can solve)

거대한 코드 베이스는 여러 개발팀, 컴포넌트 라이브러리, 프레임워크, 서드파티(외부) 코드에서 온 스타일들을 포함할 수 있습니다. 얼마나 많은 스타일시트가 포함되든 간에, 이 모든 스타일들은 결국 단 하나의 출처, 즉 작성자(author) 스타일시트 안에서 하나로 쏟아져 내립니다(cascade).

이렇게 많은 소스에서 온 스타일들이 한데 섞이다 보면, 특히 서로 협업하지 않는 팀들 간에 문제가 발생할 수 있습니다. 팀마다 개발 방법론이 다를 수 있거든요. 어떤 팀은 명시도를 낮추는 것을 모범 사례로 삼는 반면, 다른 팀은 모든 선택자에 id를 포함하는 것을 표준으로 삼을 수도 있습니다.

명시도 충돌은 순식간에 눈덩이처럼 불어납니다. 웹 개발자는 급한 불을 끄기 위해 !important 플래그를 추가하는 "임시방편(quick fix)"을 쓸지도 몰라요. 이게 당장은 쉬운 해결책처럼 느껴지겠지만, 결국 명시도 전쟁터를 일반 스타일에서 중요 스타일 영역으로 옮겨놓을 뿐입니다.

캐스케이드 출처(origin)가 사용자, 브라우저, 작성자 스타일 간의 권력 균형을 맞춰주는 것과 마찬가지로, 캐스케이드 레이어는 단일 출처 내에서 마치 각 레이어가 '하위 출처'인 것처럼 관심사를 구조화하고 균형을 맞출 수 있는 방법을 제공합니다. 각 팀별로, 컴포넌트별로, 서드파티별로 레이어를 생성하고 레이어 순서를 바탕으로 스타일의 우선순위를 지정할 수 있죠.

한 레이어 안에 있는 규칙들은 함께 모여 적용되며, 레이어 밖의 다른 스타일 규칙들과 명시도로 싸우지 않습니다. 캐스케이드 레이어를 사용하면 이러한 하위 출처들 간의 명시도를 걱정할 필요 없이, 특정 스타일시트 전체를 다른 스타일시트보다 우선시할 수 있습니다.

레이어 우선순위는 언제나 선택자의 명시도를 이깁니다. 우선순위가 높은 레이어의 스타일이 낮은 우선순위 레이어의 스타일을 무조건 "이깁니다". 지고 있는 레이어 안에 있는 선택자의 명시도가 아무리 높더라도 아무 소용이 없습니다. 명시도는 여전히 '같은 레이어 내에서' 경쟁하는 속성 값들 사이에서는 중요하지만, 각 속성에 대해 오직 가장 우선순위가 높은 레이어만 고려되기 때문에 레이어 간에는 명시도 고민을 할 필요가 전혀 없어집니다.

중첩된 캐스케이드 레이어가 해결할 수 있는 문제들 (Issues nested cascade layers can solve)

캐스케이드 레이어를 사용하면 중첩된 레이어(nested layers)도 만들 수 있습니다. 각각의 캐스케이드 레이어는 그 안에 또 다른 레이어들을 품을 수 있죠.

예를 들어, 컴포넌트 라이브러리를 components라는 레이어 안으로 임포트(import)할 수 있습니다. 일반적인 캐스케이드 레이어는 컴포넌트 라이브러리를 작성자 출처 안으로 추가하면서도 다른 작성자 스타일과의 명시도 충돌을 싹 제거해 줍니다. 그리고 개발자는 이 components 레이어 안에서 여러 테마들을 각각 별도의 중첩 레이어로 정의하기로 결정할 수 있습니다. 이러한 중첩된 테마 레이어들의 순서는 뷰포트 크기나 화면 방향(orientation) 같은 미디어 쿼리(아래 레이어 생성과 미디어 쿼리 섹션 참고)에 기반하여 정의될 수 있습니다. 이러한 중첩 레이어들은 명시도 충돌 없이 테마를 구축할 수 있는 훌륭한 방법을 제공합니다.

레이어를 중첩할 수 있는 기능은 컴포넌트 라이브러리, 프레임워크, 서드파티 위젯, 테마 등을 개발하는 모든 사람에게 매우 유용합니다.

레이어를 중첩하는 기능은 레이어 이름이 서로 충돌할지도 모른다는 걱정도 덜어줍니다. 이 부분은 중첩 레이어 섹션에서 다루겠습니다.

"작성자는 요소의 기본값, 서드파티 라이브러리, 테마, 컴포넌트, 오버라이드(재정의), 그리고 기타 스타일링 관심사를 나타내기 위해 레이어를 생성할 수 있습니다. — 그리고 각 레이어 내의 선택자나 명시도를 수정하거나, 레이어 간의 충돌을 해결하기 위해 단순히 등장 순서에 의존하지 않고도, 레이어의 캐스케이드 순서를 명시적인 방식으로 재정렬할 수 있습니다."

Cascading and Inheritance 사양서.


캐스케이드 레이어 생성하기 (Creating cascade layers)

레이어는 다음 방법 중 하나를 사용하여 만들 수 있습니다:

  • @layer 문장(statement) at-규칙: @layer 뒤에 하나 이상의 레이어 이름을 쉼표로 나열하여 선언합니다. 이 방법은 레이어에 어떤 스타일도 할당하지 않고 이름만 가진 레이어를 '선언'만 합니다.
  • @layer 블록(block) at-규칙: 블록({ }) 안의 모든 스타일이 지정된 이름(혹은 이름 없는) 레이어에 추가됩니다.
  • layer 키워드나 layer() 함수를 사용한 @import 규칙: 임포트(불러오기)하는 파일의 내용을 해당 레이어 안으로 쏙 집어넣습니다.

위의 세 가지 방법 모두, 지정한 이름의 레이어가 아직 초기화되지 않았다면 새 레이어를 생성합니다. 만약 @layer at-규칙이나 layer()를 사용한 @import에서 레이어 이름을 제공하지 않으면, 새로운 익명(이름 없는) 레이어가 생성됩니다.

참고:
레이어의 우선순위는 레이어가 생성된 순서를 따릅니다. 레이어에 속하지 않은 스타일, 즉 "언레이어드 스타일(unlayered styles)"은 한데 모여 암시적인 마지막 레이어가 되며, 가장 강력한 우선순위를 가집니다.

중첩 레이어에 대해 논의하기 전에, 레이어를 생성하는 이 세 가지 방법에 대해 조금 더 자세히 알아보겠습니다.

이름을 부여하는 @layer 문장 at-규칙 (The @layer statement at-rule for named layers)

레이어의 순서는 레이어가 CSS에 나타나는 순서에 의해 결정됩니다. 아무 스타일도 할당하지 않고 @layer 뒤에 하나 이상의 레이어 이름을 쉼표로 나열하여 선언하는 방식은 레이어 순서를 정의하는 한 가지 좋은 방법입니다.

@layer CSS at-규칙은 캐스케이드 레이어를 선언하고 여러 개의 캐스케이드 레이어가 있을 때 우선순위 순서를 정의하는 데 사용됩니다. 다음 at-규칙은 나열된 순서대로 세 개의 레이어를 선언합니다:

@layer theme, layout, utilities;

레이어 순서에 대한 완벽한 통제권을 쥐기 위해, 여러분은 이 @layer 선언문(물론 여러분의 사이트에 맞는 의미 있는 레이어 이름들을 써서요)을 CSS 파일의 가장 첫 번째 줄에 두고 싶으실 겁니다.

만약 위 문장이 사이트 CSS의 첫 번째 줄이라면, 레이어 순서는 theme, layout, utilities가 됩니다. 만약 위 문장이 나오기 전에 이미 일부 레이어가 생성되어 있었다면, 이 이름들의 레이어가 존재하지 않는다는 전제하에 이 세 레이어가 생성되어 기존 레이어 목록의 맨 뒤에 추가됩니다. 하지만 똑같은 이름을 가진 레이어가 이미 존재한다면, 위 문장은 이미 있는 것을 제외하고 새로운 레이어만 추가로 생성합니다. 예를 들어 layout 레이어가 이미 만들어져 있었다면 themeutilities 두 개만 새로 생성되지만, 레이어의 순서는 layout, theme, utilities가 됩니다 (먼저 생성된 놈이 앞순위를 차지하니까요).

이름 있는 레이어와 익명 레이어를 위한 @layer 블록 at-규칙 (The @layer block at-rule for named and anonymous layers)

레이어는 블록 형태의 @layer at-규칙을 사용해서도 생성할 수 있습니다. @layer at-규칙 뒤에 식별자(이름)와 스타일 블록이 오면, 이 식별자는 레이어의 이름이 되고 at-규칙 안의 스타일들은 해당 레이어의 스타일로 추가됩니다. 만약 지정한 이름의 레이어가 아직 존재하지 않는다면 새로운 레이어가 생성될 것입니다. 만약 지정한 이름의 레이어가 이미 존재한다면, 이 스타일들은 이전에 생성된 기존 레이어 안으로 덧붙여집니다. @layer를 사용하여 스타일 블록을 만들 때 이름을 아예 지정하지 않으면, at-규칙 안의 스타일들은 새로운 익명(anonymous) 레이어에 추가됩니다.

아래 예제에서는 4개의 @layer 블록 at-규칙과 1개의 @layer 문장 at-규칙을 사용했습니다. 이 CSS는 나열된 순서대로 다음 작업들을 수행합니다:

  1. layout이라는 이름의 레이어를 생성합니다.
  2. 이름 없는 익명 레이어를 생성합니다.
  3. 세 개의 레이어 목록을 선언하지만, layout은 이미 존재하므로 themeutilities 두 개의 새 레이어만 생성합니다.
  4. 이미 존재하는 layout 레이어에 추가 스타일을 덧붙입니다.
  5. 두 번째 이름 없는 익명 레이어를 생성합니다.
/* file: layers1.css */

/* 레이어에 속하지 않은 일반 스타일 (unlayered styles) */
body {
  color: #333333;
}

/* 첫 번째 레이어 `layout`을 생성합니다 */
@layer layout {
  main {
    display: grid;
  }
}

/* 두 번째 레이어: 이름 없는 익명 레이어를 생성합니다 */
@layer {
  body {
    margin: 0;
  }
}

/* 세 번째와 네 번째 레이어 `theme`과 `utilities`를 생성합니다 */
@layer theme, layout, utilities;

/* 이미 존재하는 `layout` 레이어에 스타일을 덧붙입니다 */
@layer layout {
  main {
    color: black;
  }
}

/* 다섯 번째 레이어: 또 다른 이름 없는 익명 레이어를 생성합니다 */
@layer {
  body {
    margin: 1vw;
  }
}

위 CSS에서 우리는 layout, <anonymous(01)>, theme, utilities, 그리고 <anonymous(02)>라는 총 5개의 레이어를 그 순서대로 생성했습니다. 그리고 body 스타일 블록에 포함된 언레이어드(레이어에 속하지 않은) 스타일들이 모인 6번째 암시적 레이어도 있죠. 레이어 순서는 레이어가 생성된 순서이며, 이 언레이어드 스타일들로 이루어진 암시적 레이어는 항상 맨 마지막에 위치합니다. 한 번 생성된 레이어의 순서는 절대로 바꿀 수 없습니다.

우리는 layout이라는 레이어에 몇 가지 스타일을 할당했습니다. 이름 있는 레이어가 존재하지 않을 때, @layer at-규칙에 이름을 명시하면 (스타일을 할당하든 안 하든) 그 즉시 레이어가 생성되며 기존 레이어 이름 목록의 맨 끝에 추가됩니다. 이름 있는 레이어가 이미 존재한다면, 블록 내의 모든 스타일은 이전에 존재하던 레이어의 스타일에 조용히 덧붙여집니다. 즉, 이미 존재하는 레이어 이름을 재사용하여 스타일 블록을 지정한다고 해서 새로운 레이어가 또 생성되는 것은 아닙니다.

익명 레이어는 레이어의 이름을 짓지 않고 스타일을 할당하여 생성됩니다. 이름 없는 레이어에는 오직 생성되는 바로 그 순간에만 스타일을 추가할 수 있습니다.

참고:
이름을 적지 않고 @layer를 또 사용하면 단순히 새로운 이름 없는 레이어가 하나 더 추가될 뿐입니다. 이전에 존재하던 이름 없는 레이어에 스타일을 덧붙이는 것이 절대 아닙니다.

@layer at-규칙은 레이어를 생성하거나, 이름 있는 레이어가 이미 존재하는 경우 스타일을 덧붙입니다. 우리가 첫 번째 익명 레이어를 <anonymous(01)>로, 두 번째를 <anonymous(02)>라고 부른 건 단순히 설명을 돕기 위해서일 뿐입니다. 이들은 실제로는 아무 이름도 없는 레이어입니다. 이들을 나중에 다시 참조하거나 스타일을 덧붙일 수 있는 방법은 없습니다.

레이어 외부에서 선언된 모든 스타일은 '암시적 레이어(implicit layer)' 하나로 뭉쳐집니다. 위 예제 코드에서 가장 첫 번째 선언은 bodycolor: #333333 속성을 설정했습니다. 이것은 어떤 레이어 외부에서 선언되었죠. 레이어에 속하지 않은 일반 선언은, 비록 명시도가 낮고 소스 코드 맨 처음에 등장했더라도, 레이어 안에 있는 일반 선언들보다 무조건 우선순위가 높습니다. 이것이 바로 코드 블록의 맨 처음에 언레이어드 CSS가 선언되었음에도 불구하고, 이 언레이어드 스타일들을 포함하는 암시적 레이어가 마치 가장 마지막에 선언된 레이어인 것처럼 우선순위를 차지하는 이유를 설명해 줍니다.

@layer theme, layout, utilities; 라는 여러 레이어를 선언한 줄에서는, 오직 themeutilities 레이어만 생성되었습니다; layout은 첫 번째 줄에서 이미 만들어졌기 때문이죠. 이 선언은 이미 생성된 레이어의 순서를 바꾸지 않는다는 점을 유의하세요. 현재로선 한 번 선언된 레이어의 순서를 재정렬할 방법은 없습니다.

다음 예제에서는 두 개의 레이어에 스타일을 할당하면서, 생성과 동시에 이름을 지정해 보았습니다. 이 레이어들은 처음 사용될 때 이미 생성되어 존재하기 때문에, 맨 마지막 줄에서 그것들을 다시 선언해 봤자 아무 일도 일어나지 않습니다.

<h1>Is this heading underlined?</h1>
@layer page {
  h1 {
    text-decoration: overline;
    color: red;
  }
}

@layer site {
  h1 {
    text-decoration: underline;
    color: green;
  }
}

/* 이 줄은 아무 역할도 하지 않습니다 */
@layer site, page;

MDN Playground에서 직접 예제 코드 만져보기

마지막 줄의 @layer site, page; 코드를 잘라서 맨 첫 줄로 옮겨보세요. 어떤 일이 발생하나요?

레이어 생성과 미디어 쿼리 (Layer creation and media queries)

만약 여러분이 미디어 쿼리(media queries)기능 쿼리(feature queries)를 사용해서 레이어를 정의했는데, 현재 환경이 그 미디어 조건에 맞지 않거나 브라우저가 해당 기능을 지원하지 않는다면, 그 레이어는 아예 생성되지 않습니다. 아래 예제는 기기나 브라우저 창의 크기를 변경하면 레이어의 순서가 어떻게 바뀔 수 있는지를 보여줍니다. 이 예제에서 우리는 화면이 넓은 브라우저에서만 site 레이어를 생성하도록 했습니다. 그런 다음 pagesite 레이어에 각각 그 순서대로 스타일을 할당했습니다.

<h1>Is this heading underlined?</h1>
@media (width >= 50em) {
  @layer site;
}

@layer page {
  h1 {
    text-decoration: overline;
    color: red;
  }
}

@layer site {
  h1 {
    text-decoration: underline;
    color: green;
  }
}

MDN Playground에서 직접 예제 코드 만져보기

넓은 화면(wide screens)에서는 맨 첫 줄의 미디어 쿼리 안에서 site 레이어가 먼저 선언되므로, site 레이어는 나중에 선언된 page 레이어보다 낮은 우선순위를 갖게 됩니다. 반대로 좁은 화면(narrow screens)에서는 site 레이어가 나중에 선언되므로 page 레이어보다 우선순위가 높습니다. 만약 효과가 잘 보이지 않는다면, 미디어 쿼리의 50em10em이나 100em으로 바꿔보세요.

@import를 사용해 이름 있는 레이어나 익명 레이어로 스타일시트 불러오기 (Importing style sheets into named and anonymous layers with @import)

@import 규칙을 사용하면 사용자가 다른 스타일시트의 스타일 규칙들을 메인 CSS 파일이나 <style> 요소 안으로 직접 가져올 수 있습니다.

스타일시트를 가져올 때, @import 문은 스타일시트나 <style> 블록 안의 다른 어떤 CSS 스타일들보다 무조건 가장 먼저 정의되어야 합니다. @import 문이 첫 번째로 와야 하지만, 그 앞에는 스타일을 할당하지 않고 레이어의 이름만 나열해서 생성하는 @layer at-규칙이 먼저 올 수는 있습니다. (또한 @charset 규칙이 @import 앞에 올 수도 있습니다.)

여러분은 스타일시트를 이름 있는 레이어, 이름 있는 중첩 레이어, 또는 익명 레이어로 불러올 수 있습니다. 다음 코드는 스타일시트들을 각각 components 레이어, components 레이어 안의 중첩된 dialog 레이어, 그리고 이름 없는 레이어에 차례대로 불러오는 모습을 보여줍니다:

@import "components-lib.css" layer(components);
@import "dialog.css" layer(components.dialog);
@import "marketing.css" layer();

단일 레이어에 여러 개의 CSS 파일을 불러오는 것도 가능합니다. 다음 선언은 두 개의 개별 파일을 하나의 social 레이어 안으로 불러옵니다:

@import "comments.css" layer(social);
@import "sm-icons.css" layer(social);

미디어 쿼리기능 쿼리를 사용해 특정 조건에 기반하여 스타일을 가져오고 레이어를 생성할 수도 있습니다. 다음 코드는 브라우저가 display: ruby 속성을 지원할 때만 스타일시트를 international 레이어로 불러오며, 가져오는 파일은 화면 너비 조건에 따라 달라집니다.

@import "ruby-narrow.css" layer(international) supports(display: ruby)
  (width < 32rem);
@import "ruby-wide.css" layer(international) supports(display: ruby)
  (width >= 32rem);

참고:
HTML의 <link> 태그를 써서 스타일시트를 연결할 때 레이어를 지정하는 직접적인 방법은 아직 없습니다. 스타일시트 안에서 직접 @layer를 사용할 수 없는 외부 파일을 레이어 안으로 가져오고 싶다면 @import 방식을 사용하세요.


중첩된 캐스케이드 레이어 개요 (Overview of nested cascade layers)

중첩 레이어(Nested layers)는 이름 있는 레이어나 익명 레이어 '안에 들어있는' 레이어입니다. 모든 캐스케이드 레이어는 심지어 익명 레이어조차도 자기 안에 또 다른 레이어들을 품을 수 있습니다. 다른 레이어로 임포트(import)된 레이어들은 해당 레이어 안에서 중첩 레이어가 됩니다.

레이어 중첩의 장점 (Advantages of nesting layers)

레이어를 중첩할 수 있는 기능 덕분에, 개발팀들은 다른 팀이 자신들의 레이어를 또 다른 레이어 안에 넣을지 말지 고민할 필요 없이 캐스케이드 레이어를 자유롭게 생성할 수 있습니다. 마찬가지로, 서드파티(외부) 스타일시트를 임포트할 때도 그 스타일시트가 자체적으로 레이어 구조를 가지고 있는지 걱정할 필요 없이 안심하고 특정 레이어 안으로 가져올 수 있죠. 레이어는 중첩이 가능하기 때문에, 외부 스타일시트의 레이어 이름과 우리 내부 스타일시트의 레이어 이름이 서로 충돌할지도 모른다는 걱정에서 해방됩니다.

💡 강사님의 실무 팁!
"내 레이어 이름이 다른 회사 플러그인의 레이어 이름이랑 겹치면 어떡하지?" 바로 이 걱정을 중첩(Nesting)이 완벽히 해결해 줍니다! 마치 자바스크립트의 네임스페이스(namespace)나 스코프(scope)처럼 내 코드를 안전하게 격리할 수 있는 마법 같은 기능이에요.

중첩된 캐스케이드 레이어 생성하기 (Creating nested cascade layers)

중첩 레이어는 일반 레이어에 대해 설명한 것과 동일한 방법을 사용해 생성할 수 있습니다. 예를 들어, 점 표기법(dot notation, .)을 사용해서 @layer at-규칙 뒤에 하나 이상의 레이어 이름을 적어주면 됩니다. 점과 레이어 이름이 여러 개 이어진다는 것은 그만큼 여러 번 깊게 중첩되어 있다는 뜻입니다.

블록 형태의 @layer at-규칙 안에 또 다른 블록 형태의 @layer at-규칙을 넣으면(이름이 있든 없든 상관없이), 그 안에 들어간 블록은 중첩 레이어가 됩니다. 이와 유사하게, 스타일시트가 layer 키워드나 layer() 함수를 포함한 @import 선언을 통해 불려오면, 그 안의 스타일들은 지정된 이름 있는(또는 익명) 레이어에 할당됩니다. 만약 임포트 되는 파일 안에 이미 레이어들이 존재한다면, 그 레이어들은 고스란히 이 익명(또는 이름 있는) 레이어 안의 중첩 레이어로 쏙 들어가게 됩니다.

다음 예제를 살펴보겠습니다:

@import "components-lib.css" layer(components);
@import "narrow-theme.css" layer(components.narrow);

첫 번째 줄에서 우리는 components-lib.csscomponents 레이어 안으로 불러왔습니다. 만약 저 파일 안에 이름이 있든 없든 어떤 레이어들이 존재한다면, 그 레이어들은 자동으로 components 레이어 내의 중첩 레이어가 됩니다.

두 번째 줄은 narrow-theme.csscomponents의 하위 레이어인 narrow 레이어로 불러옵니다. 중첩된 components.narrow 레이어는 components 레이어 안에서 가장 마지막 레이어로 생성됩니다. 단, components-lib.css 파일 안에 이미 narrow라는 레이어가 있었다면 예외겠죠. 그럴 경우엔 새로 생성되는 대신, 기존의 components.narrow 중첩 레이어 안으로 narrow-theme.css의 내용이 그냥 덧붙여질 것입니다. 점 표기법 components.<layerName> 패턴을 사용하면 components 레이어에 새로운 이름 있는 중첩 레이어들을 얼마든지 계속 추가할 수 있습니다. 앞서 언급했듯이 이름 없는 레이어도 만들 수는 있지만, 나중에 다시 접근할 방법은 없습니다.

다른 예제를 보겠습니다. 이번에는 아래 구문을 사용하여 layers1.css를 이름 있는 레이어로 임포트해 볼까요:

@import "layers1.css" layer(example);

이렇게 하면 example이라는 이름의 단일 레이어가 생성되는데, 이 안에는 몇 가지 선언들과 함께 5개의 중첩 레이어가 들어있게 됩니다. 즉, example.layout, example.<anonymous(01)>, example.theme, example.utilities, 그리고 example.<anonymous(02)> 가 됩니다.

이름이 있는 중첩 레이어에 스타일을 추가하고 싶을 때는 점 표기법(dot notation)을 사용하면 됩니다:

@layer example.layout {
  main {
    width: 50vw;
  }
}

레이어 순서에 따른 우선순위 결정하기 (Determining the precedence based on the order of layers)

레이어의 순서가 곧 우선순위를 결정합니다. 그러므로 레이어를 정의하는 순서는 굉장히 중요합니다. 캐스케이드가 출처와 중요도를 기준으로 정렬하는 것과 완벽히 똑같은 방식으로, 캐스케이드 레이어는 각 CSS 선언을 출처 레이어(origin layer)와 중요도에 따라 정렬합니다.

일반 캐스케이드 레이어의 우선순위 순서 (Precedence order of regular cascade layers)

@import "A.css" layer(firstLayer);
@import "B.css" layer(secondLayer);
@import "C.css";

위의 코드는 두 개의 이름 있는 레이어를 생성합니다 (C.css 스타일들은 레이어에 속하지 않은 언레이어드 스타일들의 암시적 레이어에 덧붙여집니다). 설명의 편의를 위해 세 개의 파일(A.css, B.css, C.css) 내부에는 추가적인 레이어가 없다고 가정해 봅시다. 다음 목록은 이 파일들 안팎에서 선언된 스타일들이 가장 낮은 우선순위(1)부터 가장 높은 우선순위(10)까지 어떻게 정렬되는지를 보여줍니다.

  1. firstLayer 일반 스타일 (A.css)
  2. secondLayer 일반 스타일 (B.css)
  3. 레이어에 속하지 않은 일반 스타일 (C.css)
  4. 인라인(inline) 일반 스타일
  5. 애니메이션 중인 스타일 (animating styles)
  6. 레이어에 속하지 않은 중요(!important) 스타일 (C.css)
  7. secondLayer 중요(!important) 스타일 (B.css)
  8. firstLayer 중요(!important) 스타일 (A.css)
  9. 인라인(inline) 중요(!important) 스타일
  10. 트랜지션 중인 스타일 (transitioning styles)

레이어 내부에 선언된 일반 스타일은 가장 낮은 우선순위를 받으며, 레이어가 생성된 순서대로 정렬됩니다. 가장 먼저 생성된 레이어의 일반 스타일이 가장 꼴찌이고, 가장 마지막에 생성된 레이어의 일반 스타일이 레이어들 중에서는 가장 대장이 됩니다. 다시 말해, 만약 충돌이 발생한다면 firstLayer 내에 선언된 일반 스타일은 이 목록 뒤에 나오는 모든 스타일들에 의해 사정없이 덮어씌워지게 됩니다.

그다음 순위는 레이어 외부에서 선언된 스타일들입니다. C.css의 스타일들은 레이어 안으로 임포트되지 않았으므로 firstLayersecondLayer에서 온 충돌하는 스타일들을 모두 덮어씁니다. 레이어에 선언되지 않은 스타일은 레이어 '안에' 선언된 스타일보다 항상 우선순위가 높습니다 (중요 스타일은 예외입니다).

인라인 스타일은 HTML의 style 속성을 사용하여 선언됩니다. 이렇게 선언된 일반 스타일은 언레이어드 및 레이어드 스타일시트(firstLayer – A.css, secondLayer – B.css, 그리고 C.css)에서 발견된 일반 스타일보다 우선순위를 갖습니다.

애니메이션 중인 스타일은 인라인 일반 스타일을 포함한 모든 일반 스타일보다 우선순위가 높습니다.

중요한 스타일(Important styles), 즉 !important 플래그가 포함된 속성 값들은 지금까지 목록에서 언급된 모든 스타일보다 무조건 우선합니다. 이들은 일반 스타일의 정반대 순서로 정렬됩니다. 레이어 외부에서 선언된 모든 중요 스타일은 레이어 '내부'에서 선언된 중요 스타일보다 순위가 밀립니다. 레이어 내부에서 발견된 중요 스타일들끼리도 레이어 생성 순서에 따라 정렬되지만, 그 방향이 반대입니다. 중요 스타일에 한해서는, 가장 마지막에 생성된 레이어가 가장 약하고, 가장 처음 생성된 레이어가 선언된 레이어들 사이에서 가장 강력한 우선순위를 차지합니다.

💡 강사님의 실무 팁!
"왜 중요(!important) 스타일은 순서가 반대일까요?"
레이어를 설계한 사람들은 외부 라이브러리(먼저 선언된 레이어)가 자신이 가진 레이아웃을 '무조건 지키기 위해' 사용한 !important를, 커스텀 스타일(나중에 선언된 레이어)이 존중해주길 바랐기 때문입니다. 그래서 먼저 선언된 레이어의 !important가 더 힘이 세도록 설계된 것이죠!

인라인 중요 스타일은 당연하게도 다른 곳에 선언된 중요 스타일보다 우선순위가 높습니다.

트랜지션 중인 스타일(Transitioning styles)은 무적의 우선순위를 자랑합니다. 일반 속성 값이 트랜지션 중일 때는 인라인 중요 스타일마저도 이겨버리고 모든 다른 속성 값 선언들보다 우선합니다. 단, 오직 트랜지션이 진행되는 동안에만 그렇습니다.

<div>
  <h1 style="color: yellow; background-color: maroon !important;">
    Inline styles
  </h1>
</div>
@layer A, B;

h1 {
  font-family: sans-serif;
  margin: 1em;
  padding: 0.2em;
  color: orange;
  background-color: green;
  text-decoration: overline pink !important;
  box-shadow: 5px 5px lightgreen !important;
}

@layer A {
  h1 {
    color: grey;
    background-color: black !important;
    text-decoration: line-through grey;
    box-shadow: -5px -5px lightblue !important;
    font-style: normal;
    font-weight: normal !important;
  }
}

@layer B {
  h1 {
    color: aqua;
    background: yellow !important;
    text-decoration: underline aqua;
    box-shadow: -5px 5px magenta !important;
    font-style: italic;
    font-weight: bold !important;
  }
}

MDN Playground에서 직접 예제 코드 만져보기

이 예제에서는 처음에 어떤 스타일도 할당하지 않고 @layer 문장 at-규칙을 사용해 두 개의 레이어(AB)가 정의되었습니다. 레이어 스타일들은 어떤 레이어에도 속하지 않은 h1 CSS 규칙 뒤에 나타나는 두 개의 @layer 블록 안에서 정의되었습니다.

h1 요소에 style 속성을 사용해 추가된 인라인 스타일은, 일반 color와 중요한 background-color를 설정하고 있습니다. 일반 인라인 스타일은 레이어 안팎의 모든 일반 스타일을 덮어씁니다. 중요한 인라인 스타일은 레이어 안팎의 모든 일반 및 중요 작성자 스타일을 사정없이 덮어씁니다. 작성자 스타일이 중요한 인라인 스타일을 덮어쓸 방법은 없습니다.

일반 text-decoration과 중요한 box-shadowstyle 속성을 통한 인라인 스타일에 포함되어 있지 않으므로 덮어써질 수 있습니다. 인라인이 아닌 일반 스타일의 경우, 레이어에 속하지 않은 스타일(unlayered styles)이 레이어 스타일보다 우선합니다. 하지만 중요 스타일의 경우는 레이어 순서가 결정적인 역할을 합니다. 일반 스타일에서는 언레이어드 스타일이 레이어 안의 스타일들을 덮어쓰지만, 중요 스타일에서는 이 우선순위가 뒤집혀서 언레이어드 중요 스타일이 레이어 안의 중요 스타일보다 힘이 약해집니다.

오로지 레이어 안에서만 선언된 두 가지 스타일은 일반 중요도를 가진 font-style!important 플래그가 붙은 font-weight입니다. 일반 스타일의 경우, 나중에 선언된 B 레이어가 먼저 선언된 A 레이어의 스타일을 덮어씁니다. (일반 스타일은 나중 놈이 이긴다!) 하지만 중요 스타일에 대해서는 이 순서가 완전히 역전됩니다. 중요한 font-weight 선언을 보면, 먼저 선언된 레이어 A가 나중에 선언된 레이어 B를 짓누르고 우선순위를 차지합니다. (중요 스타일은 먼저 선언된 놈이 이긴다!)

첫 번째 줄을 @layer A, B;에서 @layer B, A;로 바꾸어 레이어 순서를 뒤집어 보세요. 직접 해보시길 권합니다! 순서를 바꿨더니 어떤 스타일들이 변하고 어떤 것들은 그대로 남아있나요? 그 이유는 무엇일까요?

레이어의 순서는 레이어가 CSS에 등장하는 순서에 의해 결정된다고 했죠. 첫 번째 줄에서 우리는 레이어에 스타일을 할당하지 않고 @layer 뒤에 쉼표로 이름만 나열한 뒤 세미콜론으로 끝맺었습니다. 만약 우리가 이 줄을 생략했더라도 결과는 완벽히 똑같았을 것입니다. 왜일까요? 우리가 바로 뒤에 나오는 이름 있는 @layer 블록에서 A, 그리고 B 순서로 스타일 규칙을 할당했기 때문입니다. 이 두 레이어는 그 첫 번째 줄에서 '생성'되었습니다. 만약 첫 줄이 없었더라도 뒤에 나오는 룰 블록들이 그 순서대로(A 다음 B) 레이어를 생성했을 것입니다.

우리가 굳이 첫 번째 줄을 맨 위에 따로 적어둔 데는 두 가지 이유가 있습니다: 첫째는 여러분이 쉽게 코드를 편집해서 순서를 바꿔볼 수 있게 하기 위함이었고, 둘째는 실제로 실무에서도 레이어 순서를 파일의 맨 위에서 명시적으로 먼저 선언해 두는 것이 레이어 관리의 최고 모범 사례(best practice)이기 때문입니다.

자, 요약해 볼까요:

  • 레이어의 우선순위는 레이어가 생성된 순서를 따릅니다.
  • 한 번 생성되면 레이어 순서를 변경할 수 없습니다.
  • 일반 스타일(normal styles)의 레이어 우선순위는 나중에 생성된 레이어일수록 높습니다.
  • 레이어에 속하지 않은(언레이어드) 일반 스타일이 레이어에 속한 일반 스타일보다 항상 우선순위가 높습니다.
  • 중요 스타일(important styles)의 레이어 우선순위는 정반대여서, 먼저 생성된 레이어일수록 높습니다.
  • 레이어에 속한 모든 중요 스타일은 레이어에 속하지 않은 중요(그리고 일반) 스타일보다 무조건 우선순위가 높습니다.
  • 인라인 일반 스타일은 레이어에 속해있든 아니든 모든 일반 스타일보다 우선합니다.
  • 인라인 중요 스타일은 트랜지션 중인 스타일을 제외한 그 어떤 다른 스타일보다도 무조건 이깁니다.
  • 작성자 스타일이 인라인 중요 스타일을 이길 방법은 (잠깐 동안 적용되는 트랜지션을 제외하면) 존재하지 않습니다.

중첩된 캐스케이드 레이어의 우선순위 순서 (Precedence order of nested cascade layers)

중첩 레이어의 캐스케이드 우선순위 순서는 일반 레이어와 유사하지만, 오직 해당 레이어 '내부'에서만 작동합니다. 우선순위 순서는 중첩 레이어가 생성된 순서를 기반으로 합니다. 레이어 내에서 중첩되지 않은(non-nested) 스타일은 중첩된 일반 스타일보다 우선하며, 중요 스타일의 경우에는 이 순서가 반전됩니다. 중첩 레이어들 간에는 명시도 가중치가 아무런 의미가 없지만, 단일 중첩 레이어 '내부'에서 서로 충돌하는 스타일들끼리는 여전히 명시도가 중요합니다.

다음 예제는 components 레이어, components.narrow 중첩 레이어, 그리고 components.wide 중첩 레이어를 순서대로 생성하고 스타일을 추가합니다:

<div>Text</div>
div {
  height: 150px;
  width: 150px;
  margin: 1rem;
  padding: 1rem;
  font-size: 3rem;
}
div {
  background-color: wheat;
  color: pink !important;
}

@layer components {
  div {
    background-color: yellow;
    border: 1rem dashed red;
    color: orange !important;
  }
}

@layer components.narrow {
  div {
    background-color: skyblue;
    border: 1rem dashed blue;
    color: purple !important;
    border-radius: 50%;
  }
}

@layer components.wide {
  div {
    background-color: limegreen;
    border: 1rem dashed green;
    color: seagreen !important;
    border-radius: 20%;
  }
}

MDN Playground에서 직접 예제 코드 만져보기

여기 어떤 속성들이 사용되었고 왜 각각의 선언이 최종 적용되었는지에 대한 요약입니다:

  • background-color: 레이어에 속하지 않은 일반 스타일이 레이어에 속한 일반 스타일을 이기기 때문에, 결국 wheat 색상이 승리합니다.
  • border: 레이어 내부에서는 중첩되지 않은 스타일이 중첩된 일반 스타일을 이기기 때문에, components 바로 아래에 있는 red 색상이 승리합니다.
  • color: 중요 스타일의 경우, 레이어에 속한 스타일이 레이어에 속하지 않은 언레이어드 스타일을 이기며, 더 먼저 선언된 레이어 안의 중요 스타일이 나중에 선언된 레이어보다 강합니다. 이 예제에서 중첩 레이어가 생성된 순서는 components.narrow가 먼저고 그다음이 components.wide입니다. 따라서 components.narrow 안의 중요 스타일이 components.wide 안의 중요 스타일보다 우선순위가 높기 때문에 purple 색상이 승리합니다.
  • border-radius: 이 속성은 오직 중첩 레이어들 안에서만 설정되었으므로 일반적인 선언 순서(나중에 선언된 놈이 이긴다)에 따라 마지막인 components.wide20% 반경이 승리합니다.

요약 (Summary)

만약 여러분이 이 문서의 내용을 대부분 이해하셨다면, 정말 잘하셨습니다! 이제 여러분은 CSS 캐스케이드 레이어의 근본적인 작동 원리에 익숙해지셨습니다.

이해가 어려운 부분이 있으셨더라도 너무 걱정하지 마세요. 실무에서 부딪혀 보며 천천히 체득하는 것이 가장 확실한 방법입니다. 언제든 다시 이 문서를 찾아오셔도 좋습니다!

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

0개의 댓글