テーマシステム¶
Phase 58(v1.41.0)は、旧来のライト/ダークのペアを、単一の
data-theme ディメンション上の 6 つのクラシックテーマから
なるシステムに置き換え、加えて OS に追随する auto 選択を
導入しました。Phase 63(v1.63.0)は、推奨される WCAG AA
プリセットを 6 つ追加し、ピッカー全体で12 テーマを
扱うようになりました。
推奨プリセット(Phase 63 / v1.63.0)¶
設定 → 表示 のピッカーは、推奨サブタブが先頭に来ます。
- ライト:
catppuccin-latte、supabase、graphite - ダーク:
catppuccin-mocha、soft-pop、amethyst-haze
これらは tweakcn のプリセットから
scripts/generate_preset_themes.py によって完全な 44 トークンの
テーマとして生成され、計算的に強制された WCAG AA
(12 テーマすべてにわたる contrast.test.ts)を備えています。
クラシックな 6 つ(light、dark、ocean、forest、
high-contrast、sepia)は変更されていません。
仕組み¶
- 正規のカラートークンは
frontend/src/styles/themes/theme-<id>.cssにあり、data-themeの値(light、dark、ocean、forest、high-contrast、sepia)ごとに 1 ブロックです。各ファイルは 完全なセマンティックトークンセットを定義します。ライトへの フォールスルーはありません。 - テーマに依存しないトークン(間隔、角丸、フォント、ブランドの
メソッドパレット)とレガシーエイリアス(
--bg、--surface、--fg、--dangerなど)はstyles/global.css :rootにあります。 エイリアスは正規トークンを経由して解決されるため、古い ルールも自動的にアクティブなテーマに追従します。 - テーマファイルは
main.tsxでインポートされ、ライトが先頭に 来るので、アクティブなテーマが:rootに対する特異性の引き分けに 勝ちます。 frontend/src/lib/themes.tsはレジストリです。THEMES、型ThemeId/ThemeChoice、autoのマッピング用のresolveTheme(choice, prefersDark)、そしてプレビューの スウォッチです。frontend/src/hooks/useTheme.tsは適用されるdata-theme属性を 所有し、選択をadaptive-learner.themeとして永続化します (古いadaptive-learner-themeキーを一度だけ移行します)。index.htmlには、保存されたテーマを最初のペイントの前に 適用する小さなインラインスクリプトがあります(ちらつきなし)。 これはフックの解決をミラーリングしています。両者を同期させて おいてください。- チャート(Recharts)は CSS 変数を SVG 属性で読めないため、
lib/chartTheme.ts+useChartThemeが計算済みのトークン値を 読み取り、data-themeの変更時に再読み込みします。
トークンセット(すべてのテーマが定義する)¶
背景(--bg-primary/secondary/surface/elevated/overlay)、
テキスト(--fg-primary/secondary/muted/inverse)、ボーダー
(--border-primary/subtle/accent)、インタラクティブ
(--interactive-bg/hover/active/disabled)、アクセント
(--accent、-hover、-fg、-subtle、-rgb)、ステータスの
ペア(--success/-bg、--error/-bg、--warning/-bg、
--info/-bg)、演習フィードバック
(--exercise-correct/-wrong/-selected/-matched)、--star、
チャートシリーズ(--chart-1..6)、シャドウ
(--shadow-card/-elevated/-md)。
styles/themes/themes.test.ts は、あるテーマがこれらのトークンの
いずれかを欠くか、余分なものを持つと失敗します。
styles/contrast.test.ts は 12 テーマすべてにわたって WCAG 2.1
AA を検査します。完全なトークンリファレンスは
デザイントークン・アーキテクチャに
あります。
新しいテーマを追加する¶
- 既存のファイルをコピーします。例:
cp theme-dark.css theme-midnight.css。セレクタを[data-theme="midnight"]に変更します。すべてのトークンを 保持し、値だけを変更します。ここにコンポーネントのスタイルは 書きません。 lib/themes.tsに登録します。THEMESにThemeMetaエントリ(id、英語のlabel、familylight|dark、設定の プレビュー用のswatch)を追加し、ThemeIdの union に id を 加えます。main.tsxでtheme-light.cssの後にインポートします (順序はライトに対する相対位置だけが重要です)。- プリペイントガードで許可します。
index.htmlのインライン<script>内のvalid配列に id を加えます。 - i18n:
backend/config/i18n/*.yamlの 8 つすべての カタログにui.themes.midnightを追加し、make sync-i18nを 実行します。 - 検査:
npx vitest run src/styles/themes src/styles/contrast— 完全性とコントラストのピンがグリーンのままでなければなりません (新しいテーマでコントラストが AA を満たすまで値を調整します)。
これで完了です。ThemePicker、プリペイントスクリプト、チャート、 そしてすべてのコンポーネントは、いずれも正規トークンを読むので、 新しいテーマを自動的に取り込みます。
規則¶
- コンポーネントに色をハードコードしないこと。
styles/no-hardcoded-colors.test.tsが.tsxのスタイルに ついてこれを強制します(文書化された許可リストがチャートの リゾルバ、装飾的な紙吹雪、データの色をカバーします)。 - すべてのテーマがすべてのトークンを定義する。 ライトからの 継承によるギャップはありません。これが F1 監査のバグ (ダークモードで明るい hex を表示した未定義のトークン)でした。
- テーマの切り替えは即座です。
data-themeの入れ替えであり、 再読み込みは決して行いません。