본문으로 건너뛰기
Stability contract / 03

React 밖에서도
깨지지 않는 약속.

.axe-* 클래스만 쓰는 Rust maud, Python jinja, 정적 HTML 소비자와의 공개 계약입니다. 예측 가능한 이름과 변경 규율을 문서보다 코드에 가깝게 관리합니다.
stable classesv0.22.1
0.22.1 전달 상태
설치 가능한 0.22.1 package와 Consumer Kit은 구현됐지만 승인된 registry/tag는 아직 발행 전입니다. 과거 git tag나 raw source 경로로 아래 계약을 흉내 내지 않습니다. 운영 승인 전에는 공급자 worktree에서 만든 tarball의 npm shasum/integrity를 고정해 검증합니다.
(a)

명명 규칙

prefix 하나 · 변수 하나 — 소비자가 예측 가능하게.

패턴의미
.axe-<component>컴포넌트 루트.axe-btn
.axe-<c>__<part>내부 파트 (BEM element).axe-card__title
.axe-<c>--<variant>변형 (BEM modifier).axe-btn--primary
--<token>디자인 토큰 (CSS 변수)--accent · --bg-base · --space-4
색·간격·폰트는 언제나 CSS 변수로 참조한다. 하드코딩된 hex/px 가 아니라 var(--accent) 를 쓰면 라이트/다크 · 테넌트 테마가 자동으로 따라온다.
(b)

안정성 보장

컴포넌트 문서의 클래스 표에 붙는 stable / internal 배지가 계약 등급.

stable
비-React 소비자가 의존해도 되는 공개 표면. minor 버전 안에서 rename 금지. 제거·rename 은 major 버전 + 직전 minor 에 deprecation 공지 (1버전 유예) 를 거친다. 유예 기간에는 구·신 클래스가 공존한다.
internal
구현 세부(트랜지션 헬퍼 · data-state 훅 등). 예고 없이 변경·제거될 수 있다. 여기에 의존해 마크업을 짜지 말 것.
버전 규약
semver 를 따른다. patch = 버그·시각 미세 조정(계약 불변), minor = 신규 컴포넌트·클래스 추가(기존 stable 불변), major = stable 표면의 파괴적 변경(deprecation 유예 후).
(c)

비-React 소비 절차

서비스별 concat 대신 선언형 config에서 같은 CSS·theme·font·lock을 생성합니다.

#파일역할
1tokens/index.cssfont import를 최상단에 hoist하고 모든 토큰을 전개
2styles/reset.css최소 리셋을 토큰 뒤에 배치
3styles/components.css15개 group import를 실제 cascade 순서로 재귀 전개

@import 미해석 환경 — managed output, public font base, compatibility profile만 선언합니다. 수동으로 파일을 이어붙이거나 package 내부 경로를 복사하지 않습니다.

axe-ui.consumer.json
{  "$schema": "./node_modules/@axe/ui/consumer-config.schema.json",  "out": "static/axe-ui",  "assetBase": "fonts",  "compatibility": "modern"}
export / verify (CSS-only)
# CSS·theme·font·OFL을 stage하고 SHA lock을 마지막에 promotenpx --no-install axe-ui-export --config axe-ui.consumer.json# build/ship gate — 파일을 고치지 않고 drift만 검사npx --no-install axe-ui-export --config axe-ui.consumer.json --check

@import 해석 번들러 (Next · webpack · vite) — 진입점 두 개로 충분. components.css 가 그룹 파일을 @import 로 말아올린다. 첫 설치 때 axe-ui-copy-fonts로 same-origin 폰트 자산도 복사한다:

import (bundler)
/* Next/webpack/vite 등 @import 해석 번들러 — 두 줄이면 충분.   components.css 가 나머지 그룹(form·layout·indicators·document …)을   @import 로 이미 말아올린다. tokens 는 별도 진입점. */import "@axe/ui/tokens";          /* CSS 변수 (light/dark) */import "@axe/ui/components.css";  /* .axe-* 전 그룹 */

어느 환경에서나 같은 단일 파일이 필요하면 canonical entry를 사용합니다. 비-React 서비스에서는 이 entry를 직접 복사하지 않고 Consumer Kit의 axe-ui.css를 route에 연결합니다.

import (canonical flat bundle)
/* 번들러 유무와 무관한 단일 정본 — local @import 0 */import "@axe/ui/bundle.css";
(d)

라이선스에 맞춘 자산 전달

OFL 핵심 폰트는 자체완결하고 ITF FFL 브랜드 폰트는 공식 API가 직접 전달합니다.

금지
  • canonical bundle 안 미승인 런타임 <script>/스타일시트
  • 핵심 UI 폰트의 원격 @import first-paint 체인
  • 같은 URL 자산 교체(캐시 포이즌 — URL 버저닝으로 회피)
원칙
  • OFL 폰트 woff2는 same-origin self-host
  • Sarasa Fixed K 로 한글:ASCII 2:1 고정폭 보장
  • unicode-range 미설정 — minifier 의 u+11?? wildcard 함정 회피
앱 채널과 외부 소비자 채널 모두 same-origin Pretendard mirror + self-host Sarasa를 쓴다. @axe/ui/bundle.css는 폰트 CSS까지 inline하며, 실제 woff2와 SIL OFL 고지는 axe-ui-copy-fonts가 함께 배치한다. Clash Display는 ITF FFL 대상이라 저장소·package·Consumer Kit에 바이너리를 넣지 않는다. 필요한 host만 route-scoped 또는 첫 paint 뒤 client loader로 Fontshare 공식 API 500/600 <link>를 삽입한다. f[]= query의 빌드 탈락 이력 때문에 CSS @import는 지원하지 않으며, provider 도착 전·실패 시 --font-sans로 폴백한다.
(e)

버전 · CHANGELOG

현재 계약 버전과 변경 이력의 SSOT.

항목SSOT비고
현재 버전src/lib/VERSIONv0.22.1 — semver
변경 이력CHANGELOG.mdstable 표면 변경·deprecation 공지 기록
release manifestpackages/ui/package.jsonESM exports · peer · CLI · private publish gate
소비자 증빙axe-ui.lock.jsonversion · payload/selector/file SHA · compatibility
컨벤션src/lib/ARCHITECTURE.md컴포넌트 작성 규약
(f)

테마 · 브랜드 오버라이드

같은 계약의 색 축 — 무엇을 갈아끼우면 재스킨이고, 무엇을 바꾸면 시스템이 깨지는가.

색 토큰은 두 층이다. 브랜드층(--brand-* · --accent* · --cta*, 정체성 캐리어)은 테넌트가 theme-template.css 사본을 소유해 값만 바꾼다. 구조층(gray ramp · 보더 · 상태색 · 그림자)은 대비 보장·상태 의미를 지고 있어 전 테넌트 공통 — 이 계약이 보호하는 표면과 같은 원리로 불변이다.

도구 · 가이드
  • 전체 가이드(층 분류 · 토큰 표 · 템플릿 · light-dark · 테넌트 절차) = /theme
  • 템플릿 생성 = scripts/gen-theme-template.mjs (현 AXE 값 + 주석)
  • 누출 검증 = scripts/verify-theme-override.mjs — 브랜드 값이 var() 경유로만 쓰이는지 스캔(--strict = ship 게이트)
검색창을 열면 컴포넌트 인덱스를 불러옵니다.