Theme layer / 04
정체성은 바꾸고,
의미는 지킵니다.
@axe/ui의 색을 브랜드층과 구조층으로 나눕니다. 소비자는
theme-template.css 사본에서 정체성만 교체하고, 대비와 상태 의미를 지는 골격은 공유합니다.(a)
두 층 — 브랜드 vs 구조
무엇을 바꾸면 재스킨이고, 무엇을 바꾸면 시스템이 깨지는가.
브랜드층 · 교체 대상
--brand-* · --accent* · --cta* — 브랜드와 기능의 명시적 교체 지점. Logo는 브랜드 토큰, 링크·focus는 accent, Primary 행동은 CTA가 각각 맡습니다.구조층 · 불변 골격
gray ramp ·
--bg-* / --text-* · 보더 · 상태색(success/warning/danger/info) · 그림자. 대비 보장과 의미(빨강=위험)를 지고 있어 전 테넌트 공통 — 템플릿에 없다.(b)
브랜드층 토큰
템플릿에 담기는 정확한 목록. 값은 tokens/colors.css 를 빌드타임 파싱 — 이 표는 항상 라이브 값.
| 토큰 | 층 | 재스킨 시 | 현재 값 | 용도 |
|---|---|---|---|---|
--brand-claret | 브랜드 팔레트 | 뿌리 교체 | light-dark(#821f3b, #d2839a) | AXE 브랜드 와인 |
--brand-claret-print | 브랜드 팔레트 | 직접 교체(raw) | #821f3b | 흰 종이 문서용 브랜드 와인 |
--accent | 액센트 | 직접 교체 | var(--palette-blue) | 기능 강조 — 링크 · focus · Badge · selection |
--accent-hover | 액센트 | 직접 교체(raw) | light-dark(#0c3c5b, #82bce0) | accent hover/active |
--accent-fg | 액센트 | 직접 교체(raw) | light-dark(#ffffff, #181818) | accent 위 전경(대비 파트너) |
--accent-soft | 액센트 | 직접 교체(raw) | light-dark(rgb(16 74 112 / 0.10), rgb(90 165 210 / 0.14)) | accent soft 배경 |
--cta | Primary 행동 | 선택(중립 기본) | var(--gray-12) | Primary 행동 배경 |
--cta-hover | Primary 행동 | 선택(중립 기본) | var(--gray-11) | CTA hover |
--cta-fg | Primary 행동 | 선택(중립 기본) | var(--gray-1) | CTA 전경 |
(c)
구조층 — 손대지 않는 골격
바꾸면 대비 보장·상태 의미가 깨진다. 정말 필요하면 디자인 승인 후 별도 논의.
| 그룹 | 토큰 | 왜 공유(불변)인가 |
|---|---|---|
| Gray ramp | --gray-1 … --gray-12 | 12-step 대비 사다리 — 텍스트/배경 legibility 의 골격. |
| 표면·텍스트 | --bg-* · --text-* | gray ramp 에서 파생 — 브랜드 무관. |
| 보더 | --border-subtle/default/strong/control | hue 실은 알파 잉크지만 역할은 구분선 — 표면 위 자연 합성. |
| 상태색 | --success · --warning · --danger · --info (+ *-soft) | 의미를 진다(빨강=위험). 테넌트가 바꾸면 상태 언어가 어긋남. |
| 그림자·링 | --shadow-xs…xl · --ring · --ring-danger | elevation 레시피 + 파생 focus 링(accent · soft 자동 추종). |
(d)
템플릿 — 생성하고, 소유하고, 로드한다
gen-theme-template.mjs 가 현 AXE 값 + 주석으로 사본의 출발점을 만든다.
1. 생성
# 소비자 앱 — 패키지에 동봉된 검증된 사본을 가져온다.cp node_modules/@axe/ui/dist/theme-template.css ./theme-template.css# @axe/ui 공급자 저장소에서 manifest+현재 토큰으로 재생성npm run theme:generatenpm run verify:theme-template2. 소유·편집 — 나온 theme-template.css 를 당신 앱으로 복사하고 값만 교체한다. 토큰 이름은 계약이라 바꾸지 않는다(구조층·컴포넌트가 그 이름으로 참조).
3. 로드 — cascade 순서를 지킨다. 사본이 AXE 토큰 뒤에 와서 브랜드층만 덮어쓴다:
import 순서
// 앱 진입점 — cascade 순서가 핵심(뒤에 온 사본이 AXE 기본값을 이긴다).import "@axe/ui/tokens"; // AXE 기본 토큰 (구조층 포함)import "./theme-template.css"; // 이 사본 = 브랜드층만 재정의import "@axe/ui/components.css";(e)
다크 모드 — light-dark() 는 두 인자 모두
AXE 색 토큰은 light-dark(<라이트>, <다크>) 단일 선언. 재스킨도 두 팔을 함께 편집.
light-dark 편집
/* ✅ light-dark(<라이트>, <다크>) — 두 인자를 모두 편집 */--accent: light-dark(#2f7d4f, #7be6a1); /* delyfresh green: 라이트 / 다크 */--accent-soft: light-dark(rgb(47 125 79 / 0.10), rgb(123 230 161 / 0.10));/* ❌ 한 쪽만 바꾸면 반대 모드가 AXE clear blue로 남는다 */--accent: light-dark(#2f7d4f, #5aa5d2);(f)
누출 검증 — 오버라이드가 도달하는가
manifest 토큰 중 literal 브랜드 색이 raw 로 새면 오버라이드가 못 미친다. var() 파생 관계와 대비 검사는 별도 계약으로 함께 확인.
verify-theme-override
# 브랜드 값이 var() 경유로만 쓰이는지 — raw 누출 스캔node scripts/verify-theme-override.mjs # 보고만 (exit 0)node scripts/verify-theme-override.mjs --strict # 미승인 HARD 누출 시 exit 1 (ship 게이트용)| 등급 | 의미 | --strict |
|---|---|---|
| HARD (미승인) | raw hex/rgb 직접 사용 — 오버라이드 미도달. 고쳐야 함. | exit 1 |
| HARD (승인) | 의도적 baking(ALLOWLIST) — 이유 명시. | 통과 |
| SOFT | var(--token, #fallback) 의 fallback — 오버라이드가 var 로 도달, 무해. | 통과 |
(g)
테넌트 재사용 절차 (delyfresh 류)
격리형 고객사가 @axe/ui 를 자기 브랜드로 입히는 최소 경로.
- 템플릿 생성 —
node scripts/gen-theme-template.mjs --out theme-template.css. - 브랜드 값 결정 — 라이트/다크 브랜드색, 기능 accent와 전경 대비, CTA를 중립으로 유지할지까지 함께 결정합니다. 구조층 배경은 자동 재스킨되지 않습니다.
- 사본 편집 — 값만 교체. raw 표시(
--accent-hover·--accent-soft)는 반드시 함께 갱신,light-dark()는 두 팔 모두. - 로드 순서 배선 — AXE 토큰 → 사본 → 컴포넌트 CSS.
- 검증 —
verify-theme-override --strict로 새 raw 누출 0 확인. 인쇄 문서(.axe-doc)가 필요하면 document.css 팔레트는 별도 대응(known-gap). - 구조층 불변 유지 — gray ramp·상태색·보더·그림자는 건드리지 않는다. 바꿔야 할 이유가 있으면 안정성 계약 선상에서 디자인 승인 후.