@tabula-css/tokens
Tabula의 플랫 DTCG 프로파일 파서, 값 의미론(value-semantics) 검증기, 축(axis) 리졸버, 이미터입니다.
설치
@tabula-css/tokens는 퀵스타트에서 직접 설치하는 대상이 아닙니다 — 이는 @tabula-css/registry(그리고 이를 통해 @tabula-css/cli)의 전이 의존성이며, 대부분의 프로젝트는 이를 통해 토큰을 빌드로 변환합니다. 토큰 파이프라인 자체를 위한 커스텀 도구, 예를 들어 전체 tabula build를 실행하지 않고 토큰 파일을 검증하거나 CSS로 리졸브하는 스크립트를 만드는 경우에만 직접 설치하십시오.
npm install @tabula-css/tokens개요
여기가 바로 디자인 토큰이 JSON이기를 멈추고 시스템의 나머지 부분이 신뢰할 수 있는 값이 되는 지점입니다. @tabula-css/tokens는 시작하기에서 설명하는 빌드 파이프라인의 1~5단계를 구현합니다: 토큰 파일과 설정 파일을 파싱하고, 값 의미론을 검증하며(단순히 형태만이 아니라 — { value: 0, unit: "px" } 폰트 크기는 형식상으로는 올바른 JSON이지만 여기서는 빌드 오류입니다), 축 맵과 별칭(alias)을 리터럴 행렬로 리졸브하고, 네 가지 W1 아티팩트(theme.css, tokens.resolved.json, vocabulary.txt, types.d.ts)를 방출합니다. 이 패키지가 산출하는 리졸브된 모델 — 합성된 어휘, 그 예상 선언, 커스텀 속성 테이블 — 은 @tabula-css/registry가 Tailwind의 실제 컴파일 출력에서 registry.json을 도출하기 위해 그대로 소비하는 것입니다.
내보내기
buildProfile()
export interface BuildResult {
readonly ok: boolean;
readonly diagnostics: readonly Diagnostic[];
readonly model?: ResolvedModel; // present only when validation passed
readonly artifacts?: Artifacts; // present only when validation passed
}
export function buildProfile(
tokensJson: string,
configJson: string,
options?: ValidateOptions,
): BuildResult;W1 슬라이스 전체를 처음부터 끝까지 실행합니다 — 파싱, 검증, 리졸브, 방출 — 그리고 실패 시 폐쇄적으로(fail closed) 동작합니다: 오류 심각도의 진단이 하나라도 있으면 model도 artifacts도 반환되지 않습니다. "토큰과 설정을 넣으면 아티팩트 또는 진단이 나온다"만 원한다면 호출할 단 하나의 함수입니다.
import { readFileSync } from 'node:fs';
import { buildProfile } from '@tabula-css/tokens';
const result = buildProfile(
readFileSync('tokens/base.tokens.json', 'utf8'),
readFileSync('tabula.config.json', 'utf8'),
);
if (!result.ok) {
for (const d of result.diagnostics) console.error(`${d.code}: ${d.message}`);
process.exit(1);
}
console.log(result.artifacts['vocabulary.txt']);parseTokens()
export interface ParseResult {
readonly ast: TokensFile | null;
readonly diagnostics: readonly Diagnostic[];
}
export function parseTokens(json: string): ParseResult;토큰 파일 문자열을 AST로 파싱합니다. 이 단계는 입력이 RFC 8259 JSON이고 최상위가 객체임을 보장할 뿐입니다 — JSON5, 주석, 후행 쉼표, 선행 BOM은 허용되지 않으며, 그렇지 않으면 TAB-E101과 ast: null을 반환합니다. 값 의미론은 validateTokens()가 별도로 검증합니다.
validateConfig() / parseConfig()
export interface ConfigResult {
readonly ok: boolean;
readonly config: TabulaConfig | null;
readonly diagnostics: readonly Diagnostic[];
}
export function validateConfig(input: unknown): ConfigResult;
export function parseConfig(json: string): ConfigResult;이미 파싱된 설정 객체(validateConfig)나 원시 JSON 문자열(parseConfig — 잘못된 JSON에 대해서도 TAB-E101을 보고합니다)을 검증합니다. 둘 다 tabula.config.json을 코드로 구조적으로 검사합니다 — 런타임에 JSON 스키마 엔진이 없으므로 이 패키지의 유일한 의존성은 @tabula-css/core입니다 — 축 문제는 TAB-E120으로, 안전하지 않은 dynamicProperties[*].syntax(아무것도 검증하지 못하는 보편적인 '*')는 TAB-E127로 보고합니다.
validateTokens()
export interface ValidateOptions {
/** Reference "now" for expiry/removeAfter checks. Defaults to the current date. */
readonly now?: Date;
}
export interface ValidationResult {
readonly ok: boolean;
readonly diagnostics: readonly Diagnostic[];
}
export function validateTokens(
ast: TokensFile,
config: TabulaConfig,
options?: ValidateOptions,
): ValidationResult;스키마 및 값 의미론 게이트입니다. parseTokens() 이후에 실행되며, 모든 구조적 검사(P1–P15: 깊이-2 중첩, 알려진 네임스페이스, 케밥 표기 토큰 이름, 20자 이상의 필수 $description, 금지된 $ref, 별칭 깊이 ≤ 1 등)와 핵심 개념의 모든 값 의미론 검사 단계를 수행합니다: 축의 완전성(TAB-E113 — 선언된 모든 축 멤버는 폴백 없이 리터럴이 필요합니다), 모든 축 조합에 걸친 contrastWith(TAB-E153), 네임스페이스별 차원 도메인(TAB-E111, TAB-E155–E157), duration/opacity/fontWeight/z 도메인, type/shadow 합성 값(TAB-E162/E163), 별칭 그래프, 예외 서류(TAB-E140/E141 — 이유는 40자 이상이며 상투적이지 않아야 하고, ISO 만료일은 12개월 이내), 클래스 이름 고유성(TAB-E112).
import { parseTokens, parseConfig, validateTokens } from '@tabula-css/tokens';
const { ast } = parseTokens(tokensJson);
const { config } = parseConfig(configJson);
const { ok, diagnostics } = validateTokens(ast, config, { now: new Date('2026-07-22') });resolveTokens()와 축 헬퍼
export function resolveTokens(ast: TokensFile, config: TabulaConfig): ResolvedModel;
export function axisSignature(value: unknown, ast: TokensFile, depth?: number): readonly string[];
export function enumerateCombos(axes: readonly string[], config: TabulaConfig): AxisState[];
export function defaultCombo(axes: readonly string[], config: TabulaConfig): AxisState;
export function resolveRaw(value: unknown, combo: AxisState, ast: TokensFile, depth?: number): unknown;
export function resolveLiteral(token: Token, combo: AxisState, ast: TokensFile): ResolvedLiteral;resolveTokens()는 축 리졸버이자 어휘 합성기입니다: 모든 별칭을 따라가고, 모든 축 맵을 완전한 리터럴 행렬로 전개하며, 각 후보의 예상 선언, 슬롯, 순위와 함께 후보 어휘(네임스페이스 × 패밀리 × 토큰, 그리고 STATIC_UTILITIES와 선언된 예외)를 합성합니다. AST가 이미 validateTokens()를 통과했다고 가정합니다. axisSignature()는 토큰의 값이 어떤 축들에 걸쳐 달라지는지 보고합니다; enumerateCombos()/defaultCombo()는 축 조합의 데카르트 곱과 그 기본 멤버를 산출합니다; resolveRaw()/resolveLiteral()은 하나의 특정 조합에서 원시 $value(또는 토큰 전체)를 리졸브합니다.
export interface ResolvedModel {
readonly profileId: string;
readonly config: TabulaConfig;
readonly tokens: readonly ResolvedToken[];
/** The synthesized vocabulary, in ascending rank order. */
readonly candidates: readonly CandidateClass[];
readonly customProperties: readonly CustomPropertyDef[];
/** Declared variant products (contract J22): chain prefix → sorted family names, `"*"` pre-expanded. */
readonly variantProducts: Readonly<Record<string, readonly string[]>>;
/** The materialized variant chains, sorted ascending by `(effectiveRank, class)`. */
readonly chainCandidates: readonly ChainCandidate[];
readonly diagnostics: readonly Diagnostic[];
}
export interface ChainCandidate {
/** Full canonical class, e.g. `"hover:bg-accent-hover"`. */
readonly class: string;
/** Canonical ascending-rank, colon-joined chain prefix, no trailing colon: `"hover"` | `"sm:hover"`. */
readonly chainPrefix: string;
/** The base utility class name the prefix is applied to. */
readonly utility: string;
/** Σ of the chain's variant ranks. */
readonly conditionRank: number;
/** `conditionRank * CONDITION_RANK_SCALE + base class rank` — the merge's total order. */
readonly effectiveRank: number;
/** Exception paperwork, present only for an exact-class chain grant (`except add --chain`). */
readonly exception?: ExceptionMeta;
}v0.2에서 나온 variants 폐쇄성: resolveTokens()는 설정에 선언된 변형 프로덕트를 읽어, 각 chainPrefix × family 프로덕트를 하나의 ChainCandidate로 전개하고, 이를 (effectiveRank, class) 순서로 chainCandidates에 방출합니다. @tabula-css/preset의 buildChainLayer()와 @tabula-css/registry의 생성기는 그 배열을 그대로 읽습니다 — 프리셋은 체인당 하나의 리터럴 CSS 규칙을 내기 위해, 생성기는 registry.json에 variantProducts/chainCount를 기록하기 위해서입니다.
이미터
export function emitThemeCss(model: ResolvedModel): string;
export function emitResolvedJson(model: ResolvedModel): string;
export function emitVocabulary(model: ResolvedModel): string;
export function emitTypes(model: ResolvedModel): string;
export function ambientBaselineDecls(model: ResolvedModel): { decls: Array<[string, string]>; missing: string[] };
export function checkAmbientBaseline(model: ResolvedModel): Diagnostic[];각각 생성된 아티팩트 하나에 대응하는 네 개의 순수한 ResolvedModel → string 함수입니다: emitThemeCss(@property 선언과 @layer tabula.tokens 루트/속성/미디어 축 블록), emitResolvedJson(평면적이고 완전히 리터럴화된 tokens.resolved.json), emitVocabulary(순위 순서로 한 줄에 하나씩 합법적인 클래스), emitTypes(types.d.ts 안의 TabulaUtility/TabulaVariant/TabulaClass 유니온 타입).
v0.2부터 emitResolvedJson은 각 토큰의 출처(provenance) 마커 — $deprecated, $extensions(com.example.figma 같은 외부 벤더 네임스페이스 포함), 그리고 소스 $value가 단일 최상위 별칭이었을 때 원래의 점(dot)-경로를 기록하는 $alias 마커 — 를 그대로 보존하므로, 커밋된 .tabula/가 자신을 빌드한 소스 프로파일을 재구성할 수 있습니다. 마이그레이션 § .tabula/에서 프로파일 복원하기를 참고하십시오.
ambientBaselineDecls()는 base 레벨의 주변 기준선(ambient baseline)을 계산하고(핵심 개념 § 프로파일 레벨에 따라, 상속 가능한 프로파일 소유 속성 각각을 :root에서 한 번씩 방출), 출처를 찾을 수 없었던 항목을 보고합니다. checkAmbientBaseline()은 비어 있지 않은 missing 목록을 TAB-E220 오류 진단으로 바꿉니다 — strict에서는 타이포그래피 폐쇄성 때문에 기준선이 불필요해지므로 아무 작업도 하지 않습니다.
색상
export interface Rgb { readonly r: number; readonly g: number; readonly b: number; readonly a: number; }
export type ColorParse =
| { readonly ok: true; readonly srgb: true; readonly rgb: Rgb }
| { readonly ok: true; readonly srgb: false } // valid CSS colour, not sRGB-resolvable here
| { readonly ok: false };
export function parseColor(input: string): ColorParse;
export function relativeLuminance(c: Rgb): number;
export function contrastRatio(a: Rgb, b: Rgb): number;작고 독립적인 CSS 색상 파서(hex, rgb()/rgba(), hsl()/hsla(), 자주 쓰이는 색상 이름 집합, transparent)와, validateTokens()의 색상-도메인 및 contrastWith 검사가 사용하는 WCAG 2.x 상대 휘도(relative luminance) 및 대비 비율(contrast ratio)입니다. 광색역(wide-gamut) 함수(oklch(), oklab(), lab(), lch(), color())는 구문적으로 유효하게 파싱되지만(srgb: false) sRGB 채널로 리졸브되지는 않습니다 — 이 덕분에 검증기는 "애초에 색상이 아님"(TAB-E150)과 "sRGB 색역 소속 여부를 확인할 수 없는 유효한 색상"(TAB-E151)을 구분할 수 있습니다.
import { parseColor, contrastRatio } from '@tabula-css/tokens';
const surface = parseColor('#0b0b0c');
const text = parseColor('#ffffff');
if (surface.ok && surface.srgb && text.ok && text.srgb) {
contrastRatio(surface.rgb, text.rgb); // ≥ 1, WCAG-comparable
}함께 보기
@tabula-css/core—validateTokens()/resolveTokens()가 대조하는 고정 테이블과 오류 카탈로그.@tabula-css/registry—ResolvedModel을 소비해 Tailwind의 컴파일된 CSS로부터registry.json을 도출합니다.@tabula-css/preset—ResolvedModel(과emitThemeCss())을 소비해 Tailwind 진입 스타일시트를 조립합니다.- 시작하기 — 토큰 작성 전체 과정을 다루는 안내서.
- 핵심 개념 — 지역성, 테마 축, 프로파일 레벨.