振る舞い
押下への応え方と、キーボードフォーカスの見せ方。量ではなく「種類」の違いを、デザインシステムが他のトークンと同じように選びます。
デザインシステム同士の違いは、ほとんどが「量」の違いです。角丸の半径、色のトーン、アニメーションの時間。量は値なので、レイヤで変えられます。
ところが、量では表せない違いが少しだけあります。押したときにリップルが広がるシステムもあれば、コントロール全体が暗くなるシステムもある。フォーカスを硬いリングで示すシステムもあれば、柔らかなグローで示すシステムもある。既存のトークンの数値をどう変えても、一方を他方にすることはできません。Tanrei はこれを種類の選択と呼び、sys.behavior の下に 2 つのトークンとして持っています。それぞれが、短く閉じた選択肢の一覧を持ち、デザインシステムはその中から 1 つを選びます。
| デザインシステム | pressed-feedback | focus-style |
|---|---|---|
tanrei | overlay | ring |
cupertino | dim | ring |
m3-expressive-like | ripple | ring |
m3-like | ripple | ring |
生成元 tokens/systems/*.system.json, design/tokens/<system>/tanrei.light.tokens.json
コンポーネントはこのトークンを読むだけです。いまどのデザインシステムが有効かを調べることはありません。
tanrei.sys.behavior.pressed-feedback
How a press is answered. overlay: the pressed tokens apply to the whole container at once. ripple: the pressed state layer grows from the point of contact. dim: the pressed opacity applies to the whole control and no state layer is drawn. none: no visual answer to a press (focus and hover still apply).
生成元 tokens/sys/behavior.tokens.json
| 値 | 押している間にコンポーネントが描くもの |
|---|---|
overlay |
pressed 状態のトークンを、コンテナ全体に一度に適用します。 |
ripple |
pressed のステートレイヤーが、触れた位置から広がります。キーボードで押した場合は中央から。コンテナの形で切り抜かれます。 |
dim |
コンポーネントの pressed の不透明度をコントロール全体に適用します。ステートレイヤーは描きません。 |
none |
何も描きません。hover とフォーカスの表示は残ります。 |
この選択が決めるのは、pressed のレイヤーがどう現れるかだけです。その色と不透明度は、これまでどおりコンポーネントの状態別トークンが決め、レイヤがその値を与えます。
どれを選んでも、pressed 状態は遅延なく現れ、少なくとも sys.motion.duration.press-hold(既定では 100ms。モーションのダイヤルに追従します)の間は表示され続けます。タップやキー入力は 1 フレームで終わってしまうので、この下限がなければ応答は目に見えません。Button ではこれを要件 BTN-8・BTN-9 として定めています。
角が直線部分へどうつながるか(四分円か、連続曲率か)は種類の選択に見えますが、量です。Figma の角のスムージングと同じ 0〜1 の値で、0.6 が iOS の角にあたります。半径と同じ場所、sys.shape.corner-smoothing にあり、形状のページで説明しています。
tanrei.sys.behavior.focus-style
How keyboard focus is shown. ring: the two-tone focus ring at sys.focus offset and width. glow: the same colors as a soft halo (blurred by the ring width) instead of a hard line. Both must meet the focus contrast invariants.
生成元 tokens/sys/behavior.tokens.json
glow も、リングと同じ 2 色・同じオフセット・同じ幅で描かれます。そのためフォーカスのコントラスト不変条件はそのまま適用されます。変わるのは表示の輪郭の硬さであって、表示の有無ではありません。
同じ Button を、同梱の各システムのもとで描いています(インストールされている Web ビルドによる描画です)。それぞれ押してみて、次に Tab でフォーカスを当ててみてください。
押して、Tab で移動
tanrei overlay · ring
cupertino dim · ring
m3-expressive-like ripple · ring
m3-like ripple · ring
選択も、他の値と同じように変えます。tokens の下に、reason を添えて書きます。
{
"id": "acme",
"extends": "tanrei",
"tokens": {
"tanrei.sys.behavior.pressed-feedback": { "$value": 1, "reason": "Acme は押下にリップルで応える。" },
"tanrei.sys.behavior.focus-style": { "$value": 1, "reason": "Acme はキーボードフォーカスをグローで示す。" }
}
}$value は数値です。そのトークンの一覧の中での位置を、0 から数えたものです。pressed-feedback の一覧は overlay・ripple・dim・none なので、1 は ripple を指します。Tanrei のトークンファイルの形式(DTCG 2025.10)には文字列型がないため、ファイルには番号を書きます。一覧の範囲外の番号はビルドが拒否します。
番号が現れるのはレイヤファイルの中だけです。生成される CSS には名前が入り(--tanrei-sys-behavior-pressed-feedback: ripple)、SwiftUI・Compose・Flutter・React Native はシステムインスタンス上の型付き列挙として受け取ります。
一覧そのものはコアの語彙です。レイヤは名前の中から選ぶことはできますが、名前を足すことはできません。新しい振る舞いはすべてのプラットフォームが描けなければならないので、他の語彙の変更と同じく、コアへの提案として扱います。
各一覧の先頭の名前が既定値であり、フォールバックでもあります。ある選択を描けないプラットフォームは、代わりに先頭の値で描き、そのことをパッケージの README に明記します。
ノート
番号を手で書くのは扱いにくいので、Theme Studio(「ふるまい」グループ)、tanrei explain、MCP サーバーが名前を表示し、名前で受け付けます。