デザインシステムとカスタマイズ
語彙は 1 つ、デザインシステムは多数。ダイヤルを回す・パレットを宣言する・トークンの参照先を差し替える — 1 コミットで全プラットフォームが追従します。
Tanrei は、自分自身の語彙を小さく保ったまま、あらゆるデザインシステムを — 近似ではなく完全一致で — 再現できるように作られています。それを可能にする規則は 2 つです。デザインシステム同士の違いは、まず「値」で表す。値で表せないものは、レイヤが足す。
Tanrei のコアの語彙は、「1 つのデザインシステムが、1 つのプロダクトの中で取り得るすべての状態」です。ここから、提案されたトークンすべてに適用する判定基準 — 1 プロダクトテスト — が導かれます。
- トークンである:1 つのプロダクトが、意味や場所によってその区別を必要とし得るもの。
focus-ringがprimaryと別なのは、「ブランドカラーは鮮やかに、フォーカスリングは控えめに」というプロダクトがあり得るからです。既定で両者が近い値になっているのは結果論であって、統合する理由にはなりません。ボタンにサイズがあるのは、1 つのプロダクトが複数のサイズを使うからです。 - トークンではない:特定のデザインシステムや、特殊な 1 プロダクトだけが持つもの。Material Design 3 の
tertiaryはそれを再現するプリセットが「宣言するパレット」、primary-fixed系のロールはそのプリセットの「拡張」であって、全員が抱える枠ではありません。特殊なプロダクトのための 20 種類のボタンサイズもコアの語彙ではなく、そのプロダクトが自分のレイヤに足すものです。
それでもなお、あるデザインシステムの特徴(背景のマテリアルや、独自の文字サイズ表など)を再現できない場合、それは Tanrei 側の欠陥として扱い、mimicry-gaps 台帳(リポジトリの docs/governance/mimicry-gaps.json)に登録します。
デザインシステムは、既定のままで自然に見え、1 つ変えれば全体が追従するべきものです。すべての値の下に、8 つの数値 ダイヤル(dial) があります。
| ダイヤル | 既定値 | 動くもの |
|---|---|---|
dial.type.base |
16px | すべての文字サイズ |
dial.type.contrast |
1 | 見出しと本文の差の大きさ |
dial.space.unit |
8px | すべての余白・間隔 |
dial.size.unit |
4px | コントロールの高さ、アイコンサイズ、ヒット領域 |
dial.density |
1 | コントロールと行の高さ、内容のまわりの余白。1 より小さいと詰まり、大きいとゆとりが出る |
dial.shape.unit |
2px | すべての角丸 |
dial.stroke.unit |
1px | 枠線、リング、フォーカス表示 |
dial.motion.unit |
50ms | すべての時間 |
各トークンの値は次の 3 種類のいずれかです。
- 導出(derived):小さなルール。多くはダイヤルへの係数です。
displayはtype.base × 3、space175はspace.unit × 1.75。現実の文字サイズの並びは等比ではないので、トークンごとに固有の係数を持ちます。 - 固定(fixed):理由を明記したリテラル。ダイヤルの影響を受けません(
corner.full、イージング曲線、法的に定められた注記の文字サイズなど)。 - エイリアス(alias):差し替え可能な参照(
button.container.gap→space100)。
Web ではダイヤルが実行時にも効きます。導出トークンは calc() の式として出力されるので、任意の要素に --tanrei-dial-type-base を設定すると、その内側のすべてが追従します。複数のダイヤル設定のもとで全導出トークンを実ブラウザーで評価し、ビルド時の計算結果と一致することをテストで保証しています。
デザインシステムは、別のシステムを extends する小さな JSON ファイルです。ほとんどは値を変えるだけで、必要なときだけ語彙に足します。
{
"id": "acme",
"extends": "tanrei",
"palettes": {
"brand": { "description": "Acme の赤はブランドガイドラインで固定。", "source": { "seed": "#e60012" }, "roles": "intent" },
"correct": { "description": "クイズの正解色。success でも primary でもない。", "source": { "seed": "#0f766e" }, "roles": "intent" }
},
"tokens": {
"tanrei.dial.type.base": { "$value": { "value": 17, "unit": "px" }, "reason": "利用者は少し離れて画面を見る。" },
"tanrei.comp.button.container.padding-inline-start.medium": { "scale": 2 },
"tanrei.comp.button.container.gap.medium": { "$value": "{tanrei.sys.space.space50}" },
"tanrei.sys.typescale.label-small": { "$value": { "fontSize": { "value": 11, "unit": "px" } }, "reason": "法定表記の文字サイズは契約で固定。" }
}
}| やりたいこと | 書くこと |
|---|---|
| 全体を少し大きく | ダイヤルを設定 |
| ボタンの padding を既定の 2 倍に(余白ダイヤルには追従させたまま) | "scale": 2 |
| このトークンだけ別の段を使う | エイリアスを差し替え |
| この値だけはダイヤルに関係なく固定 | reason を添えて $value を固定 |
on- や -container を持ち、コントラスト検査も効くブランド色・ドメイン色 |
"roles": "intent" でパレットを宣言 |
すべてのプラットフォームのコードは同じ解決済みシステムから生成されるので、1 つのプルリクエストで Web・SwiftUI・Compose・Flutter・React Native が同時に変わります。
値だけでは足りないこともあります。参照元のデザインシステムにはコアに無いボタンサイズがあり、あなたのプロダクトには独自の評価コンポーネントがある。そこでレイヤは語彙を拡張することもできます — vocabulary の下に、必ず rationale(理由)を添えて、追加だけを行います。
"vocabulary": {
"tanrei.sys.space.space1200": { "$type": "dimension", "rationale": "ヒーローセクションに 96px のリズムが必要。",
"derive": { "fn": "mul", "of": "{tanrei.dial.space.unit}", "by": 12 } },
"tanrei.comp.button.size.3x": { "from": "large", "scale": 1.5, "rationale": "ランディング画面の主要な行動喚起。" },
"tanrei.comp.button.variant.danger": { "from": "filled", "rationale": "破壊的操作の確認は、このプロダクトでは独立した強調。" },
"acme.comp.rating.star.size": { "$type": "dimension", "rationale": "評価コンポーネントの星。",
"derive": { "fn": "mul", "of": "{tanrei.dial.size.unit}", "by": 5 } }
}| 足すもの | 起きること |
|---|---|
系列の段 — 余白の段、角丸、テキストロール、カラーロール(上に描くロールを onRole で指定) |
隣の段と同じダイヤルに追従し、カラーロールはコアの組と同じくコントラスト検査を受けます |
| コンポーネントの軸の値 — Button のサイズや variant | 1 行で足ります。サイズは軸の全メンバー(高さ・各パディング・ラベルの文字スタイル・アイコン)を別のサイズから複製して倍率を掛け、ダイヤル追従のまま。variant は静止時の色を複製し、hovered / focused / pressed / disabled はシステムのパターンルールが自動で値を決めます |
あなた自身のトークン(レイヤの名前空間 acme.…) |
自前のコンポーネント用。tanrei.* のどのトークンでも参照でき、ダイヤルとスキームに追従し、Web では --acme-…、他のプラットフォームでは型付きオブジェクトになります |
vocabulary は「存在する」ことを宣言し、tokens と rules が「値」を与えます — コアのトークンとまったく同じです。だから子レイヤは拡張も他と同じように変更できます。拡張は継承され、コアを削除したり型を変えたりはできず、インタラクション状態は足せません(それはプラットフォームのものです)。生成されるバンドルには、誰が・なぜ宣言したかとともに一覧されます。
コンポーネントは足した値を受け付けます。Web では <tanrei-button size="3x" variant="danger"> がそのまま動き、ネイティブの各パッケージも同じキーを受け取ります。現在のデザインシステムに無い値は既定値として描画されます。何も足さないシステムは、何も支払いません — 生成コードに拡張の痕跡は一切現れません。
こうして 2 つの理想が両立します。Tanrei 自身の語彙は小さく素直な名前のまま、かつ あらゆるデザインシステムを完全一致で再現できる — 誰かのためにコアを増やす必要がないからです。
コンポーネントは hover や pressed の見た目を計算しません。各状態は固有のトークン(button.filled.hovered.container.color など)を持ち、システムのパターンルールがその値を決めます。
{ "match": "tanrei.comp.*.*.hovered.container.color",
"derive": { "fn": "mix", "base": "{tanrei.comp.$1.$2.container.color}", "with": "{tanrei.comp.$1.$2.label.color}", "amount": "{tanrei.sys.state.hover-state-layer-opacity}" } }これが Tanrei のステートレイヤーです。状態ごとに色を明示するシステムは代わりにエイリアスを書き、hover を持たないシステムは静止時の値へのエイリアスにし、押下で減光するシステムは pressed.opacity を設定します。語彙は同じで、値が違うだけです。
量ではまったく表せない違いも、少しだけあります。押下にリップルで応えるか、コントロールを暗くして応えるか。キーボードフォーカスをリングで示すか、グローで示すか。これらは sys.behavior の下の 2 つのトークン(pressed-feedback、focus-style)で、それぞれが短く閉じた名前の一覧を持ち、レイヤは他の値と同じようにその中から 1 つを選びます。角の曲がり方は選択ではなく量です。形状の sys.shape.corner-smoothing を参照してください。詳しくは振る舞いを参照してください。アイコンセットと書体もレイヤで変えられます。アイコンとタイポグラフィを参照してください。
同じ <tanrei-button>、同じマークアップ。m3-like と cupertino は、性格が正反対の 2 つのシステムでこの仕組みを確かめるため、そして自分のレイヤを書くときの出発点として同梱しています。
完全一致は「主張」ではなく「計測」です。 m3-like は material-web(Apache-2.0)が公開しているトークンへの忠実度マップを持ち、参照元の全カラーロール、全テキストスタイル、Button の全トークンが「一致」「他のトークンに包含」「理由(mimicry gap)つきで未対応」のいずれかでなければテストが落ちます。数値は下のプリセット紹介に表示されます。他のトークンに包含されるものを除いた件数のうちの一致数で、tokens/generated/<id>/fidelity.json から読まれます。名前が「-like」なのは商標上の理由であって、近似だからではありません。
tanrei Tanrei
- 宣言パレット
accent- 値の変更
- 152
- パターンルール
- 132
- WCAG
- 全スキームで不変条件を満たします。 違反はビルドエラーになります。
The default design system. Near-monochrome surfaces, a monochrome primary and one accent. It takes its values from core and leaves out the components and variants it does not draw.
cupertino Cupertino (Apple HIG-inspired)
- 継承元
core- 宣言パレット
- なし
- 値の変更
- 581
- パターンルール
- 15
- 語彙の拡張
- 3
- 忠実度(計測)
- 未計測 — 参照元が機械可読なトークンを公開していないため
- WCAG
- 113 件のコントラストが Tanrei の閾値を下回ると計測されています(すべて標準コントラスト)。 参考情報です。模倣プリセットには Tanrei のアクセシビリティ基準を、どのコントラストスキームでも適用しません。
An independent approximation of the look of Apple's platforms, expressed purely as values: a saturated system tint, pure white and black canvases, gray fills with tinted labels, rounded rectangles, no hover state and a dimmed pressed state.
Tanrei is not affiliated with, endorsed by, or sponsored by Apple Inc. Apple is a trademark of Apple Inc. The values here are Tanrei's own approximations, not a transcription of Apple's documentation, and no Apple font or symbol is bundled: the system font is referenced by name only.
m3-expressive-like M3 Expressive-like (Material 3 Expressive-inspired)
- 継承元
m3-like- 宣言パレット
- なし
- 値の変更
- 53
- パターンルール
- 0
- 語彙の拡張
- 2
- 忠実度(計測)
- 35 / 55 件が参照元と一致 · 0 件が不一致 · 20 件は未対応(台帳に登録済み) — androidx.compose.material3 button size tokensv0_11_0 (Apache-2.0)
- WCAG
- 全スキームで不変条件を満たします。 参考情報です。模倣プリセットには Tanrei のアクセシビリティ基準を、どのコントラストスキームでも適用しません。
m3-like with the five button sizes of Material 3 Expressive — extra small to extra large, each with its own paddings, icon size, icon-to-label gap and outline width. Colors, type, shape and states are m3-like's.
Tanrei is not affiliated with, endorsed by, or sponsored by Google. "Material Design" is Google's; this preset is an independent reproduction of published, Apache-2.0 licensed token values.
m3-like M3-like (Material 3-inspired)
- 継承元
core- 宣言パレット
tertiary- 値の変更
- 711
- パターンルール
- 19
- 語彙の拡張
- 18
- 忠実度(計測)
- 273 / 274 件が参照元と一致 · 0 件が不一致 · 1 件は未対応(台帳に登録済み) — material-webv0_192 (Apache-2.0)
- WCAG
- 全スキームで不変条件を満たします。 参考情報です。模倣プリセットには Tanrei のアクセシビリティ基準を、どのコントラストスキームでも適用しません。
An independent approximation of Material Design 3's baseline look, expressed purely as values over Tanrei's vocabulary: a chromatic primary, tonal surfaces, pill buttons, shadow elevation without a ring.
Tanrei is not affiliated with, endorsed by, or sponsored by Google. "Material Design" is Google's; this preset is an independent approximation derived from publicly available, Apache-2.0 licensed design tokens.
Web ではプリセットは属性でスコープされた追加のスタイルシート 1 枚なので、複数のシステムを同じページに並べることもできます。そのプリセットが持つコンポーネントは、プリセットのエントリーが登録します。既定のシステムが持たないものもここに含まれます(@tanrei/web が登録するのは既定のシステムのコンポーネントだけです)。
import '@tanrei/web/tokens.css';
import '@tanrei/web/systems/cupertino.css';
import '@tanrei/web/cupertino'; // cupertino が持つコンポーネント<body data-tanrei-system="cupertino">値でも種類の選択でもまだ表現できないもの(背景をぼかすマテリアル、スタイルとしての大文字ラベルなど)は、隠さずに Tanrei 側の欠陥として docs/governance/mimicry-gaps.json に登録しています。
システムは、コンポーネントを丸ごと外すことも、コンポーネントの variant の値を 1 つだけ外すこともできます。既定のシステムには segmented button がなく、テキストフィールドの filled と outlined もありません。レイヤはそれを components に、理由と代わりに使うものと一緒に書きます。
"components": {
"omit": { "carousel": { "reason": "Acme shows one thing at a time.", "insteadUse": ["list"] } },
"variants": { "tabs": { "omit": ["tonal"] } }
}外したものも描画され、動きます。開発ビルドでは、それを使う要素ごとに 1 度だけコンソールに警告が出て、あなたのシステムの名前と、代わりに使うものを示します。警告が名指すのは、あなたが書いたコンポーネントです。<tanrei-text-field variant="outlined"> は、内側に描くテキスト入力ではなく、テキストフィールドの outlined として報告されます。別のシステムのレイヤにあるコンポーネント(m3-like にある FAB menu など)も同じように、それがあるレイヤとともに報告されます。
テストで失敗させるには、エラーとして報告させ、何が使われたかを尋ねます。
import { setAvailabilityMode, unavailableUses } from '@tanrei/web';
setAvailabilityMode('error'); // テストのセットアップで、何かを描画する前に
test('プロダクトはシステムにあるものだけを使っている', async () => {
// …プロダクトの画面を描画する…
expect(unavailableUses()).toEqual([]);
});unavailableUses() は、どちらのモードでも、使われたものを 1 件ずつアルファベット順に返します(segmented-button、text-field:outlined)。本番ビルドは何も確かめないので、そこではこのリストは空のままです。要素を 1 つも登録せずに同じ 2 つの関数だけを使うには、@tanrei/web/availability を読み込みます。
システムをビルドすると、自己完結した DTCG 2025.10 バンドル — 全値が入った素のトークンファイルと標準のリゾルバ — が生成されます。デザイントークンを読めるツールなら、Tanrei なしでそのまま利用できます。生成元のレイヤファイルもバンドルに同梱されるので、Tanrei(や AI エージェント)が再構築・変更できます。
アクセシビリティ
コントラストの閾値は、誰にも変更できない定数です。デフォルトのシステムでは違反はビルド失敗になります。プロダクトのレイヤでは違反は警告になります — ビルド時に表示され、生成物にも書き込まれ、黙殺はされません。他のシステムを模倣するプリセットは、代わりに模倣元への忠実さで評価します。標準・中・高のどのコントラストでも Tanrei のアクセシビリティ基準は適用せず、ビルドがそこで計測したコントラスト比は、警告ではなく参考情報として記録します。操作できないコントロールや名前・状態の欠落といった機能上の問題は、模倣プリセットを含め、どのシステムでも不具合です。