Cascade Layers

Chrome99+, Firefox97+, Safari Technology Preview 및 Safari iOS에 도입된 CSS의 새로운 스펙 Cascade Layers에 대해 정리했습니다.

이해

a. CSS Cascade

CSS Cascade는 CSS의 가장 강력한 강점으로, CSS가 요소에 적용하려는 스타일의 우선순위를 정하는 데 사용하는 알고리즘입니다. CSS Cascade는 어떤 스타일을 적용할지 결정하기 위해 몇 가지 기준을 고려하는데, 그 기준은 다음과 같습니다.

  1. Origin and Importance
  2. Context
  3. Style Attribute
  4. Specificity(명시도)
  5. Order of Apperance(작성 순서)

CSS Cascade 기준은 높은 순위에서 낮은 순위로 순위가 매겨지며, 스타일이 각 기준에 따라 비교되었을 때 동등한 기준일 경우, 다음 기준으로 넘어가 비교합니다.

개발자는 CSS를 작성할 때 명시도(Specificity) 와 작성 순서(Order of Appearance) 를 신중하게 고려해야합니다. 만약 적절한 코드 구성 계획이 없으면 아래와 같이 Cascade가 불리하게 작용할 수 있습니다.

  • 선택자를 과다하게 사용할 경우, 코드의 일부 속성을 재정의할 때 더 무거운 선택자의 사용이나 !important의 사용으로 이어지며 이는 나중에 더 큰 문제를 야기할 수 있습니다.
  • 선택자를 적게 사용할 경우, 나중에 사용하는 명령문으로 너무 쉽게 덮어쓸 수 있습니다.

 
개발자가 이러한 딜레마를 쉽게 제어할 수 있도록 CSS Cascade 알고리즘에 Cascade Layers라는 새로운 개념이 도입됩니다.

 
 더 이상 사용되지 않는 layer태그와 이름이 비슷하여 혼동하기 쉽지만, 다른 개념입니다.

b. Cascade Layers

Cascade Layers는 CSS Cascade기준에서 Layers라는 새로운 기준을 추가합니다.

  1. Origin and Importance
  2. Context
  3. Style Attribute
  4. Layers
  5. Specificity(명시도)
  6. Order of Apperance(작성 순서)

Layers의 진정한 힘은 명시도(Specificity)와 작성 순서(Order Of Appearance)보다 더 높은 기준에 있다는 점에서 나옵니다.

Layers는 보다 상위 등급의 기준이므로, Layers 기준 내에서 스타일의 우선권이 정해지면 Cascade는 해당 스타일에 대한 명시도와 작성 순서를 더이상 체크하지 않습니다.

그 때문에 우리는 다른 Layers에서 사용되는 CSS의 명시도와 작성 순서에 대해 걱정할 필요가 없습니다.

또한 개발자는 Cascade Layers를 사용하여 CSS를 여러 레이어로 분할할 수 있습니다. 그리고 적용할 레이어 순서를 정할 수 있습니다. 그렇기 때문에 이 Layers가 CSS를 로드하는 순서에 대해 전혀 걱정할 필요가 없습니다.

/* Cascade Layers 선언 예시 코드 */
/* 레이어 순서 정의 - 1. reset, 2. base, 3. theme */
@layer reset, base, theme;

 /* 첫번째 레이어 “reset” */
@layer reset { … }

/* 2번째 레이어 “base” */
@layer base { … }

/* 3번째 레이어 “theme” */
@layer theme { … }

구문

a. Cascade Layers 선언

기본 구문

@layer [ <layer-name># | <layer-name>?  {
    <stylesheet>
} ]

@layer블록에서 즉시 할당된 스타일 사용

/* reset이름의 layer 생성, 아래의 스타일 적용 */
@layer reset {
  * { 
    margin: 0;
    padding: 0;
  }
}

@layer에서 스타일을 작성하지 않고 선언

/* reset이름의 layer 생성 */
@layer reset;

/* base, theme, util 레이어 생성 */
@layer base, theme, util;

@import 및 layer키워드 혹은 layer()함수를 사용하여 선언

/* reset.css파일 전체가 이름없는 layer로 생성 */
@import url(reset.css) layer;

/* base.css파일 전체가 base 이름의 layer에 스타일이 추가 */
@import url(base.css) layer(base);

b. Layers 순서 관리

Cascade Layers는 선언된 순서에 따라 정렬됩니다.

/* Layers 순서 예시 */
@layer reset { /* 첫번째 레이어 “reset” */
  * {
    margin: 0;
    padding: 0;
  }
}

/* 2번째 레이어 “base” */
@layer base { … }

/* 3번째 레이어 “theme” */
@layer theme { … }

/* 4번째 레이어 “util” */
@layer util { … }

순서대로 resetbasethemeutil 레이어가 생성됩니다. 레이어가 늦게 생성될수록 스타일 우선권을 가지게 됩니다.
 

c. Layer 이름

레이어의 이름은 레이어를 구별할 수 있는 고유한 식별자이지만, 이름을 정하는 것은 선택사항입니다.
이름을 지정하지 않은 레이어는 익명 레이어라고 합니다. 익명 레이어는 식별되지 않기 때문에 다른 위치에서 참조할 수 없습니다.

/* reset.css 파일 전체가 익명 레이어1 로 생성 */
@import url(reset.css) layer;

/* base.css 파일 전체가 익명 레이어2 로 생성 */
@import url(base.css) layer;

/* 아래의 스타일을 가지는 익명 레이어3 생성 */
@layer {
  /* the next layer */
}

/* 아래의 스타일을 가지는 익명 레이어4 생성 */
@layer {
  /* and another */
}

레이어 이름 재사용

이미 선언한 레이어 이름을 재사용할 경우, 기존에 존재하던 레이어에 스타일이 덧붙여 추가됩니다. 레이어의 적용 순서는 변하지 않습니다.

 /* 첫번째 레이어 “reset” */
@layer reset { … }

/* 2번째 레이어 “base” */
@layer base { … }

/* 3번째 레이어 “theme” */
@layer theme { … }

/* 2번째 레이어인 “base”에 스타일 추가 */
@layer base {
    * {
        color : red;
    }
}

 
그렇기 때문에 레이어 순서를 계속해서 제어하려면 한 줄 구문을 사용하여 모든 레이어를 미리 선언하여 순서를 결정하고, 이후 스타일을 추가하는 것이 권장됩니다.

/* 선언 순서에 따라 1. reset, 2.base, 3. theme, 4.util */
@layer reset, base, theme, util;

 /* 첫번째 레이어 “reset” */
@layer reset { … }

/* 네번째 레이어 “util” */
@layer util { … }

/* 두번째 레이어 “base” */
@layer base { … }
~

d. revert-layer

속성의 값에 revert-layer를 사용할 수 있습니다. revert-layer는 이전 계층에서 정의한 값으로 롤백하기 위한 값입니다. 즉, 현재 레이어의 값이 지정되지 않은 것처럼 계산합니다.

@layer default {
  h3 { color: rebeccapurple; }
}

@layer theme {
  h3 { color: maroon; }
  /* h3태그에 no-theme클래스가 추가되어있으면, revert-layer값으로 인해 color가 이전 레이어인 default의 스타일인 rebeccapurple가 적용됩니다. */
  .no-theme { color: revert-layer; }
}

활용

a. Layers 중첩

@layer명령문을 중첩해서 사용할 수 있습니다. 중첩된 레이어 내부에서 레이어 우선순위는 기존 레이어와 동일하게 작용합니다.

/* 중첩 레이어 예시 */
@layer base { /* 첫번째 레이어 */
  p { max-width: 70px; }
}

@layer framework { /* 두번째 레이어 */
  @layer base { /* 두번째 레이어의 첫번째 레이어 */
    p { max-width: 100px; }
  }

  @layer theme { /* 두번째 레이어의 두번째 레이어 */
    p { color: #222; }
  }
}

예시 레이어의 순서는 다음과 같습니다.

  1. base
  2. framework
    1. base
    2. theme

만약 레이어 안에 포함된 레이어를 참조하려면 마침표 .를 이용하여 계층 구조를 참조할 수 있습니다.

@layer framework {
  @layer theme {
    p { color: #222; }
  }
}

/* framework layer안의 theme layer를 참조하고 속성을 추가 */
@layer framework.theme {
  blockquote { color: rebeccapurple; }
}

b. Unlayered Style

@layer를 사용하지 않은 스타일은 Unlayered Styles라고 하며 레이어의 순서 마지막에 위치합니다.
그렇기 때문에 레이어가 없는 스타일은 항상 레이어가 있는 스타일보다 우선권을 가집니다.

/* 첫번째 layer reset */
@import(reset.css) layer(reset);

/* layer 없는 스타일 - Unlayered Styles */
h1 { color: hotpink; }

 /* 두번째 layer base */
@layer base {
  h1 { font-family: 돋움; }
}

/* layer 없는 스타일 - Unlayered Styles */
h1 { font-family: 굴림; }

/* 3rd layer */
@layer theme { 
  body h1 { color: rebeccapurple; }
}

예시의 레이어 순서는 다음과 같습니다.

  1. reset
  2. base
  3. theme
  4. (Unlayered Styles)

따라서 모든 h1태그들의 color속성은 hotpink색이되고, font-family는 굴림이 됩니다.

c. Layers 미디어쿼리

미디어쿼리 안에 @layer를 사용할 수 있습니다. 하지만 미디어쿼리가 조건이 충족되지 않으면 레이어 순서에 고려되지 않습니다. 조건에 충족될 때 레이어 순서가 다시 계산됩니다.

@media (min-width: 30px) {
  @layer layout {
    .title { font-size: x-large; }
  }
}

@media (prefers-color-scheme: dark) {
  @layer theme {
    .title { color: white; }
  }
}

해당 예시에서 첫 번째 미디어쿼리가 일치하는 경우 layout이 레이어 순서에 먼저 표기되고, 두 번째 미디어쿼리도 일치할 경우 그다음 순서에 theme이 추가됩니다. 쿼리문이 충족되지 않으면 레이어 순서에서 사라집니다.

주의사항

a. Cascade Layers에서 !important사용

Cascade의 기준 중 첫 번째 기준인 Origin기준을 평가할 때, Cascade는 여러 Origin을 다음과 같이 정렬합니다.

  1. Transition
  2. Important User-Agent
  3. Important User
  4. Important Author
  5. Animations
  6. Author
  7. User
  8. User-Agent

선언이 !important로 표시되면 Cascade에서 가중치가 증가하고 우선순위가 반전됩니다.
이 규칙은 Cascade Layers의 선언에도 똑같이 적용이 됩니다.

/* 예시 레이어 reset, base, theme, utilities */
@layer reset, base, theme, utilities;

위 예시 레이어들의 일반 선언은 모두 Normal Author Origin으로 이동하며 다음과 같이 정렬됩니다.

  1. reset
  2. base
  3. theme
  4. utilities

해당 레이어들 내부의 important선언은 모두 Important Author Origin으로 이동하며 순위가 반전됩니다.
따라서 해당 레이어들의 우선순위는 아래와 같습니다.

  1. important Author Origin
    1. important utilities
    2. important theme
    3. important base
    4. important reset
  2. Animations
  3. Normal Author Origin
    1. reset
    2. base
    3. theme
    4. utilities

b. 인터리빙 불가

@layer CSS parser가 이전 규칙을 따르는 것을 보는 순간, 이후의 @import규칙은 적용되지 않습니다. 즉, @import@namespace규칙과 @layer는 서로 인터리빙 할 수 없습니다.

@layer default;
@import url(theme.css) layer(theme);

/* @import 뒤에 @layer규칙이 나왔으므로 @import규칙은 여기서 종료됩니다. */
@layer components; 

/* 서로 인터리빙이 되지 않기 때문에 이후에 나오는 모든 @import규칙들은 무시됩니다. */
@import url(default.css) layer(default); 
@layer default {
  audio[controls] {
    display: block;
  }
}

따라서 이를 잘 사용하려면 정해진 형식을 갖추고 그 형식에 맞게 코드를 작성하는 것이 권장됩니다.

  1. @layer문 at-rules를 사용하여 사전에 레이어 순서를 결정
  2. @import 를 그룹화
  3. @layer block at-rules를 사용하여 스타일 추가
/* 1. 레이어의 순서를 결정 */
@layer default, theme, components;

/* 2. @import 그룹화 */
@import url(theme.css) layer(theme);
@import url(default.css) layer(default);

/* 3. @layer 스타일 추가 */
@layer default {
  audio[controls] {
    display: block;
  }
}

@layer theme { … }
~

브라우저 지원

업데이트 사항

a. Unlayered Style issue#6284 

이전 버전의 Cascade Layer에서는 Unlayered Style이 가장 우선권이 약한 스타일로 지정이 되었습니다. 하지만 레이어의 주요 사용 사례는 재설정 혹은 리팩토링 및 프레임워크 테마입니다. Layered style 순위 위에 Unlayered style이 있을 경우 레이어를 고려해 규칙 순서가 우선 순위와 일치하도록 파일을 다시 작성해야하기 때문에 가장 우선권이 강한 스타일로 바뀌게 되었습니다.

b. @layer문법 변경 issue#5681 

/* @layer <name>? url(<contents>) */
@layer box url(box.css);
@layer url(reset.css);

이전 버전의 Cascade Layers에서는 @layer 와 url()를 사용해서 레이어를 선언할 수 있었으나, 문법문제로 인하여 @layer에서는 url()을 사용하지 못하고 @import에서만 사용가능하게 변경되었습니다.

참고자료


3개의 댓글

김보미 · 2022년 5월 25일 10:09 오전

정리가 잘 된 좋은 글 감사합니다! 덕분에 이해가 쏙쏙 잘 되었습니다 🙂

두루미 · 2022년 5월 26일 9:52 오전

좋은 글 고맙습니다~

이상호 · 2022년 5월 28일 11:12 오후

좋은 글 감사합니다~

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다.