メインコンテンツへスキップ
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
はじめにWebReactSwiftUIJetpack ComposeFlutterReact Nativeプリセット自分のデザインシステムを作るAI エージェントと Tanrei を使うブランドデザイントークンデザインシステムとカスタマイズTheme Studioカラータイポグラフィアイコン形状エレベーションモーションインタラクション状態振る舞いレイアウトアクセシビリティ言葉カラートークンタイプスケールトークン形状トークンエレベーショントークンモーショントークン状態トークンスペースと寸法のトークンコンポーネントボタンメインアクションボタンセグメントボタンボタングループメニュー付きボタンツールバーコピーボタンチェックボックスラジオボタンスイッチチップメニュースライダースライダーフィールドカレンダーDateTimePickerフィールドグループラジオグループチェックボックスグループセレクトセレクトフィールドメニューバーファイルアップロードファイルピッカー 上級者向けドロップゾーンテキスト入力欄 上級者向けテキストフィールドチップフィールドフィールドコンボボックス 上級者向けコンボボックスフィールド数値入力欄 上級者向け数値フィールドカラー入力欄 上級者向けカラーフィールドワンタイムコード入力欄 上級者向けワンタイムコードフィールド埋め込みテキスト入力欄 上級者向けチャットコンポーザーカード区切り線モーダルレイヤー 上級者向けダイアログリストリスト行ボトムシートサイドシートカルーセルコードブロックテーブルポップオーバーステップアコーディオン説明リスト図アプリシェルページヘッダーツリービュータイムライン画像プレビューハンドルサイズ変更できる枠スクロールエリアバッジプログレスインジケーターメーターナンバーティッカーローディングインジケータースナックバーツールチップキーボードショートカットタグインラインコードコールアウトアバター空の状態タググループスケルトンファイル項目色見本マーキーテキストタブタブビューアプリバーナビゲーションバーナビゲーションドロワー検索リンクページネーション前後のリンクステッパーパンくずリスト目次リンクカードFAB メニューステータスTanrei の作り方コントリビューションライセンスと帰属
ホーム基礎デザインシステムとカスタマイズ

デザインシステムとカスタマイズ

語彙は 1 つ、デザインシステムは多数。ダイヤルを回す・パレットを宣言する・トークンの参照先を差し替える — 1 コミットで全プラットフォームが追従します。

content_copyMarkdown で表示

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 箇所変えれば全体に波及

デザインシステムは、既定のままで自然に見え、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

ライトFilledTonalElevatedOutlinedTextDisabled
ダークFilledTonalElevatedOutlinedTextDisabled
ダーク · 高コントラストFilledTonalElevatedOutlinedTextDisabled
宣言パレット
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)

ライトFilledTonalElevatedOutlinedTextDisabled
ダークFilledTonalElevatedOutlinedTextDisabled
ダーク · 高コントラストFilledTonalElevatedOutlinedTextDisabled
継承元
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)

ライトFilledTonalElevatedOutlinedTextDisabled
ダークFilledTonalElevatedOutlinedTextDisabled
ダーク · 高コントラストFilledTonalElevatedOutlinedTextDisabled
継承元
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)

ライトFilledTonalElevatedOutlinedTextDisabled
ダークFilledTonalElevatedOutlinedTextDisabled
ダーク · 高コントラストFilledTonalElevatedOutlinedTextDisabled
継承元
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 のアクセシビリティ基準は適用せず、ビルドがそこで計測したコントラスト比は、警告ではなく参考情報として記録します。操作できないコントロールや名前・状態の欠落といった機能上の問題は、模倣プリセットを含め、どのシステムでも不具合です。