Skip to content

@tabula-css/tokens

Tabula のフラット DTCG プロファイルパーサー、値セマンティクスバリデーター、軸リゾルバー、エミッター。

インストール

@tabula-css/tokens はクイックスタートで直接インストールする対象には含まれません — これは @tabula-css/registry の推移的依存(さらにそれを通じて @tabula-css/cli の推移的依存)であり、ほとんどのプロジェクトはこの registry を使ってトークンをビルドに変換します。トークンパイプライン自体を対象としたカスタムツール — たとえばトークンファイルを検証したり、完全な tabula build を実行せずに CSS へ解決したりするスクリプト — を構築する場合にのみ直接インストールしてください。

bash
npm install @tabula-css/tokens

概要

ここは、デザイントークンが単なる JSON であることをやめ、システムの残りの部分が信頼できる値になる場所です。@tabula-css/tokens は、Getting started に記載されたビルドパイプラインのステップ 1〜5 を実装します: トークンファイルと設定ファイルをパースし、値セマンティクスを検証し(形だけでなく — { value: 0, unit: "px" } というフォントサイズは、well-formed な JSON ではありますが、ここではビルドエラーです)、軸マップとエイリアスをリテラルの行列に解決し、4 つの W1 アーティファクト(theme.csstokens.resolved.jsonvocabulary.txttypes.d.ts)を出力します。ここで生成される解決済みモデル — 合成された語彙、その期待される宣言、カスタムプロパティテーブル — こそが、@tabula-css/registry が Tailwind の実際のコンパイル済み出力から registry.json を導出するために消費するものです。

エクスポート

buildProfile()

ts
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 です: エラー重大度の診断が 1 つでもあれば、modelartifacts も返されません。「トークンと設定を入力し、アーティファクトまたは診断を得る」だけで済ませたい場合に呼び出すべき唯一の関数です。

ts
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()

ts
export interface ParseResult {
  readonly ast: TokensFile | null;
  readonly diagnostics: readonly Diagnostic[];
}
export function parseTokens(json: string): ParseResult;

トークンファイルの文字列を AST にパースします。この段階では、入力が RFC 8259 の JSON であり、かつトップレベルがオブジェクトであることのみを保証します — JSON5、コメント、末尾カンマ、先頭 BOM は不可で、それ以外の場合は TAB-E101ast: null を返します。値セマンティクスは validateTokens() によって別途検証されます。

validateConfig() / parseConfig()

ts
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 Schema エンジンは存在せず、このパッケージの唯一の依存は @tabula-css/core です — 軸に関する問題は TAB-E120 として、安全でない dynamicProperties[*].syntax(何も検証しない汎用的な '*')は TAB-E127 として報告されます。

validateTokens()

ts
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、…)と、Concepts にあるすべての値セマンティクスパスを実行します: 軸の網羅性(TAB-E113 — 宣言されたすべての軸メンバーにはリテラルが必要で、フォールバックはありません)、すべての軸の組み合わせにわたる contrastWith(TAB-E153)、名前空間ごとの寸法ドメイン(TAB-E111TAB-E155E157)、duration/opacity/fontWeight/z のドメイン、type/shadow コンポジット(TAB-E162/E163)、エイリアスグラフ、例外の書類(TAB-E140/E141 — 理由が 40 文字以上かつ定型文でないこと、ISO 形式の有効期限が 12 か月以内であること)、そしてクラス名の一意性(TAB-E112)。

ts
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() と軸ヘルパー

ts
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(またはトークン全体)を特定の 1 つの組み合わせで解決します。

ts
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/presetbuildChainLayer()@tabula-css/registry のジェネレーターは、その配列を逐語的に読み取ります — プリセットはチェーンごとに 1 つのリテラル CSS ルールを出力するために、ジェネレーターは registry.jsonvariantProducts/chainCount を記録するために。

エミッター

ts
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[];

生成される 4 つのアーティファクトそれぞれに対応する、4 つの純粋な ResolvedModel → string 関数です: emitThemeCss(@property 宣言に加え、@layer tabula.tokens の root/attribute/media 軸ブロック)、emitResolvedJson(フラットで完全にリテラルな tokens.resolved.json)、emitVocabulary(1 行につき 1 つの合法なクラスを、ランク順に)、emitTypes(types.d.ts 内の TabulaUtility/TabulaVariant/TabulaClass のユニオン型)。

v0.2 以降、emitResolvedJson は各トークンのプロヴェナンスマーカーを逐語的に保持します — $deprecated$extensions(com.example.figma のような外部ベンダーの名前空間を含む)、そしてソースの $value がトップレベルの単一のエイリアスであった場合に元のドットパスを記録する $alias マーカーです — これにより、コミット済みの .tabula/ は、それをビルドした元のソースプロファイルを再構築できます。Migration § .tabula/ からプロファイルを復元する を参照してください。

ambientBaselineDecls() は、base レベルのアンビエントベースライン(Concepts § profile levels に従い、継承可能でプロファイルが所有するすべてのプロパティを :root で一度だけ出力するもの)を計算し、値を取得できなかったものを報告します。checkAmbientBaseline() は、空でない missing リストを TAB-E220 エラー診断に変換します — strict の下では、その typography の閉包によってベースラインが冗長になるため、これは何もしません。

ts
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)に加え、WCAG 2.x の相対輝度とコントラスト比を提供します。validateTokens() のカラードメインおよび contrastWith チェックで使用されます。広色域の関数(oklch()oklab()lab()lch()color())は構文的に valid(srgb: false)としてパースされますが、sRGB チャンネルへは解決されません — これにより、バリデーターは「そもそも色ではない」(TAB-E150)と「sRGB 色域への所属を確認できない valid な色」(TAB-E151)を区別できます。

ts
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/corevalidateTokens()/resolveTokens() が照合する凍結テーブルとエラーカタログ。
  • @tabula-css/registryResolvedModel を消費し、Tailwind のコンパイル済み CSS から registry.json を導出します。
  • @tabula-css/presetResolvedModel(および emitThemeCss())を消費し、Tailwind のエントリスタイルシートを組み立てます。
  • Getting started — トークン作成の一連のウォークスルー。
  • Concepts — 局所性、テーマ設定の軸、プロファイルレベル。

Released under the MIT License.