カラー
zudo-docの3層カラー戦略、ランプネイティブなカラーモデル、カラースキーム、カスタマイズ。
zudo-docは3層カラー戦略を採用し、サイト上のすべての色をテーマ切り替え可能にしています。パッケージの@takazudo/はTailwindのデフォルトカラーテーマをインポートせず、その@themeブロックで--color-*: initialとしてカラー名前空間をリセットしてから同梱トークンを定義します。プロジェクト側の後続の@themeブロックはオーバーライドポイントです。これにより、カラースキームを切り替えるだけでサイト全体が一括で更新されます。
3層カラー戦略
カラーは3つの層で構成されています。各層は上位の層のみを参照します:
| 層 | 名前 | 目的 | 定義場所 |
|---|---|---|---|
| 1 | ランプ | スキームの土台となる、共有される OKLCH カラーランプ | zfb.config.ts の自作 colorSchemes 上書き(任意 — 未設定ならパッケージのデフォルト)→ ColorSchemeProvider → :root |
| 2 | セマンティック | デザイン上の意味 — 各色がUIで何を表すか | パッケージの@takazudo/ @theme(プロジェクト側の後続の@themeブロックでオーバーライド可能) |
| 3 | コンポーネント | 特定コンポーネント向けのスコープ付きオーバーライド | .zd-content(共有 content.css) |
この階層構造により:
ランプストップの再調整(
zfb.config.tsのcolorSchemesフィールドを上書き)→ それを土台とするすべてのセマンティックロールが更新セマンティックロールの再マッピング(例:
accentを別のランプストップに向ける)→accentを使用するすべてのコンポーネントが更新コンポーネントトークンのオーバーライド → 他のコンポーネントに影響なし
Tier 1:ランプ
zudo-docのカラーエンジンはランプネイティブです。ColorSchemeは{ ramps, map }です — この型は@takazudo/から提供されます。
ramps— 共有されるソースオブトゥルース:暖色ニュートラルなbaseランプ(5 ストップ、インデックス0が最も明るい)、accentランプ(3 ストップ)、4 つのstateカラー(danger、success、warning、info)。ライトモードとダークモードはこれらのランプ値を共有します。map— モードごとの紐付け:各 UI ロールがどのランプストップ(またはリテラル OKLCH オーバーライド)を指すか。
共有ランプ
| ランプ | ストップ | CSS カスタムプロパティ |
|---|---|---|
base | 5 — インデックス0(最も明るい)→ 4(最も暗い) | --palette-base-0 … --palette-base-4 |
accent | 3 | --palette-accent-0 … --palette-accent-2 |
state | 4 つの名前付きロール | --palette-state-danger、--palette-state-success、--palette-state-warning、--palette-state-info |
Default LightとDefault Dark — 同梱されるスキームはこの 2 つだけ — は、まったく同じbase/accent/stateのランプ値(パッケージの@takazudo/内の 1 つのrampsオブジェクト)を共有し、両者の違いはモードごとのmapだけです。
Note
ランプサイズの最小化は意図的なものです。5 つの base ストップと 3 つの accent ストップには、「予備」となるニアホワイトやニアブラックのフィラートーンを置く余地がありません。いくつかのセマンティックロールは、それぞれ独自のストップを持つのではなく、意図的に同じストップにマージされています — 後述のロールマージの設計思想を参照してください。
RampRef — ロールがランプストップを指す仕組み
type RampRef =
| { base: number } // ramps.base[n]
| { accent: number } // ramps.accent[n]
| { state: StateRole } // ramps.state[role]
| string; // a literal OKLCH value, used as-isスキームのmapは、4 つの base ロール(bg、fg、selectionBg、selectionFg)と 23 のセマンティックロールすべてにRampRefを使用します。ほとんどのロールは共有ランプストップを参照しますが、一部は代わりにリテラル OKLCH 文字列を持ちます — これは、共有ストップでは作り出せない色をロールが必要とする場合(通常はモードごとの AA コントラスト調整)に使われます。モードごとのリテラルオーバーライドを参照してください。
ランプの注入方法
ColorSchemeProviderコンポーネント(packages/)がアクティブなスキームを読み取り、ビルド時に:rootへ CSS カスタムプロパティ — 解決済みの base ロール、素のランプストップ、解決済みの 23 のセマンティックロール — を注入します:
:root {
--zd-bg: oklch(.185 .005 65);
--zd-fg: oklch(.965 .004 65);
--zd-selection-bg: oklch(.480 .008 65);
--zd-selection-fg: oklch(.965 .004 65);
--palette-base-0: oklch(.965 .004 65);
--palette-base-1: oklch(.705 .008 65);
/* ... through --palette-base-4 */
--palette-accent-0: oklch(.755 .130 64);
/* ... through --palette-accent-2 */
--palette-state-danger: oklch(.640 .170 25);
--palette-state-success: oklch(.680 .145 145);
--palette-state-warning: oklch(.760 .135 82);
--palette-state-info: oklch(.680 .130 245);
--zd-accent: oklch(.700 .158 62);
--zd-code-bg: oklch(.300 .006 65);
/* ... 21 more --zd-{role} semantic properties */
}--zd-*プロパティ(base ロール + セマンティックロール)は、下流のすべてが帰結するソースオブトゥルースです。素の--palette-*ランププロパティは、Design Token PanelのPaletteタブとスキーム作者のために存在します — コンテンツとコンポーネントはセマンティックな--zd-*トークンを参照します。生のランプストップ用の Tailwind ユーティリティはありません(生のランプストップへのアクセスを参照)。
Tier 2:セマンティックトークン
23 のセマンティックロールが、コンポーネントが実際に使用するカラーの面です。パッケージの@takazudo/が、それら(および 4 つの base ロール)を Tailwind 互換トークンにマッピングします。スキャフォールドではこのファイルを独自のsrc/にある@themeオーバーライドブロックより前にインポートするため、変更が必要なエイリアスだけを再定義してください:
@theme {
--color-*: initial; /* reset ALL Tailwind defaults */
/* Base */
--color-bg: var(--zd-bg);
--color-fg: var(--zd-fg);
--color-sel-bg: var(--zd-selection-bg);
--color-sel-fg: var(--zd-selection-fg);
/* Semantic aliases */
--color-surface: var(--zd-surface);
--color-muted: var(--zd-muted);
--color-accent: var(--zd-accent);
--color-accent-hover: var(--zd-accent-hover);
--color-code-bg: var(--zd-code-bg);
--color-code-fg: var(--zd-code-fg);
--color-success: var(--zd-success);
--color-danger: var(--zd-danger);
--color-warning: var(--zd-warning);
--color-info: var(--zd-info);
/* Search highlight (dedicated, live-editable in Design Token Panel) */
--color-matched-keyword-bg: var(--zd-matched-keyword-bg);
--color-matched-keyword-fg: var(--zd-matched-keyword-fg);
}@themeに登録されると、標準のTailwindユーティリティクラスとして使用できます:bg-surface、text-accent、border-mutedなど。
セマンティックトークンリファレンス(Default Dark の紐付け)
どちらのスキームも 23 のロールすべてを紐付けます。下の表は Default Dark のmap.semantic — パッケージの@takazudo/で提供される、作り込まれたリファレンススキーム — を示します。Default Light はこれらのいくつかを別のランプストップやモードごとのリテラルに再度向けます。モードごとのリテラルオーバーライドを参照してください。
| トークン | デフォルトのランプ参照 | 用途 |
|---|---|---|
bg | { base: 4 } | ページ背景 |
fg | { base: 0 } | メインテキスト |
surface | { base: 4 }(= bg) | パネル/サイドバーのサーフェス — bgにマージ、ロールマージの設計思想を参照 |
muted | { base: 1 } | ミュートテキスト、ボーダー、コメント |
accent | { accent: 1 } | リンク、アクティブ状態、CTA |
accentHover | { accent: 0 } | accentのホバー状態 |
codeBg | { base: 3 } | コードブロック背景 |
codeFg | { base: 0 } | インラインコードテキスト |
success | { state: "success" } | 成功状態、確認 |
danger | モードごとのリテラル | エラー、破壊的操作 |
warning | { state: "warning" } | 警告メッセージ、Admonition、find-in-pageハイライト |
info | { state: "info" } | 情報ハイライト |
mermaidNodeBg / mermaidText / mermaidLine / mermaidLabelBg / mermaidNoteBg | @takazudo/を参照 | Mermaid ダイアグラムの色 |
chatUserBg / chatUserText | { accent: 1 } / { base: 4 } | AI チャットのユーザーバブル |
chatAssistantBg / chatAssistantText | { base: 4 }(= bg) / { base: 0 } | AI チャットのアシスタントバブル — bgにマージ |
imageOverlayBg / imageOverlayFg | { base: 4 } / { base: 0 } | 画像拡大オーバーレイ — bgにマージ |
matchedKeywordBg / matchedKeywordFg | 共有リテラル | 検索結果の<mark> — 専用トークン、Design Token Panel でライブ編集可能 |
シンタックスセマンティクス
ModeMap.syntax は、9 つのシンタックス固有ロールを持つ optional な partial map です。map 自体がない場合も、個別ロールを省略した場合も、下表の既存セマンティックロールを継承します。そのため、シンタックストークン導入前に作成されたスキームや v3 トークン JSON も引き続き有効で、名前付き variation や一方の mode では必要なロールだけを上書きできます。
ModeMap.syntax のキー | CSS カスタムプロパティ | 継承元 | zfb レンダラーのロール |
|---|---|---|---|
syntaxComment | --zd-syntax-comment | muted | comment |
syntaxString | --zd-syntax-string | success | string, escape |
syntaxNumber | --zd-syntax-number | warning | number, constant |
syntaxKeyword | --zd-syntax-keyword | accent | keyword, heading |
syntaxCallable | --zd-syntax-callable | info | function |
syntaxType | --zd-syntax-type | warning | type, namespace |
syntaxName | --zd-syntax-name | codeFg | property, variable, tag, attribute |
syntaxInserted | --zd-syntax-inserted | success | inserted |
syntaxDeleted | --zd-syntax-deleted | danger | deleted |
残る zfb ロール(operator と punctuation)は codeFg を使用します。この圧縮により、zfb の 18 レンダラーロールをすべて公開カラー判断にせず、コンポーネントレベルの詳細として保てます。inserted/deleted の背景もコンポーネントトークンです。2 つの公開ロールを追加する代わりに、対応するシンタックス前景色を color-mix() で codeBg へ 15% 混ぜて生成します。
たとえば、一方の mode で keyword だけを上書きできます:
const darkMap: ModeMap = {
// ...base と semantic のマッピング...
syntax: {
syntaxKeyword: { state: "info" },
},
};Provider は解決済みの 9 つの --zd-syntax-* 変数をすべて出力します。ネイティブなコードフェンス出力と HtmlPreview の遅延 WASM 出力は、どちらも zfb の --zfb-hi-* コンポーネントブリッジを通してそれらを使用します。ブリッジは var() 参照を維持するため、スキーム切り替えや Design Token Panel の編集では、ハイライトを再実行せず既存の hi-* DOM が再配色されます。出荷する前景色/背景色の組み合わせは、実際に描画される inserted/deleted の tint を含め、4.6:1 以上のコントラスト比で検証されます。
ロールマージの設計思想
パレットは、区別すべきトーンの数を小さく保つために意図的に最小化されています。いくつかのロールは、それぞれ独自のトーンを持つ代わりに、共有ストップにマージされています:
surface、codeBg(ライトモード)、chatAssistantBgはbgに統合されます。 ヘッダーのバージョンボックス、ドキュメントカード、チャットアシスタントのバブルは、ページ背景 + ボーダーのみでレンダリングされ、別途のグレーの塗りはありません。5 つの base ストップには、繊細なニアホワイト(ライトモード)やニアブラック(ダークモード)のフィラートーンを置く余地がありません。 それを作り出すためにランプを引き伸ばすのではなく、浮上サーフェスは設計上ボーダーのみとしています。
結果として、同期を保つべきトーンが減り、パネル・カード・チャットバブル全体でよりフラットで一貫したビジュアル言語になります。
モードごとのリテラルオーバーライド
すべてのロールが両モードで共有ランプストップだけで満たせるわけではありません。accent と state のカラーはダーク背景を前提に作り込まれています。ライト背景では、いくつかは WCAG AA をクリアするためにより暗い、そのモード専用のリテラルを必要とします — 共有ランプストップはそのまま維持されるため、Default Dark は影響を受けません:
Default Dark —
dangerは、共有されるstate.dangerのランプ値よりわずかに明るく調整したリテラル(oklch(.655 .170 25))を使用し、danger アドモニションのタイトルが 12% ティントの背景上で AA をクリアします。Default Light —
accentHover、success、danger、warning、infoはすべて、ニアホワイトのページ背景に対して AA をクリアするために、モードごとのリテラル(共有ランプ/state カラーを暗くしたもの)を使用します。両モード —
matchedKeywordBg/matchedKeywordFg(検索結果のハイライト)はランプ参照ではなく共有リテラルのため、琥珀色のハイライトは両モードで同一に見えます。
これらのリテラルの背後にある完全なコントラストペアマトリクス、しきい値、調整手法については、color-scheme-a11yスキル(.)を参照してください。
スキームのセマンティック紐付けの調整
自作のcolorSchemes上書きモジュール(zfb.config.tsのcolorSchemesフィールドで渡す)のmap.semanticを編集します。ロールは共有ランプストップまたはリテラル OKLCH 文字列を指すことができます:
const darkMap: ModeMap = {
bg: { base: 4 },
fg: { base: 0 },
selectionBg: { base: 2 },
selectionFg: { base: 0 },
semantic: {
accent: { accent: 1 },
// Per-mode AA-tuned literal — shared state.danger is too dark for the
// danger-admonition title on its 12%-tint dark bg.
danger: "oklch(.655 .170 25)",
// ...
},
// 任意。すべて継承する場合は syntax 自体を省略できます。
syntax: {},
};生のランプストップへのアクセス
生の--palette-*ランプストップ用の Tailwind ユーティリティはありません — セマンティックトークンが Tailwind に露出する唯一のカラー面です。一度きりのスタイルがどうしても生のランプストップを必要とする場合(まれ)は、CSS カスタムプロパティを直接参照します:
<div style="background: var(--palette-accent-1)">...</div>これはまれなケースであるべきです。既存のセマンティックトークンを優先するか、同じ生のストップが複数箇所で必要な場合は新しいセマンティックロールを追加してください。
Tier 3:コンポーネントトークン
一部のコンポーネントは、Tier 2のセマンティックトークンを参照する独自のカラー変数を定義しています。これらは内部的な実装の詳細です。
コンテンツタイポグラフィ
.zd-contentクラスはセマンティックトークンを使用した直接的な要素スタイリングを提供します(外部タイポグラフィプラグイン不要):
.zd-content {
color: var(--color-fg);
font-size: var(--text-body);
line-height: var(--leading-relaxed);
}
.zd-content :where(a) {
color: var(--color-accent);
}
.zd-content :where(code:not(pre code)) {
color: var(--color-code-fg);
background-color: var(--color-code-bg);
}
.zd-content :where(li::marker) {
color: var(--color-muted);
}
/* ... */Info
Tier 3のトークンはコンポーネント内部のものです。独自のUIを構築する際は、Tier 2のセマンティックトークンを直接使用してください。
カラートークンの使い方
標準的なUIパターンにはセマンティックトークンを使用します:
<!-- Text -->
<p class="text-fg">Primary text</p>
<p class="text-muted">Secondary text</p>
<a class="text-accent hover:text-accent-hover">Link</a>
<!-- Backgrounds -->
<div class="bg-bg">Page background</div>
<div class="bg-surface">Panel or sidebar</div>
<!-- Borders -->
<div class="border border-muted">Bordered element</div>検索とハイライトのトークン(役割を分離)
ハイライトの役割は意図的に専用セマンティックトークンに分離されています。無関係なハイライトUI間でひとつのトークンを使い回すのはアンチパターンです。
matched-keyword-bg/matched-keyword-fgは検索パネルの<mark>要素に使われます。別の役割のcolor-mix()ではなく専用トークンのため、Design Token Panelのスウォッチがハイライト色の唯一のソースになります。パネルで見えている色=実際のハイライト色です。warningはAdmonition(:::warning)、find-in-page(.find-match、.find-match-active)など、意味的に 警告 にあたるUIで使います。新しいUIクロム向けのハイライトにwarningを使い回してはいけません。
新しいハイライトの役割が出てきたとき(新種の<mark>、新しいピル、新しいコールアウト)は、既存の役割過多なトークンに載せるのではなく、専用のセマンティックトークンを追加してください。プロダクトに現れるハイライト色はどれも、パネルのスウォッチに1対1で対応するべきです。
カラースキーム
デフォルトではcolorModeが有効なため、lightSchemeとdarkSchemeがアクティブなスキームを選び、colorSchemeより優先されます。defaultModeは"dark"で、同梱のダークスキームに対応します。respectPrefersColorScheme: trueでは、OSの設定によりもう一方のモードが選ばれることがあります:
import { defineConfig } from "zfb/config";
import { zudoDoc } from "@takazudo/zudo-doc/config";
export default defineConfig(
zudoDoc({
colorMode: {
defaultMode: "dark",
lightScheme: "Default Light",
darkScheme: "Default Dark",
respectPrefersColorScheme: true,
},
siteName: "My Docs",
// ...
}),
);ライト/ダーク切り替えなしで1つの固定スキームを使うには、colorMode: falseを設定します。この場合はcolorSchemeがスキームを選びます:
export default defineConfig(
zudoDoc({
colorMode: false,
colorScheme: "My Theme",
}),
);デフォルトテーマ
zudo-docはちょうど 2 つのカラースキーム — Default LightとDefault Dark — を同梱しており、1 セットのランプを共有します(パッケージの@takazudo/を参照)。どちらも手作業で作り込まれ、その場で AA 調整されています。選べるコミュニティ製やターミナル由来のプリセットカタログはバンドルされていません。以前の Design Token Panel には 50 種類以上のバンドルプリセットをブラウズするための "Scheme…" ドロップダウンがありましたが、ランプネイティブなモデルではこれを廃止しました — Design Token Panelを参照してください。
カスタムカラースキームの追加
colorSchemesはzudoDoc()のエスケープハッチの1つです — 省略した場合は上記の2つの同梱スキームがそのまま使われます。新しいスキャフォールドにはカラースキームモジュールがありません。スキームを追加または置き換える場合は、独自のモジュール(例:src/)を作成してRecord<string, ColorScheme>を組み立て、zfb.config.tsのcolorSchemesフィールドで渡します。新しいスキームには独自のrampsと、必要な各モードのmapが必要です(SEMANTIC_RAMP_DEFAULTSをスプレッドし、異なる必要のあるものだけを上書きします)。2つの同梱スキームも一緒に残したい場合はdefaultColorSchemesもスプレッドしてください:
import { SEMANTIC_RAMP_DEFAULTS } from "@takazudo/zudo-doc/color-scheme-utils";
import { defaultColorSchemes } from "@takazudo/zudo-doc/color-schemes-defaults";
import type { ColorScheme, ModeMap, Ramps } from "@takazudo/zudo-doc/color-scheme-utils";
const myRamps: Ramps = {
base: [
"oklch(.97 .01 250)", // 0 — lightest
"oklch(.72 .02 250)", // 1
"oklch(.48 .03 250)", // 2
"oklch(.28 .03 250)", // 3
"oklch(.15 .02 250)", // 4 — darkest
],
accent: [
"oklch(.78 .12 30)", // 0
"oklch(.68 .18 30)", // 1
"oklch(.48 .16 30)", // 2
],
state: {
danger: "oklch(.62 .18 25)",
success: "oklch(.66 .15 145)",
warning: "oklch(.75 .14 82)",
info: "oklch(.66 .13 245)",
},
};
const myMap: ModeMap = {
bg: { base: 4 },
fg: { base: 0 },
selectionBg: { base: 2 },
selectionFg: { base: 0 },
semantic: {
...SEMANTIC_RAMP_DEFAULTS,
accent: { accent: 1 }, // only override the roles that need to differ
},
syntax: {}, // 任意。syntax 固有の上書きだけをここへ追加します
};
export const colorSchemes: Record<string, ColorScheme> = {
...defaultColorSchemes, // keep "Default Light" / "Default Dark"
"My Theme": { ramps: myRamps, map: myMap },
};import { colorSchemes } from "./src/color-schemes";
export default defineConfig(
zudoDoc({
colorMode: false,
colorScheme: "My Theme",
colorSchemes,
// ...
}),
);Tip
このショーケースリポジトリでは、同梱スキームを変更するときにコントリビューターがpnpm contrast:auditを実行し、color-scheme-a11yスキルを使用します。これらはショーケース保守用のツールであり、生成されたスキャフォールド用のコマンドではありません。カスタムスキームを作成する場合は、プロジェクトに適したコントラスト要件を検証してください。
やってはいけないこと
カラーのアンチパターン
Tailwindデフォルトを使わない — initialにリセットされています:
<!-- WRONG -->
<div class="bg-gray-800 text-blue-500">No visible color</div>
<!-- RIGHT -->
<div class="bg-surface text-accent">Works correctly</div>16進数値をハードコードしない — テーマ切り替えが壊れます:
<!-- WRONG -->
<div class="bg-[#1e1e2e]">Breaks on theme switch</div>
<!-- RIGHT -->
<div class="bg-surface">Adapts to any theme</div>Tier 3の変数を自分のコンポーネントで参照しない:
/* WRONG — don't use hardcoded colors */
.my-component {
color: #3b82f6;
}
/* RIGHT — use semantic tokens */
.my-component {
color: var(--color-accent);
}