공유 디자인 토큰은 페이지 CSS에 두고, 컴포넌트별 변수는 스타일링 API로 공개한 다음, 페이지가 소유하는 간단한 매핑으로 연결하세요. 이 튜토리얼에서는 이러한 책임을 분리하는 로컬 Mac 데모를 만듭니다.
페이지에 속하는 것과 컴포넌트에 속하는 것은 무엇인가요?
Mac에서 직접 작성한 웹페이지를 편집하는 사람의 구체적인 작업은 페이지의 토큰 이름에 구현이 의존하지 않도록 재사용 가능한 컴포넌트에 테마를 적용하는 것입니다. 아래 예제에서는 추상적인 도형과 짧은 레이블이 포함된 가상의 orbit-tile 컴포넌트를 사용합니다.
디자인 토큰과 공개 컴포넌트 변수는 모두 CSS 사용자 지정 속성일 수 있습니다. 둘의 차이는 문서화하는 계약에서 비롯됩니다. 일반적인 미등록 사용자 지정 속성은 캐스케이드에 참여하고 상속됩니다. 이를 :root에 선언하면 상속을 통해 문서 전체에서 사용할 수 있습니다. 속성을 디자인 토큰으로 만드는 특별한 CSS 키워드는 없습니다. CSS 사용자 지정 속성(변수) 사용하기
다음 소유권 표를 참고해 선언을 어디에 둘지 결정하세요. 이름은 설명을 위한 것으로 이 예제에서만 사용됩니다.
| 계층 | 예시 | 책임 |
|---|---|---|
| 페이지 디자인 토큰 | --scene-accent, --scene-rounding |
직접 작성한 페이지에서 공유하는 시각적 선택 |
| 페이지 통합 규칙 | orbit-tile을 대상으로 하는 규칙 |
페이지의 선택을 컴포넌트 API로 변환 |
| 공개 컴포넌트 변수 | --orbit-tile-fill, --orbit-tile-radius |
사용자가 이용할 수 있도록 문서화된 제어 항목 |
| 컴포넌트 구현 | .shape, 레이아웃 규칙, 리터럴 기본값 |
페이지 토큰을 요구하지 않고 컴포넌트 렌더링 |
유용한 기준은 이식성입니다. 다른 페이지에서 같은 컴포넌트를 변경 없이 사용하면서 토큰 이름을 다르게 지정할 수 있을까요? 페이지가 소유하는 매핑을 사용하면 가능합니다. 새 페이지는 자체 토큰을 동일한 공개 컴포넌트 변수에 매핑하면 됩니다.
Shadow DOM 경계를 넘어 스타일링 계약은 어떻게 전달되나요?
Shadow DOM은 선택자 매칭과 상속을 분리합니다. 페이지 선택자는 일반적으로 섀도 경계 너머의 내부 도형을 선택할 수 없습니다. 하지만 상속된 속성 값은 호스트를 거쳐 내부로 전달될 수 있습니다. CSS Shadow 명세는 이 관계에 사용되는 평탄화된 요소 트리를 설명합니다. 선택자 트리가 분리되어도 상속은 없어지지 않습니다. CSS Shadow Module Level 1
컴포넌트 내부에서는 공개 변수를 설정하지 말고, 변수를 사용하는 곳에 기본값을 제공하세요. 예를 들어 background-color: var(--orbit-tile-fill, #667085)는 소비자가 상속한 값을 받아들이면서 독립 실행 시 기본값도 제공합니다. 대신 :host에 --orbit-tile-fill: #667085를 선언하면 호스트가 자체 값을 가지게 되어 조상으로부터 상속한 값이 차단됩니다. 소비자가 호스트를 직접 대상으로 지정할 수는 있습니다. 웹 컴포넌트 스타일링
이 예제에서는 공개 제어 항목을 두 개만 문서화합니다.
| 공개 변수 | 이 예제에서 허용하는 값 | 값이 없을 때의 기본값 | 효과 |
|---|---|---|---|
--orbit-tile-fill |
유효한 CSS 색상 | #667085 |
도형 배경색 |
--orbit-tile-radius |
음수가 아닌 길이 또는 백분율 하나 | 1rem |
도형 모서리 반경 |
이 허용 값은 API 계약입니다. 기본 구현은 이 값을 등록하거나 자동으로 검증하지 않습니다. 내부 클래스 이름은 구현 세부 사항이므로, 페이지 작성자는 .shape에 의존하지 말고 공개 변수를 사용자 지정해야 합니다.
Mac에서 완전한 로컬 예제 만들기
CSS 사용자 지정 속성, Shadow DOM, 독립형 사용자 지정 요소를 지원하는 브라우저와 일반 텍스트 편집기를 사용하세요. 이 예제는 HTMLElement를 확장하는 독립형 요소를 사용합니다. MDN은 이를 Safari가 지원할 계획이 없는 맞춤형 내장 요소와 구분합니다. 사용자 지정 요소 사용하기
TextEdit에서 File > New를 선택한 다음 Format > Make Plain Text를 선택하세요. 아래의 전체 문서를 붙여 넣고 token-demo.html로 저장하세요. 메시지가 표시되면 Use .html을 선택하세요. 다시 열어 편집할 때는 열기 대화상자에서 Ignore rich text commands를 선택하거나 설정에서 Display HTML files as HTML code instead of formatted text를 활성화하세요. Mac의 TextEdit에서 HTML 문서 작업하기
문서의 첫 번째 스타일시트는 토큰, 통합 규칙, 페이지 레이아웃을 소유합니다. 컴포넌트는 섀도 루트 안에 별도의 스타일시트를 만듭니다. HTML은 CSS를 포함하는 방법으로 style 요소를 정의합니다. 모든 소스를 한 파일에 넣으면 예제의 리소스 구성이 명확해집니다. HTML 표준: style 요소
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Orbit tile token demo</title>
<style>
/* Page-owned shared tokens */
:root {
--scene-accent: #4058b8;
--scene-rounding: 12px;
}
/* Page-owned integration mapping */
orbit-tile {
--orbit-tile-fill: var(--scene-accent);
--orbit-tile-radius: var(--scene-rounding);
}
/* A local page theme */
.alternate {
--scene-accent: #276f64;
--scene-rounding: 24px;
}
/* An intentional override for one component instance */
orbit-tile.special {
--orbit-tile-fill: #a65b20;
--orbit-tile-radius: 50%;
}
body {
max-width: 36rem;
margin: 3rem auto;
padding: 0 1rem;
font: 1rem/1.5 system-ui, sans-serif;
}
main { display: grid; gap: 1rem; }
</style>
</head>
<body>
<main>
<orbit-tile>Sample A</orbit-tile>
<section class="alternate" aria-label="Alternate samples">
<orbit-tile>Sample B</orbit-tile>
</section>
<orbit-tile class="special">Sample C</orbit-tile>
</main>
<script>
class OrbitTile extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
:host { display: block; }
.tile {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: #f2f4f6;
color: #202630;
}
.shape {
display: block;
width: 4rem;
height: 4rem;
flex: 0 0 4rem;
background-color: var(--orbit-tile-fill, #667085);
border-radius: var(--orbit-tile-radius, 1rem);
}
`;
const tile = document.createElement('div');
tile.className = 'tile';
const shape = document.createElement('span');
shape.className = 'shape';
shape.setAttribute('aria-hidden', 'true');
tile.append(shape, document.createElement('slot'));
root.append(style, tile);
}
}
customElements.define('orbit-tile', OrbitTile);
</script>
</body>
</html>
저장한 파일을 Safari, Chrome 또는 Firefox에서 여세요. 이 예제에는 외부 스크립트, 글꼴, 이미지 또는 스타일시트가 없습니다. JavaScript는 구문 검사를 통과했습니다. 다음의 시각적 결과는 브라우저에서 실행한 결과를 보고하는 것이 아니라 선언을 바탕으로 예상한 결과입니다.
| 인스턴스 | 예상 도형 | 값의 전달 경로 |
|---|---|---|
Sample A |
파란색, 모서리 반경 12px |
루트 토큰 → 호스트 매핑 → 컴포넌트 |
Sample B |
초록색, 모서리 반경 24px |
섹션 토큰 → 호스트 매핑 → 컴포넌트 |
Sample C |
주황색 원 | 인스턴스 재정의 → 컴포넌트 |
컴포넌트의 JavaScript에는 --scene-* 참조가 없다는 점에 주목하세요. 컴포넌트의 스타일시트는 공개된 --orbit-tile-* 이름만 사용합니다. 페이지는 컴포넌트를 변경하지 않고도 테마 용어 체계를 재구성할 수 있습니다.
토큰과 컴포넌트의 매핑은 어디에서 해야 하나요?
의도한 테마를 사용할 수 있는 곳에서 값을 매핑하세요. 이 데모에서는 orbit-tile 규칙이 각 호스트에 적용됩니다. 이렇게 하면 두 번째 호스트가 섹션의 초록색 값을 상속한 뒤 --scene-accent를 해석할 수 있습니다.
해석 시점을 고려하지 않고 매핑을 :root로 옮기지 마세요. --orbit-tile-fill: var(--scene-accent)가 그곳에서 계산되면 자손은 계산된 값을 상속합니다. 이후 섹션에서 --scene-accent만 변경해도 상속된 별칭은 다시 계산되지 않습니다. 사용자 지정 속성은 계산된 값을 상속하기 전에 var() 참조를 해석합니다. 캐스케이드 변수용 CSS 사용자 지정 속성 모듈 레벨 1
실제 판단 기준은 범위입니다. 각 인스턴스가 주변 페이지 토큰을 따르게 하려면 호스트 수준에서 매핑하세요. 한 영역 전체가 의도적으로 하나의 공개 컴포넌트 값을 공유한다면 그 영역에 공개 변수를 선언해도 됩니다. 단, 해당 변수의 호스트 매핑은 제거해야 상속된 값을 대체하지 않습니다.
내부에서 반복해서 사용할 때는 구현 별칭을 두어 대체값 선언을 한곳에 모을 수 있습니다. --_orbit-tile-fill 같은 이름으로 내부 규칙임을 나타낼 수 있지만, 밑줄이 강제되는 비공개성을 만들어 주지는 않습니다. 각 값을 한 번만 사용하는 완전한 예제에서는 값을 직접 사용하는 편이 이해하기 쉽습니다. 웹 컴포넌트 스타일링
재정의가 적용되지 않을 때 어떻게 진단하나요?
세 인스턴스를 작은 계약 테스트로 사용하세요. 한 번에 선언 하나씩 변경하고 저장한 뒤 새로고침하세요. 이 확인 방법으로 리소스 누락, 상속 문제, 유효하지 않은 값을 구분할 수 있습니다.
- 루트 강조색을
#7251a1로 변경하세요.Sample A만 이 색을 적용할 것으로 예상됩니다. 섹션과 인스턴스에는 각각 자체 설정이 있습니다. - 섹션 강조색을
#32758a로 변경하세요. 컴포넌트 코드를 수정하지 않아도Sample B가 변경될 것으로 예상됩니다. - 통합 매핑 전체를 제거하세요.
Sample A와Sample B는 컴포넌트의 회색 채우기와1rem반경을 사용할 것으로 예상됩니다.Sample C에는 명시적인 공개 재정의가 유지됩니다. - 다른 실험을 계속하기 전에 매핑을 복원하세요.
도형이 모두 사라지면 먼저 문서가 HTML로 저장되었는지, 스크립트가 온전한지 확인하세요. 레이블은 나타나지만 도형에 스타일이 적용되지 않으면 컴포넌트 스타일시트를 살펴보세요. 문서 스타일시트만으로는 일반 선택자를 통해 섀도 자손에 스타일을 적용할 수 없습니다. 구현은 섀도 트리에 내부 스타일을 연결해야 합니다. MDN은 이를 위한 임베디드 스타일과 구성된 스타일시트를 모두 설명합니다. Shadow DOM 사용하기
조상에서 적용한 재정의만 실패한다면 호스트에 공개 변수를 직접 선언했는지 살펴보세요. 이 예제에서는 통합 매핑이 해당 선언입니다. 매핑에 입력되는 페이지 토큰을 변경하거나, 인스턴스에서 공개 변수를 재정의하거나, 조상 수준의 공개 테마 적용을 계약으로 삼으려는 경우 매핑을 제거하세요.
--orbit-tile-fill: 12px를 지정한 뒤 도형이 투명해진다면 대체값으로 복구되지 않습니다. 변수는 존재하지만 대입된 값은 background-color에 사용할 수 없습니다. 해당 선언은 계산 시점에 유효하지 않은 값이 되며, 이전 배경 선언으로 돌아가지 않습니다. 값을 제거하거나 유효한 색상을 지정하세요. 캐스케이드 변수용 CSS 사용자 지정 속성 모듈 레벨 1
공개 변수에 @property를 사용해야 하나요?
이 계약에 등록은 필수가 아닙니다. @property는 구문 제약, 초기값, 명시적인 상속 동작을 추가합니다. MDN은 이를 Baseline 2024로 표시하며 2024년 7월부터 현재 브라우저 전반에서 사용할 수 있지만, 구형 브라우저에서는 지원되지 않을 수 있다고 경고합니다. 완전한 예제는 등록을 필수 조건으로 삼지 않습니다. @property CSS at-rule
등록을 도입한다면 문서 수준에서 조율하세요. Chrome 구현 문서는 섀도 루트 내부의 등록은 무시된다고 보고합니다. 해당 보고서는 2024년 8월에 작성되었으므로, 새로운 브라우저 테스트의 근거가 아니라 문서화된 상호 운용성의 경계로 보아야 합니다. 여기서 설명하는 지원 경로는 문서 범위에서 등록하고 컴포넌트가 경계를 넘어 해당 속성을 사용하는 것입니다. 작성자가 정의한 CSS 이름과 Shadow DOM: 명세와 실제 적용
등록은 동작도 바꿉니다. inherits: false로 설정하면 이 예제의 호스트에서 도형으로 이어지는 상속이 깨집니다. 등록된 초기 색상이 있으면 속성을 설정하지 않았을 때 그 색상이 사용되므로, 값을 사용하는 var() 대체값은 더 이상 누락된 값의 기본값이 아닙니다. 이 설계에서는 inherits: true를 유지하고 기본값을 어느 메커니즘이 책임질지 명확히 정하세요. @property CSS at-rule
완성된 계약에서는 페이지 작성자가 각 값의 경로를 설명할 수 있어야 합니다. 즉, 공유 페이지 선택, 문서화된 컴포넌트 제어 항목으로의 매핑, 컴포넌트가 소유하는 대체값입니다. 매핑은 의도한 테마 범위 가까이에 두고, 상속되는 공개 입력은 내부에 설정하지 말고, API를 수정할 때는 세 인스턴스 예제를 유지하세요.
출처
- CSS 사용자 지정 속성(변수) 사용하기 — MDN Web Docs.
- CSS Shadow Module Level 1 — W3C CSS Working Group.
- 웹 컴포넌트 스타일링 — Chrome for Developers.
- 사용자 지정 요소 사용하기 — MDN Web Docs.
- Mac의 TextEdit에서 HTML 문서 작업하기 — Apple Support.
- HTML 표준 — WHATWG.
- 캐스케이드 변수용 CSS 사용자 지정 속성 모듈 레벨 1 — W3C.
- Shadow DOM 사용하기 — MDN Web Docs.
- @property CSS at-rule — MDN Web Docs.
- 작성자가 정의한 CSS 이름과 Shadow DOM: 명세와 실제 적용 — Chrome for Developers.