Skip to content

デザインシステム

ranui が拠って立つデザイン言語と、それを表すトークンの完全なカタログです。ライブラリが宣言するすべてのグローバルな --ran-* カスタムプロパティを、両テーマでの値つきで載せています。コンポーネントは値を直に書く代わりにこれらを読むので、トークンをひとつ上書きすれば、それを使うものすべての見た目が変わります。

3 つのページが 3 つの異なる問いに答えます。それらはあえて分けてあります。

ページ答えること
デザインシステム(このページ)トークンが_何か_、つまり語彙
デザインガイドライン画面を作るとき、その中から_どう選ぶか_
テーマ実行時に_どう切り替え、どう上書きするか_

こんなときに:トークンの名前や値(色の役割、余白の段階、アイコンの大きさ、影の段階、イージングのカーブ)が必要なとき、あるいはスケールがなぜこの形をしているのかを知りたいとき。

言語:Geist

ranui のトークンは、Vercel のオープンソースなデザインシステム Geist を土台にしています。どの色スケールも、選ぶための濃淡の集まりではなく、段ごとに役目が定まったはしごです。200 段は「少し暗いグレー」ではなく「ホバーの背景」です。段の役目がいったん定まれば、操作の状態に対する色を選ぶことは判断ではなく引き当てになります。

ranui はそのはしごを --ran-* のスケールとして取り入れ、その上にセマンティックトークンを重ね、既定の書体として Geist Sans / Geist Mono を同梱しています。

ふたつの層

第 1 層:基礎パレット。 下に並ぶ生のスケールです。直接使うことは滅多にありません。

第 2 層:セマンティックトークン。 --ran-color-* とその仲間で、第 1 層の上に対応づけられています。使うのはこの層です。 ダークモードが再定義するのは第 1 層だけなので、すべてのセマンティックトークンは var() を通じて切り替わり、ライブラリのどこにもコンポーネントごとのダーク用の上書きはありません。

--ran-gray-1000        →  #171717(ライト) / #ededed(ダーク) ← 第 1 層。切り替わる
--ran-color-text       →  var(--ran-gray-1000)                    ← 第 2 層。追随する
--ran-btn-color        →  var(--ran-color-text, …)                ← コンポーネントのトークン

この連鎖がアーキテクチャのすべてです。基礎の段を変えればどこにでも伝わり、セマンティックトークンを変えればひとつの役割が変わり、コンポーネントのトークンを変えればひとつの要素が変わります。

はしご

どの色相のスケールも 100 → 1000 で走り、段ごとに役目がひとつ定まっています。

役目役目
100既定の背景600アクティブの境界線
200ホバーの背景700ベタ塗り(ボタン/バッジ)
300アクティブ(押下)の背景800ベタ塗り(ホバー)
400既定の境界線900副次のテキストとアイコン
500ホバーの境界線1000主要なテキストとアイコン

背景

トークンライトダーク用途
--ran-background-100 #ffffff #000000ページの背景
--ran-background-200 #fafafa #000000控えめな区画

グレー — --ran-gray-100..1000

テキスト、境界線、面の背後にあるスケールです。

ライトダーク
100 #f2f2f2 #1a1a1a
200 #ebebeb #1f1f1f
300 #e6e6e6 #292929
400 #eaeaea #2e2e2e
500 #c9c9c9 #454545
600 #a8a8a8 #878787
700 #8f8f8f #8f8f8f
800 #7d7d7d #7d7d7d
900 #4d4d4d #a0a0a0
1000 #171717 #ededed

グレー(アルファ) — --ran-gray-alpha-100..1000

半透明なので、どんな面の上にも重ねられます。覆いの膜、ホバーの淡い色、そして何が下にあるか分からない場所に置く区切り線には、これが正解です。

ライトダーク
100 #0000000d #ffffff12
200 #00000015 #ffffff17
300 #0000001a #ffffff21
400 #00000014 #ffffff24
500 #00000036 #ffffff3d
600 #0000003d #ffffff82
700 #00000070 #ffffff8a
800 #00000082 #ffffff78
900 #000000b3 #ffffff9c
1000 #000000e8 #ffffffeb

ブルー — --ran-blue-100..1000

リンクとフォーカスリングのために取ってあります。

ライトダーク
100 #f0f7ff #06193a
200 #e9f4ff #022248
300 #dfefff #002f62
400 #cae7ff #003674
500 #94ccff #00418b
600 #48aeff #0090ff
700 #006bff #006efe
800 #0059ec #005be7
900 #005ff2 #47a8ff
1000 #002359 #eaf6ff

レッド — --ran-red-100..1000

危険とエラーです。

ライトダーク
100 #ffeeef #330a11
200 #ffe8ea #440d13
300 #ffe3e4 #5d0e17
400 #ffd7d6 #6f101b
500 #ffb1b3 #88151f
600 #ff676d #f32e40
700 #fc0035 #f13242
800 #ea001d #e2162a
900 #d8001b #ff565f
1000 #47000c #ffe9ed

アンバー — --ran-amber-100..1000

警告です。

ライトダーク
100 #fff6de #2a1700
200 #fff4cf #361900
300 #fff1c1 #502800
400 #ffdc73 #5b3000
500 #ffc543 #703e00
600 #ffa600 #ed9a00
700 #ffae00 #ffae00
800 #ff9300 #ff9300
900 #aa4d00 #ff9300
1000 #561900 #fff3d5

グリーン — --ran-green-100..1000

成功です。

ライトダーク
100 #ecfdec #002608
200 #e5fce7 #00320b
300 #d3fad1 #003a0e
400 #b9f5bc #004615
500 #82eb8d #006717
600 #4ce15e #00952d
700 #28a948 #00ac3a
800 #279141 #009432
900 #107d32 #00ca50
1000 #003a00 #d8ffe4

セマンティックな色トークン

コンポーネントが実際に読む層です。ここにあるものはすべて上のスケールを通じて解決されるので、テーマに合わせて自分で切り替わります。

トークン解決先役割
--ran-color-bg--ran-background-100ページの背景
--ran-color-bg-subtle--ran-background-200控えめな区画
--ran-color-bg-elevated--ran-background-100 · gray-100 (ダーク)カード、面
--ran-color-bg-muted--ran-gray-100沈んだ/控えめな塗り
--ran-color-bg-hover--ran-gray-200ホバーの面
--ran-color-bg-active--ran-gray-300アクティブ(押下)の面
--ran-color-text--ran-gray-1000主要なテキスト
--ran-color-text-secondary--ran-gray-900副次のテキスト
--ran-color-text-disabled--ran-gray-700無効なテキスト
--ran-color-border--ran-gray-400既定の境界線
--ran-color-border-secondary--ran-gray-300より控えめな境界線
--ran-color-border-hover--ran-gray-500ホバーの境界線
--ran-color-border-active--ran-gray-600アクティブの境界線
--ran-color-primary--ran-gray-1000主たるアクション(モノクロ)
--ran-color-primary-hover #383838 · #cccccc (ダーク)primary のホバー
--ran-color-primary-active #4d4d4d · #b3b3b3 (ダーク)primary の押下
--ran-color-primary-text--ran-background-100primary の面の上に乗るインク
--ran-color-success--ran-green-700成功
--ran-color-warning--ran-amber-700警告
--ran-color-danger--ran-red-700危険/エラー
--ran-color-link--ran-blue-700リンク

--ran-color-primary-hover / -active は、セマンティック層にあるふたつのリテラルです。スケールに沿ってではなくページの背景に向かって進むので、ダークモードではこれらを直接定義し直します。

アクセントそれぞれの意味

  • primary はモノクロです:ライトでは白地に黒、ダークでは黒地に白(Geist のブランドの調子で、<r-button type="primary">)。その上のテキストとアイコンは --ran-color-primary-text を使い、これも一緒に切り替わります。別に「コントラスト」のトークンはありません。primary こそが 最もコントラストの高いアクションです。
  • ブルーは取ってあります:リンク(--ran-color-link)とフォーカスリングのためです。primary の代わりではありません。
  • グリーン=成功 · アンバー=警告 · レッド=危険。 それぞれ意味はひとつです。

--ran-color-error はありません。トークンは --ran-color-danger です。宣言されていないプロパティを指す var() は何にも解決されず、宣言まるごとが黙って捨てられます。だから名前を間違えたときは、当て推量ではなくこの表と突き合わせる価値があります。

余白

ものとものの間隔、つまり paddingmargingap です。基本単位は 4px で、値は 9 つ、それ以上はありません。

トークントークン
--ran-space-14px--ran-space-832px
--ran-space-28px--ran-space-1040px
--ran-space-312px--ran-space-1664px
--ran-space-416px--ran-space-2496px
--ran-space-624px

数字は 4px の倍数なので、スケールは飛び飛びです。--ran-space-5 はありません。そこが肝心で、ページのリズムを生むのは限られた選択肢のほうです。

寸法

要素そのものの大きさです。アイコンの大きさ、コントロールの高さ、小さな正方形や長方形のコントロールなど。

トークン典型的な用途
--ran-size-116pxチェックボックスの箱、小さな行内アイコン
--ran-size-218px
--ran-size-320pxコントロール内のアイコン
--ran-size-424pxツールバーのアイコンボタン
--ran-size-528px詰まったコントロールの高さ
--ran-size-630px
--ran-size-732px既定のコントロールの高さ

これは意図的に余白とは別のスケールです。 混ぜて使うのは機械が検出するエラーです(sizing-scale)。ふたつは範囲も刻み方も違います(4px を倍にしていく余白のスケールは、アイコンやコントロールの大きさとしては据わりの悪い値になります)。そして利用者は、片方を調整しても、もう片方を乱さずにいられなければなりません。アイコンが大きくなったからといって、たまたま同じピクセル値を共有していた余白まで広がってしまってはいけないのです。段が余白の段と数字の上で一致する場合(--ran-size-4--ran-space-6 はどちらも 24px です)、それは偶然であって別名ではありません。

ほかのどのコンポーネントとも共有しない、本当に一回きりの寸法(たとえばメニューの min-width)は、無理に段へ押し込まず、自前のリテラルなフォールバックを持つ素のコンポーネントトークンのままにします。

タイポグラフィ

トークン
--ran-font-familyGeist / Geist Sans、次いでシステム UI のスタック
--ran-font-monoGeist Mono、次いで ui-monospace、SF Mono、Menlo、Consolas…
--ran-font-size14px(基準の大きさ)
--ran-line-height1.5715

文字は役割で整理され、役割がフォント・大きさ・太さ・行間をまとめて決めます。

役割用途太さのトークン大きさのトークン
heading見出し--ran-text-heading-weight (600)--ran-text-heading-1..4 (32/24/20/16px)
label一行で、目で追えるもの--ran-text-label-weight (500)--ran-text-label-1..3 (14/13/12px)
copy複数行の本文--ran-text-copy-weight (400)--ran-text-copy-1..2 (16/14px)
buttonボタンの文字--ran-text-button-weight (500)--ran-text-button-size (14px)
monoコード、データ、小見出し--ran-text-mono-weight-regular (400) / --ran-text-mono-weight-medium (500)label / copy の大きさを借ります

役割をきちんと着地させるためだけに、ふたつのトークンがあります。

トークン理由
--ran-text-heading-tracking-0.03em大きな表示サイズでは、見出しに詰めた字間が要ります。
--ran-text-button-line-height1高さの決まったコントロールの中で、縦位置をきれいに揃えます。

Geist は太さの上限が 600(セミボールド)です。強調は大きさと余白から生まれるもので、より太い書体からではありません。--ran-text-copy-3 はありません。12px の段は --ran-text-label-3 です。

フォント

ranui はどちらの書体も自前でホストしています(可変ウェイト 100〜900、SIL OFL 1.1)。だから import ひとつで、CDN への依存なしに読み込めます。

js
import 'ranui/fonts'; // バンドラー向け
html
<link rel="stylesheet" href="…/ranui/dist/fonts/fonts.css" />

これがなければトークンはシステムのフォントスタックへフォールバックします。すべて問題なく動きますが、Geist の書体にはなりません。

角丸

トークン用途
--ran-radius-sm6pxコントロール:ボタン、入力、セレクト
--ran-radius-md12pxカード、ダイアログ
--ran-radius-lg16px大きな面
--ran-radius-full9999pxピル、アバター

影の高さ

影は装飾ではなく役割です。段階は、その要素が何であるかで選びます。ダークモードでは 3 つとも差し替えられます。白いページに合わせて調整した影は、黒いページでは消えてしまうからです。

トークン用途ライトダーク
--ran-shadow-elevated流れの中にあって境界線も持つ面:r-cardr-section0 1px 2px rgba(0,0,0,.04), 0 2px 4px -2px rgba(0,0,0,.05)0 1px 2px rgba(0,0,0,.16)
--ran-shadow-menu内容の上に一時的に重なる層:ドロップダウン、セレクト、ポップオーバー、トースト0 2px 4px rgba(0,0,0,.05), 0 8px 24px -6px rgba(0,0,0,.14)0 1px 1px rgba(0,0,0,.2), 0 4px 8px -4px rgba(0,0,0,.4), 0 16px 24px -8px rgba(0,0,0,.5)
--ran-shadow-modal行く手をふさぐダイアログ:r-modal0 4px 12px rgba(0,0,0,.08), 0 20px 48px -12px rgba(0,0,0,.22)0 1px 1px rgba(0,0,0,.2), 0 8px 16px -4px rgba(0,0,0,.4), 0 24px 32px -8px rgba(0,0,0,.5)

境界線のないオーバーレイは、周囲との切り分けを影だけに頼ります。だからオーバーレイの段階には本当に重みがあります。オーバーレイが持ち上げの段階に落ちてしまうと、平らでページに貼り付いて見えます。

重なり

浮くオーバーレイは <body> へポータルされるので、明示的な段階が要ります。

トークン既定値用途
--ran-z-modal1000行く手をふさぐダイアログとそのマスク
--ran-z-dropdown1100ドロップダウン/セレクト/ポップオーバー:モーダルの。ダイアログの中のセレクトが見えたままになります
--ran-z-message1200トーストと通知:常に最前面

はしごが 1000 から始まるのは、普通のページの外枠を越えるためです(ナビゲーションバーや背景は、たいてい十の位に置かれます)。段階を上書きするときは :root で、あるいはコンポーネントごとに(--ran-dropdown-host-z-index--ran-modal-root-z-index--ran-message-z-index)行い、!important は決して使わないでください。

モーション

トークン用途
--ran-motion-duration-fast0.15sホバー/アクティブの状態遷移
--ran-motion-duration-base0.2sポップオーバー、メニュー
--ran-motion-duration-slow0.35sもっと大きな現れ方
イージングのトークンカーブ性格
--ran-motion-ease-standardcubic-bezier(0.645,0.045,0.355,1)イン・アウト。汎用
--ran-motion-ease-snappycubic-bezier(0.33,0,0.15,1)素早く、行き過ぎなし:トグル
--ran-motion-ease-springcubic-bezier(0.34,1.26,0.5,1)わずかに行き過ぎ:ボタン、カード
--ran-motion-ease-bouncycubic-bezier(0.34,1.56,0.64,1)遊びのある行き過ぎ:いいね、カートに追加
--ran-motion-ease-smoothcubic-bezier(0.4,0,0.2,1)穏やかで行き過ぎなし:現れ、レイアウト

spring 系は、調整済みの SwiftUI のばねから蒸留したものです(response / damping を、一度だけ行き過ぎるベジェに落とし込んでいます)。

これらは動きのプロパティとだけ組み合わせてくださいtransformopacity、箱の寸法です。パレットのプロパティ(background-colorcolorborder-colorbox-shadowfillstroke)には、あえて既定のトランジションを付けていません。CSS は操作とテーマの切り替えを区別できないからです。色に付けたフェードは、ライトとダークが入れ替わるときにも発火します。それでも自分で有効にしたいときのために、どのコンポーネントも --ran-*-transition のフックを公開しています。

フォーカス

トークン用途
--ran-focus-ring0 0 0 2px var(--ran-background-100), 0 0 0 4px var(--ran-blue-700)標準のリング。box-shadow として
--ran-focus-ring-inverse-color#fffどちらの テーマでも暗いままの面のための、リングの色

リングは 2 層です。背景色の内側のリングと、青い外側のリング。だからどんな面の上でも見えたままになりますし、モノクロになった primary に追随せず青のままです。

--ran-focus-ring-inverse-colorあえてダークモードで再定義していません。ページのテーマに関わらず自分の面が暗いままのコンポーネント(任意の映像の上に重なる r-player のコントロールバー)のためにあり、その面はページが変わっても変わらないからです。

スキンのプリミティブ

コンポーネントが共有する構造的な値のうち、色でも寸法でも文字でもない、ごく少数のものです。あえて最小限に保っています。この層はかつてもっと大きく、そのほとんどはテーマパックとともに取り除かれました。

トークン用途
--ran-skin-border-width1pxコンポーネントが描く境界線の太さ
--ran-skin-border-stylesolidコンポーネントが描く境界線の種類
--ran-skin-border-image-width4pxborder-image-slice のインセット。button / checkbox / input / modal / message が共有します
--ran-skin-raised-shadowvar(--ran-shadow-elevated)持ち上げた面の影。スキンが変えられるよう間接参照にしてあります
--ran-skin-font-familyvar(--ran-font-family)コンポーネントが使う書体。同じように間接参照にしてあります

ダークモードが再定義するもの

<html>(あるいは任意のサブツリー。テーマを参照)に付いた data-ran-theme="dark" が再定義するのは、基礎パレットだけです。ただし、スケールを通じては解決できない例外が 3 つあります。

  • 第 1 層のすべて:グレー、グレー(アルファ)、ブルー、レッド、アンバー、グリーンの全段と、ふたつの背景。
  • --ran-color-bg-elevated。ダークでは --ran-gray-100 を指し、カードが黒いページに溶けるのではなく浮き上がるようにします。
  • --ran-color-primary-hover / -active。スケールの参照ではなくリテラルだからです。
  • 影の 3 段階すべて。暗い地に合わせて調整し直されます。

それ以外(ほかのすべてのセマンティックトークン、すべての寸法、すべての時間)は一度だけ定義されます。

コンポーネントのトークン

セマンティック層の下では、どのコンポーネントも自前のフックを、こう名づけて公開しています。

--ran-{component}-{element}[-{state}]-{property}

たとえば --ran-btn-hover-background--ran-select-search-active-border-width です。既定ではセマンティックトークンへ落ちます:var(--ran-btn-background, var(--ran-color-primary, #171717))。だからセマンティックトークンをひとつ上書きすればそのすべてに届き、コンポーネントのトークンを上書きすれば変化はひとつの要素に絞られます。

生成された完全な一覧は、リポジトリの style-tokens-public.md にあります。要素ごとの API はこちらです。適用のしかたはテーマを参照してください。

自分の CSS でトークンを使う

css
.panel {
  background: var(--ran-color-bg-elevated);
  color: var(--ran-color-text);
  border: var(--ran-skin-border-width) var(--ran-skin-border-style) var(--ran-color-border);
  border-radius: var(--ran-radius-md);
  padding: var(--ran-space-4);
  box-shadow: var(--ran-shadow-elevated);
}

3 つのルールが、それをダークでも安全に保ちます。

  1. テーマに追随すべきものに生の 16 進数を書かないこと
  2. フォールバックは切り替わるトークンを指すことvar(--ran-color-text, var(--ran-gray-1000)) であって、var(--ran-color-text, #171717) ではありません。
  3. フォールバックは存在するトークンを指すこと。さもないと宣言は捨てられ、要素は継承したものを黙って保ち続けます。

ライブラリが宣言するグローバルなトークンはすべてこのページに載っており、ここに書かないままトークンを増やすとユニットテストが落ちます。コンポーネント単位のトークンは別途生成され、style-tokens-public.md にあります。

MIT ライセンスのもとで公開されています。