zudo-tauri-wisdom

Type to search...

to open search from anywhere

カラーテーマシステム

作成2026年4月3日更新2026年4月3日Takeshi Takatsudo

16色パレットからセマンティック CSS カスタムプロパティへの2層カラーアーキテクチャ

カラーテーマシステム

カラーテーマシステムは2層アーキテクチャを使用する。Tier 1 は16色パレット(ターミナルカラースキームと同様)を定義し、Tier 2 はパレットインデックスからセマンティックトークン(背景、アクセント、危険、ホバー状態)を導出する。両層とも CSS カスタムプロパティとして公開され、Tailwind CSS ユーティリティやコンポーネントスタイルで使用できる。

Tier 1: カラーパレット

各カラースキームは標準のターミナルカラー規約(ノーマル8色 + ブライト8色)に合わせた16色パレットを定義する。

export interface ColorScheme {
  name: string;
  label: string;
  isDark: boolean;
  background: ColorRef;    // パレットインデックスまたは直接カラー
  foreground: ColorRef;
  cursor: ColorRef;
  selectionBg: ColorRef;
  selectionFg: ColorRef;
  palette: [
    string, string, string, string, // 0-3: black, red, green, yellow
    string, string, string, string, // 4-7: blue, magenta, cyan, white
    string, string, string, string, // 8-11: ブライトバリアント
    string, string, string, string, // 12-15: ブライトバリアント
  ];
  semantic?: { /* Tier 2 オーバーライド */ };
}

ColorRef 型はパレットインデックス(数値)または直接16進値(文字列)でカラーを参照できる。

export type ColorRef = number | string;

この柔軟性により、スキームは自身のパレットエントリを参照することも、パレットにないカスタムカラーを使用することもできる。

// "default-dark" はパレットインデックスを参照
{
  background: 0,    // palette[0] = "#1C1C1C"
  foreground: 7,    // palette[7] = "#A0A0A0"
  cursor: 6,        // palette[6] = "#90A1B9"
}

// "catppuccin-mocha" は直接16進値を使用
{
  background: "#1e1e2e",
  foreground: "#cdd6f4",
  cursor: "#f5e0dc",
}

組み込みスキーム

パッケージには10のカラースキームが同梱されている。

スキームタイプ説明
default-darkダークセマンティックオーバーライド付きカスタムウォームダークテーマ
default-lightライトセマンティックオーバーライド付きカスタムライトテーマ
catppuccin-mochaダークCatppuccin パステルパレット(mocha バリアント)
catppuccin-latteライトCatppuccin パステルパレット(latte バリアント)
tokyo-nightダークTokyo Night カラースキーム
draculaダークDracula クラシックテーマ
nordダーク北欧風ブルーイッシュパレット
solarized-darkダークEthan Schoonover の Solarized(ダーク)
one-darkダークAtom One Dark テーマ
gruvbox-darkダークレトロなグルーヴカラー

Tier 2: セマンティックトークン

生のパレットカラーは UI コンポーネントにとって意味を持たない。「palette[3] を使用」は意図を伝えない。セマンティックトークンがこのギャップを埋める。

export interface ColorSettings {
  bgPrimary: string;
  bgSecondary: string;
  bgSurface: string;
  textPrimary: string;
  textSecondary: string;
  accent: string;
  accentSubtle: string;
  border: string;
  danger: string;
  dangerStrong: string;
  onAccent: string;       // アクセント背景上のテキスト色
  hoverOverlay: string;
  hoverBg: string;
  hoverFg: string;
  selection: string;
  cursor: string;
}

セマンティックカラーの導出

schemaToColors() 関数がスキームのパレットからセマンティックトークンを生成する。

export function schemaToColors(scheme: ColorScheme): ColorSettings {
  const p = scheme.palette;
  const bg = resolveColor(scheme.background, p, p[0]);
  const fg = resolveColor(scheme.foreground, p, p[15]);
  const sem = resolveSemanticColors(scheme);

  return {
    bgPrimary: bg,
    bgSecondary: sem.bgSecondary,
    bgSurface: sem.surface,
    textPrimary: fg,
    textSecondary: sem.textSecondary,
    accent: sem.accent,
    accentSubtle: sem.accentSubtle,
    border: sem.border,
    danger: sem.danger,
    dangerStrong: sem.dangerStrong,
    onAccent: sem.onAccent,
    hoverOverlay: sem.hoverOverlay,
    hoverBg: sem.hoverBg,
    hoverFg: sem.hoverFg,
    selection: resolveColor(scheme.selectionBg, p, p[8]),
    cursor: resolveColor(scheme.cursor, p, p[6]),
  };
}

セマンティックオーバーライド

スキームは semantic オブジェクトを通じてデフォルトのパレット→セマンティックマッピングをオーバーライドできる。異なるパレットが正しく見えるためには異なるマッピングが必要なため、これは不可欠である。

// "default-dark" はいくつかのセマンティックトークンをオーバーライド
semantic: {
  bgSecondary: "bg",    // Use background color (special ref)
  surface: 0,           // Palette index 0
  accent: 3,            // Palette index 3 (yellow)
  accentSubtle: 12,     // Palette index 12
  border: 8,            // Palette index 8
  onAccent: "bg",       // Text on accent = background color
  hoverOverlay: 13,
  hoverBg: 13,
  hoverFg: 11,
}

特殊文字列値 "bg""fg" はスキームの解決済み背景色と前景色を参照し、自己参照的な定義を可能にする。

デフォルトフォールバック

セマンティックオーバーライドを提供しないスキームにはデフォルトが使用される。

export const SEMANTIC_DEFAULTS: Record<string, number> = {
  bgSecondary: 0,
  surface: 0,
  textSecondary: 15,
  accent: 3,
  accentSubtle: 3,
  border: 0,
  danger: 1,
  dangerStrong: 9,
  onAccent: 9,
  hoverOverlay: 15,
  hoverBg: 8,
  hoverFg: 7,
};

オーバーライドのないスキーム(Catppuccin、Tokyo Night など)では、カラーミキシングを使用した計算済みフォールバックも生成される。

accentSubtle: colorMixAlpha(accentColor, 0.1, bg),  // 背景上に10%のアクセント
hoverOverlay: colorMixAlpha(fg, 0.03, bg),           // 背景上に3%の前景
hoverBg: colorMixAlpha(fg, 0.1, bg),                 // 背景上に10%の前景

CSS カスタムプロパティ

Tier 1: パレット変数

applyTheme() 関数により :root に適用される。

export function applyTheme(themeName: string): ColorScheme {
  const scheme = getSchemeByName(themeName);
  const root = document.documentElement;
  const p = scheme.palette;

  root.style.setProperty("--palette-bg", resolveColor(scheme.background, p, p[0]));
  root.style.setProperty("--palette-fg", resolveColor(scheme.foreground, p, p[15]));
  root.style.setProperty("--palette-cursor", resolveColor(scheme.cursor, p, p[6]));
  root.style.setProperty("--palette-selection", resolveColor(scheme.selectionBg, p, p[8]));
  for (let i = 0; i <= 15; i++) {
    root.style.setProperty(`--palette-${i}`, p[i]);
  }
  return scheme;
}

これにより CSS から --palette-0 から --palette-15--palette-bg--palette-fg などにアクセスできる。

Tier 2: セマンティック変数

applyColors() 関数により適用される。

const colorKeyToCssVar: Record<keyof ColorSettings, string> = {
  bgPrimary: "--theme-bg-primary",
  bgSecondary: "--theme-bg-secondary",
  bgSurface: "--theme-bg-surface",
  textPrimary: "--theme-text-primary",
  textSecondary: "--theme-text-secondary",
  accent: "--theme-accent",
  accentSubtle: "--theme-accent-subtle",
  border: "--theme-border",
  danger: "--theme-danger",
  dangerStrong: "--theme-danger-strong",
  onAccent: "--theme-on-accent",
  hoverOverlay: "--theme-hover-overlay",
  hoverBg: "--theme-hover-bg",
  hoverFg: "--theme-hover-fg",
  selection: "--theme-selection",
  cursor: "--theme-cursor",
};

コンポーネントはパレットインデックスではなくセマンティック変数を使用する。

/* 良い例: セマンティックな意図が明確 */
.sidebar { background: var(--theme-bg-secondary); }
.button { background: var(--theme-accent); color: var(--theme-on-accent); }
.error { color: var(--theme-danger); }

/* 避ける: パレットインデックスにはセマンティックな意味がない */
.sidebar { background: var(--palette-0); }

xterm 統合

ターミナルには独自のテーマオブジェクトが必要(CSS 変数ではない)。toXtermTheme() 関数が ColorScheme を xterm の ITheme 形式に変換する。

export function toXtermTheme(scheme: ColorScheme) {
  const p = scheme.palette;
  return {
    background: resolveColor(scheme.background, p, p[0]),
    foreground: resolveColor(scheme.foreground, p, p[15]),
    cursor: resolveColor(scheme.cursor, p, p[6]),
    black: p[0],
    red: p[1],
    green: p[2],
    yellow: p[3],
    blue: p[4],
    // ...all 16 ANSI colors
  };
}

カスタム DOM イベント(zudotext:scheme-changed)がスキーム変更時にターミナルコンポーネントに通知し、xterm テーマを再適用できるようにする。

カラーユーティリティ関数

パッケージにはカラー操作のユーティリティが含まれている。

// Mix two hex colors at a given ratio
colorMix("#ff0000", "#0000ff", 0.5) // -> purple

// Blend foreground over background with alpha
colorMixAlpha(fg, 0.1, bg) // -> 10% fg over bg

// HSL conversion for programmatic color manipulation
hexToHsl("#1C1C1C") // -> { h: 0, s: 0, l: 11 }

// Determine readable text color for a background
contrastTextColor("#1C1C1C") // -> "#ffffff"

まとめ

2層システムは利用可能なカラー(パレット)とその意味(セマンティックトークン)を分離する。スキームは16色のパレットを定義し、オプションでセマンティックマッピングをオーバーライドするだけでよい。システムが残りを処理する:ホバー状態の生成、微妙なバリアントの計算、CSS 変数の設定、フォーマット固有のテーマオブジェクトへの変換。これにより、エディタ、ターミナル、UI コンポーネント、Tailwind ユーティリティ全体でテーマの一貫性が保たれる。