디자인 시스템 및 구성요소 라이브러리에서 맞춤 속성을 사용할 때의 이점
저는 Nordhealth의 수석 프런트엔드 개발자인 데이브입니다. 저는 디자인 시스템 Nord의 설계 및 개발을 담당하고 있으며, 여기에는 구성요소 라이브러리를 위한 웹 구성요소 빌드가 포함됩니다. CSS 맞춤 속성을 사용하여 웹 구성요소의 스타일 지정과 관련된 문제를 해결한 방법과 디자인 시스템 및 구성요소 라이브러리에서 맞춤 속성을 사용할 때의 다른 이점을 공유하고 싶었습니다.
웹 구성요소를 빌드하는 방법
웹 구성요소를 빌드하기 위해 상태, 범위가 지정된 스타일, 템플릿 등 다양한 상용구 코드를 제공하는 라이브러리인 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이 있는 것과 마찬가지로 웹 구성요소에도 Shadow DOM이 있습니다. Shadow DOM은 요소 내에 있는 숨겨진 노드 트리입니다. 이를 시각화하는 가장 좋은 방법은 웹 검사기를 열고 'Shadow DOM 트리 표시' 옵션을 사용 설정하는 것입니다. 이 작업을 완료한 후 검사기에서 네이티브 입력 요소를 살펴보세요. 이제 해당 입력을 열고 그 안에 있는 모든 요소를 볼 수 있습니다. Google의 웹 구성요소 중 하나를 사용하여 이를 시도해 볼 수도 있습니다. 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() 함수를 사용하여 맞춤 속성을 상속하는 이 기능을 통해 웹 구성요소의 Shadow DOM을 관통하고 개발자가 구성요소의 스타일을 지정할 때 더 세밀하게 제어할 수 있습니다.
Nord 웹 구성요소의 맞춤 속성
디자인 시스템의 구성요소를 개발할 때마다 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-space-l);
}
하지만 가장 강력한 이점은 구성요소에 이러한 문맥 맞춤 속성을 정의하면 각 구성요소에 일종의 맞춤 CSS API를 만들 수 있으며, 이 API는 해당 구성요소의 사용자가 활용할 수 있다는 점입니다.
<nord-tab-group label="Title">
<!-- ... -->
</nord-tab-group>
<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>
이 방법이 진정으로 '비공개'가 아니라고 주장할 수도 있지만, 우려했던 문제에 대한 꽤 우아한 해결책이라고 생각합니다. 기회가 되면 구성요소에서 이 문제를 해결하여 개발팀이 구성요소 사용을 더 세밀하게 제어하면서도 기존의 안전장치를 활용할 수 있도록 할 예정입니다.
CSS 맞춤 속성과 함께 웹 구성요소를 사용하는 방법에 관한 이 유용한 정보가 도움이 되었기를 바랍니다. 의견을 알려주세요. 이러한 방법 중 하나를 사용하기로 결정했다면 Twitter(@DavidDarnes)에서 저를 찾으실 수 있습니다. 트위터에서 Nordhealth(@NordhealthHQ)를 찾을 수 있으며, 이 디자인 시스템을 함께 만들고 이 문서에 언급된 기능을 구현하기 위해 열심히 노력한 나머지 팀원들도 찾을 수 있습니다. @Viljamis, @WickyNilliams, @eric_habich.