디자인 시스템 및 구성요소 라이브러리에서 맞춤 속성을 사용하는 이점
저는 Nordhealth의 선임 프런트엔드 개발자인 데이브입니다. 저는 디자인 시스템 Nord의 디자인과 개발을 담당하고 있으며, 여기에는 구성요소 라이브러리의 웹 구성요소 빌드가 포함됩니다. CSS 맞춤 속성을 사용하여 Web Components 스타일 지정과 관련된 문제를 해결한 방법과 디자인 시스템 및 구성요소 라이브러리에서 맞춤 속성을 사용할 때의 기타 이점을 공유하고 싶습니다.
웹 구성요소를 빌드하는 방법
웹 구성요소를 빌드하기 위해 상태, 범위가 지정된 스타일, 템플릿 등 많은 상용구 코드를 제공하는 라이브러리인 Lit를 사용합니다. Lit는 경량일 뿐만 아니라 네이티브 JavaScript API를 기반으로 빌드되므로 브라우저에 이미 있는 기능을 활용하는 간결한 코드 번들을 제공할 수 있습니다.
import {html, css, LitElement} from 'lit';
export class SimpleGreeting extends LitElement {
static styles = css`:host { color: blue; font-family: sans-serif; }`;
static properties = {
name: {type: String},
};
constructor() {
super();
this.name = 'there';
}
render() {
return html`Hey
${this.name}, welcome to Web Components!`;
}
}
customElements.define('simple-greeting', SimpleGreeting);
하지만 웹 구성요소의 가장 매력적인 점은 기존 JavaScript 프레임워크와 거의 모두 호환된다는 점입니다. 프레임워크가 없어도 작동합니다. 기본 JavaScript 패키지가 페이지에서 참조되면 웹 구성요소를 사용하는 것은 네이티브 HTML 요소를 사용하는 것과 매우 유사합니다. 기본 HTML 요소가 아님을 나타내는 유일한 실제 징후는 태그 내에 일관되게 사용되는 하이픈입니다. 이는 브라우저에 웹 구성요소임을 알리는 표준입니다.
섀도우 DOM 스타일 캡슐화
네이티브 HTML 요소에 Shadow DOM이 있는 것과 마찬가지로 Web Components에도 있습니다. 섀도 DOM은 요소 내의 숨겨진 노드 트리입니다. 이를 시각화하는 가장 좋은 방법은 웹 인스펙터를 열고 '섀도우 DOM 트리 표시' 옵션을 사용 설정하는 것입니다. 이렇게 한 후 인스펙터에서 기본 입력 요소를 살펴보세요. 이제 해당 입력을 열고 그 안에 있는 모든 요소를 볼 수 있습니다. Google의 웹 구성요소 중 하나를 사용해 볼 수도 있습니다. 맞춤 입력 구성요소를 검사하여 Shadow DOM을 확인해 보세요.

Shadow DOM의 장점 (또는 관점에 따라 단점) 중 하나는 스타일 캡슐화입니다. 웹 구성요소 내에서 CSS를 작성하면 스타일이 누출되어 기본 페이지나 다른 요소에 영향을 미칠 수 없습니다. 스타일은 구성요소 내에 완전히 포함됩니다. 또한 기본 페이지 또는 상위 웹 구성요소용으로 작성된 CSS는 웹 구성요소로 유출될 수 없습니다.
스타일의 이러한 캡슐화는 구성요소 라이브러리에서 유용합니다. 이를 통해 사용자가 구성요소를 사용할 때 상위 페이지에 적용된 스타일과 관계없이 의도한 대로 표시될 수 있습니다. 또한 확실하게 하기 위해 모든 웹 구성요소의 루트 또는 '호스트'에 all: unset;
를 추가합니다.
:host {
all: unset;
display: block;
box-sizing: border-box;
text-align: start;
/* ... */
}
하지만 웹 구성요소를 사용하는 사람이 특정 스타일을 변경해야 하는 정당한 이유가 있다면 어떻게 해야 할까요? 맥락상 대비가 더 필요한 텍스트 줄이 있거나 테두리가 더 두꺼워야 할 수도 있습니다. 스타일이 구성요소에 적용되지 않는 경우 스타일 옵션을 잠금 해제하려면 어떻게 해야 할까요?
이때 CSS 맞춤 속성이 필요합니다.
CSS 맞춤 속성
맞춤 속성은 이름이 매우 적절합니다. 완전히 직접 이름을 지정하고 필요한 값을 적용할 수 있는 CSS 속성이기 때문입니다. 두 개의 하이픈을 앞에 붙이기만 하면 됩니다. 커스텀 속성을 선언하면 var()
함수를 사용하여 CSS에서 값을 사용할 수 있습니다.
:root {
--n-color-accent: rgb(53, 89, 199);
/* ... */
}
.n-color-accent-text {
color: var(--n-color-accent);
}
상속의 경우 모든 맞춤 속성이 상속되며 이는 일반 CSS 속성 및 값의 일반적인 동작을 따릅니다. 상위 요소 또는 요소 자체에 적용된 맞춤 속성은 다른 속성의 값으로 사용할 수 있습니다. Google은 CSS 프레임워크를 통해 루트 요소에 적용하여 디자인 토큰에 맞춤 속성을 많이 사용합니다. 즉, 웹 구성요소, CSS 도우미 클래스 또는 토큰 목록에서 값을 가져오려는 개발자 등 페이지의 모든 요소가 이러한 토큰 값을 사용할 수 있습니다.
var()
함수를 사용하여 맞춤 속성을 상속하는 기능은 Web Components의 Shadow DOM을 관통하고 개발자가 구성요소를 스타일링할 때 더 세부적으로 제어할 수 있도록 하는 방법입니다.
Nord 웹 구성요소의 맞춤 속성
Google은 디자인 시스템의 구성요소를 개발할 때마다 CSS에 신중하게 접근합니다. 간결하면서도 유지관리성이 뛰어난 코드를 지향합니다. Google이 보유한 디자인 토큰은 루트 요소의 기본 CSS 프레임워크 내에서 맞춤 속성으로 정의됩니다.
:root {
--n-space-m: 16px;
--n-space-l: 24px;
/* ... */
--n-color-background: rgb(255, 255, 255);
--n-color-border: rgb(216, 222, 228);
/* ... */
}
그런 다음 이러한 토큰 값이 구성요소 내에서 참조됩니다. 일부 경우에는 CSS 속성에 값을 직접 적용하지만, 다른 경우에는 실제로 새로운 컨텍스트 맞춤 속성을 정의하고 그 속성에 값을 적용합니다.
:host {
--n-tab-group-padding: 0;
--n-tab-list-background: var(--n-color-background);
--n-tab-list-border: inset 0 -1px 0 0 var(--n-color-border);
/* ... */
}
.n-tab-group-list {
box-shadow: var(--n-tab-list-border);
background-color: var(--n-tab-list-background);
gap: var(--n-space-s);
/* ... */
}
또한 구성요소에만 해당하지만 토큰에는 없는 일부 값을 추상화하여 컨텍스트 기반 맞춤 속성으로 변환합니다. 구성요소와 관련된 맞춤 속성은 두 가지 주요 이점을 제공합니다. 첫째, 이 값을 구성요소 내의 여러 속성에 적용할 수 있으므로 CSS를 더 'dry'하게 사용할 수 있습니다.
.n-tab-group-list::before {
/* ... */
padding-inline-start: var(--n-tab-group-padding);
}
.n-tab-group-list::after {
/* ... */
padding-inline-end: var(--n-tab-group-padding);
}
두 번째로, 구성요소 상태와 변형 변경이 매우 깔끔해집니다. 예를 들어 호버 또는 활성 상태나 이 경우 변형을 스타일링할 때 이러한 속성을 모두 업데이트하려면 맞춤 속성만 변경하면 됩니다.
:host([padding="l"]) {
--n-tab-group-padding: var(--n-sp
ace-l);
}
하지만 가장 강력한 이점은 이러한 컨텍스트 기반 맞춤 속성을 구성요소에 정의할 때 각 구성요소에 일종의 맞춤 CSS API를 만들어 해당 구성요소의 사용자가 활용할 수 있다는 것입니다.
<nord-tab-group label="T>itl<e"
>!<-- ... --
/nord>-t<ab-gr>oup
style
nord-tab-group {
--n-tab-group-padding: var(--n-space<-xl);
>
}
/style
위의 예에서는 선택기를 통해 변경된 컨텍스트 기반 맞춤 속성이 있는 웹 구성요소를 보여줍니다. 이 전체 접근 방식의 결과는 사용자가 스타일을 유연하게 지정할 수 있으면서도 대부분의 실제 스타일을 관리할 수 있는 구성요소입니다. 또한 구성요소 개발자는 사용자가 적용한 스타일을 가로챌 수 있습니다. 이러한 속성 중 하나를 조정하거나 확장하려는 경우 사용자가 코드를 변경하지 않아도 됩니다.
이 접근 방식은 디자인 시스템 구성요소의 크리에이터인 Google뿐만 아니라 개발팀이 제품에서 이러한 구성요소를 사용할 때도 매우 강력합니다.
맞춤 속성 확장
작성 시점에는 Google 문서에 이러한 컨텍스트 맞춤 속성이 실제로 표시되지 않지만, 더 많은 개발팀이 이러한 속성을 이해하고 활용할 수 있도록 할 계획입니다. 구성요소는 npm에 매니페스트 파일과 함께 패키징됩니다. 매니페스트 파일에는 구성요소에 관한 모든 정보가 포함되어 있습니다. 그런 다음 문서 사이트가 배포될 때 매니페스트 파일을 데이터로 사용합니다. 이는 Eleventy와 전역 데이터 기능을 사용하여 실행됩니다. 이 매니페스트 데이터 파일에 이러한 컨텍스트 맞춤 속성을 포함할 계획입니다.
개선하고자 하는 또 다른 영역은 이러한 컨텍스트 맞춤 속성이 값을 상속하는 방식입니다. 현재 예를 들어 두 개의 구분선 구성요소의 색상을 조정하려면 선택기로 두 구성요소를 모두 구체적으로 타겟팅하거나 스타일 속성이 있는 요소에 맞춤 속성을 직접 적용해야 합니다. 이 방법도 괜찮아 보이지만 개발자가 포함 요소나 루트 수준에서 이러한 스타일을 정의할 수 있다면 더 유용할 것입니다.
<nord-divider></nord-divider>
<section>
<nord-divider></nord-divider>
<!-- ... -->
</section>
<style>
nord-divider {
--n-divider-color: var(--n-color-status-danger);
}
section {
padding: var(--n-space-s);
background: var(--n-color-surface-raised);
}
section nord-divider {
--n-divider-color: var(--n-color-status-success);
}
</style>
구성요소에 직접 맞춤 속성 값을 설정해야 하는 이유는 구성요소 호스트 선택기를 통해 동일한 요소에 정의하기 때문입니다. 구성요소에서 직접 사용하는 전역 디자인 토큰은 이 문제의 영향을 받지 않고 바로 전달되며 상위 요소에서 가로챌 수도 있습니다. 두 가지의 장점을 모두 누릴 수 있는 방법이 있을까요?
비공개 및 공개 맞춤 속성
비공개 맞춤 속성은 Lea Verou가 만든 것으로, 컴포넌트 자체의 컨텍스트가 지정된 '비공개' 맞춤 속성이지만 대체가 있는 '공개' 맞춤 속성으로 설정됩니다.
:host {
--_n-divider-color: var(--n-divider-color, var(--n-color-border));
--_n-divider-size: var(--n-divider-size, 1px);
}
.n-divider {
border-block-start: solid var(--_n-divider-size) var(--_n-divider-color);
/* ... */
}
이런 방식으로 컨텍스트 맞춤 속성을 정의하면 전역 토큰 값을 상속하고 구성요소 코드 전체에서 값을 재사용하는 등 이전과 동일한 작업을 모두 수행할 수 있습니다. 하지만 구성요소는 자체 또는 상위 요소에서 해당 속성의 새로운 정의도 정상적으로 상속합니다.
<nord-divider></nord-divider>
<section>
<nord-divider></nord-divider>
<!-- ... -->
</section>
<style>
nord-divider {
--n-divider-color: var(--n-color-status-danger);
}
section {
padding: var(--n-space-s);
background: var(--n-color-surface-raised);
--n-divider-color: var(--n-color-status-success);
}
</style>
이 방법이 진정한 '비공개'는 아니라고 주장할 수도 있지만, Google에서는 이 방법이 우려했던 문제를 해결하는 상당히 우아한 방법이라고 생각합니다. 기회가 있을 때 구성요소에서 이 문제를 해결하여 개발팀이 구성요소 사용을 더 효과적으로 제어하면서도 마련된 가이드라인의 이점을 누릴 수 있도록 할 예정입니다.
CSS 맞춤 속성과 함께 Web Components를 사용하는 방법에 관한 이 통계가 유용했기를 바랍니다. 어떤 방법이 가장 마음에 드는지 알려주세요. 이러한 방법을 업무에 사용하기로 결정했다면 Twitter @DavidDarnes에서 저를 찾으실 수 있습니다. Twitter에서 Nordhealth(@NordhealthHQ)를 팔로우할 수도 있습니다. 이 디자인 시스템을 통합하고 이 도움말에 언급된 기능을 실행하기 위해 열심히 노력한 나머지 팀원(@Viljamis, @WickyNilliams, @eric_habich)도 팔로우하세요.
Dan Cristian Pădureț의 히어로 이미지