---
title: "デザインシステムとカスタマイズ"
description: "語彙は 1 つ、デザインシステムは多数。ダイヤルを回す・パレットを宣言する・トークンの参照先を差し替える — 1 コミットで全プラットフォームが追従します。"
url: "https://tanrei.chnotchy.com/ja/foundations/design-systems/"
last_updated: 2026-10-23T06:11:40.000Z
---

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

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

デザインシステムは、既定のままで自然に見え、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 ファイルです。ほとんどは値を変えるだけで、必要なときだけ[語彙に足します](https://tanrei.chnotchy.com/ja/foundations/design-systems/#%E8%AA%9E%E5%BD%99%E3%81%AB%E8%B6%B3%E3%81%99)。

```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`（理由）を添えて、追加だけを行います。

```json
"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` など）を持ち、システムの**パターンルール**がその値を決めます。

```json
{ "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 つを選びます。角の曲がり方は選択ではなく量です。[形状](https://tanrei.chnotchy.com/ja/foundations/shape/#%E8%A7%92%E3%81%AE%E3%82%B9%E3%83%A0%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0)の `sys.shape.corner-smoothing` を参照してください。詳しくは[振る舞い](https://tanrei.chnotchy.com/ja/foundations/behaviour/)を参照してください。アイコンセットと書体もレイヤで変えられます。[アイコン](https://tanrei.chnotchy.com/ja/foundations/icons/#%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%94%E3%81%A8%E3%81%AE%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%82%BB%E3%83%83%E3%83%88)と[タイポグラフィ](https://tanrei.chnotchy.com/ja/foundations/typography/#%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%94%E3%81%A8%E3%81%AE%E6%9B%B8%E4%BD%93)を参照してください。

## 同梱されるシステム

同じ `<tanrei-button>`、同じマークアップ。`m3-like` と `cupertino` は、性格が正反対の 2 つのシステムでこの仕組みを確かめるため、そして自分のレイヤを書くときの出発点として同梱しています。

**完全一致は「主張」ではなく「計測」です。** `m3-like` は material-web（Apache-2.0）が公開しているトークンへの忠実度マップを持ち、参照元の全カラーロール、全テキストスタイル、Button の全トークンが「一致」「他のトークンに包含」「理由（mimicry gap）つきで未対応」のいずれかでなければテストが落ちます。数値は下のプリセット紹介に表示されます。他のトークンに包含されるものを除いた件数のうちの一致数で、`tokens/generated/<id>/fidelity.json` から読まれます。名前が「-like」なのは商標上の理由であって、近似だからではありません。

Web ではプリセットは属性でスコープされた追加のスタイルシート 1 枚なので、複数のシステムを同じページに並べることもできます。そのプリセットが持つコンポーネントは、プリセットのエントリーが登録します。既定のシステムが持たないものもここに含まれます（`@tanrei/web` が登録するのは既定のシステムのコンポーネントだけです）。

```js
import '@tanrei/web/tokens.css';
import '@tanrei/web/systems/cupertino.css';
import '@tanrei/web/cupertino'; // cupertino が持つコンポーネント
```

```html
<body data-tanrei-system="cupertino">
```

値でも種類の選択でもまだ表現できないもの（背景をぼかすマテリアル、スタイルとしての大文字ラベルなど）は、隠さずに Tanrei 側の欠陥として `docs/governance/mimicry-gaps.json` に登録しています。

## システムにないコンポーネント

システムは、コンポーネントを丸ごと外すことも、コンポーネントの `variant` の値を 1 つだけ外すこともできます。既定のシステムには segmented button がなく、テキストフィールドの filled と outlined もありません。レイヤはそれを `components` に、理由と代わりに使うものと一緒に書きます。

```json
"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 など）も同じように、それがあるレイヤとともに報告されます。

テストで失敗させるには、エラーとして報告させ、何が使われたかを尋ねます。

```js
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 エージェント）が再構築・変更できます。

> [!WARNING]
> **アクセシビリティ**
>
> コントラストの閾値は、誰にも変更できない定数です。デフォルトのシステムでは違反はビルド失敗になります。プロダクトのレイヤでは違反は**警告**になります — ビルド時に表示され、生成物にも書き込まれ、黙殺はされません。他のシステムを模倣するプリセットは、代わりに模倣元への忠実さで評価します。標準・中・高のどのコントラストでも Tanrei のアクセシビリティ基準は適用せず、ビルドがそこで計測したコントラスト比は、警告ではなく参考情報として記録します。操作できないコントロールや名前・状態の欠落といった機能上の問題は、模倣プリセットを含め、どのシステムでも不具合です。
