본문으로 건너뛰기
Theme layer / 04

정체성은 바꾸고,
의미는 지킵니다.

@axe/ui의 색을 브랜드층과 구조층으로 나눕니다. 소비자는 theme-template.css 사본에서 정체성만 교체하고, 대비와 상태 의미를 지는 골격은 공유합니다.
Theme contract9 override tokensLight + dark
0.22.1 전달 상태
theme manifest/template은 현재 모노레포와 검증된 vendored mirror에서 사용할 수 있습니다. 승인된 v0.22.1 package tag는 아직 발행 전이므로 과거 설치본에는 이 export가 없습니다.
(a)

두 층 — 브랜드 vs 구조

무엇을 바꾸면 재스킨이고, 무엇을 바꾸면 시스템이 깨지는가.

브랜드층 · 교체 대상
--brand-* · --accent* · --cta* — 브랜드와 기능의 명시적 교체 지점. Logo는 브랜드 토큰, 링크·focus는 accent, Primary 행동은 CTA가 각각 맡습니다.
구조층 · 불변 골격
gray ramp · --bg-* / --text-* · 보더 · 상태색(success/warning/danger/info) · 그림자. 대비 보장과 의미(빨강=위험)를 지고 있어 전 테넌트 공통 — 템플릿에 없다.
왜 brand와 accent를 따로 두는가
--accent 는 기능색이고 AXE 와인 --brand-claret은 정체성 색입니다. 브랜드를 화면 전반의 상태·포커스에 반복하지 않도록 둘을 의도적으로 분리합니다. 테넌트가 기능색까지 바꾸려면 --accent-hover · --accent-soft 를 포함한 액센트군 전체를 함께 편집합니다.
(b)

브랜드층 토큰

템플릿에 담기는 정확한 목록. 값은 tokens/colors.css 를 빌드타임 파싱 — 이 표는 항상 라이브 값.

--brand-claret
light-dark(#821f3b, #d2839a)
--brand-claret-print
#821f3b
--accent
var(--palette-blue)
--accent-hover
light-dark(#0c3c5b, #82bce0)
--accent-fg
light-dark(#ffffff, #181818)
--accent-soft
light-dark(rgb(16 74 112 / 0.10), rgb(90 165 210 / 0.14))
--cta
var(--gray-12)
--cta-hover
var(--gray-11)
--cta-fg
var(--gray-1)
토큰재스킨 시현재 값용도
--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 배경
--ctaPrimary 행동선택(중립 기본)var(--gray-12)Primary 행동 배경
--cta-hoverPrimary 행동선택(중립 기본)var(--gray-11)CTA hover
--cta-fgPrimary 행동선택(중립 기본)var(--gray-1)CTA 전경
파생 토큰은 담지 않는다
--ring (=accent + accent-soft 조합) · --ring-danger (=danger-soft 참조)는 자동 추종하므로 템플릿에 없다. --accent-fg 의 라이트 값(흰색)은 브랜드색이 아니라 대비 파트너 — accent 채도를 바꾸면 전경 대비만 재확인한다.
(c)

구조층 — 손대지 않는 골격

바꾸면 대비 보장·상태 의미가 깨진다. 정말 필요하면 디자인 승인 후 별도 논의.

그룹토큰왜 공유(불변)인가
Gray ramp--gray-1 … --gray-1212-step 대비 사다리 — 텍스트/배경 legibility 의 골격.
표면·텍스트--bg-* · --text-*gray ramp 에서 파생 — 브랜드 무관.
보더--border-subtle/default/strong/controlhue 실은 알파 잉크지만 역할은 구분선 — 표면 위 자연 합성.
상태색--success · --warning · --danger · --info (+ *-soft)의미를 진다(빨강=위험). 테넌트가 바꾸면 상태 언어가 어긋남.
그림자·링--shadow-xs…xl · --ring · --ring-dangerelevation 레시피 + 파생 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-template

2. 소유·편집 — 나온 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);
한 쪽만 바꾸는 함정
라이트 팔만 바꾸고 다크 팔을 두면, 다크 모드에서 AXE clear blue가 그대로 드러납니다. 모드 전환은 color-scheme 이 처리하므로 별도 [data-theme] 블록은 불요 — light-dark() 두 인자만 맞추면 된다.
브라우저 floor와 fallback 경계
직접 지원 기준은 Chrome/Edge 123+, Safari 17.5+, Firefox 120+입니다. canonical @axe/ui/bundle.css 자체는 구형 브라우저용 정적 팔레트를 생성하지 않습니다. 외부 수신자 표면인 layer/gate만 각 bundle 단계에서 @supports not (color: light-dark(...)) 폴백을 자동 생성하므로, 그 밖의 구형 브라우저 소비자는 같은 gate를 별도로 적용해야 합니다.
(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) — 이유 명시.통과
SOFTvar(--token, #fallback) 의 fallback — 오버라이드가 var 로 도달, 무해.통과
현재 상태 (v0 실측)
미승인 HARD 0. 승인 HARD 2건과 SOFT fallback 1건은 전부 styles/document.css .axe-doc 인쇄 문서가 PDF/print 에서 앱 CSS 변수 캐스케이드 없이 렌더되므로 claret 를 의도적으로 baking 한 것. 즉 테넌트 재스킨 시 인쇄 문서 팔레트는 자동으로 안 따라온다(known-gap — document.css 토큰화가 후속 과제).
(g)

테넌트 재사용 절차 (delyfresh 류)

격리형 고객사가 @axe/ui 를 자기 브랜드로 입히는 최소 경로.

  1. 템플릿 생성 node scripts/gen-theme-template.mjs --out theme-template.css.
  2. 브랜드 값 결정 — 라이트/다크 브랜드색, 기능 accent와 전경 대비, CTA를 중립으로 유지할지까지 함께 결정합니다. 구조층 배경은 자동 재스킨되지 않습니다.
  3. 사본 편집 — 값만 교체. raw 표시(--accent-hover· --accent-soft)는 반드시 함께 갱신, light-dark() 는 두 팔 모두.
  4. 로드 순서 배선 — AXE 토큰 → 사본 → 컴포넌트 CSS.
  5. 검증 verify-theme-override --strict 로 새 raw 누출 0 확인. 인쇄 문서(.axe-doc)가 필요하면 document.css 팔레트는 별도 대응(known-gap).
  6. 구조층 불변 유지 — gray ramp·상태색·보더·그림자는 건드리지 않는다. 바꿔야 할 이유가 있으면 안정성 계약 선상에서 디자인 승인 후.
v0 범위 = 문서·도구 층(분류·템플릿·누출 스캐너·이 가이드). 구조 변경·CSS 값 리팩터(예 document.css 토큰화, brand-only 오버라이드로 accent-hover/soft 파생화)는 후속 wave. 전체 계약은 /contract 참조.
검색창을 열면 컴포넌트 인덱스를 불러옵니다.