メインコンテンツまでスキップ

デザインシステム

Elftia のビジュアルデザインは、冷たく純粋に技術的な美学を避けながら、温かみ、親しみやすさ、洗練さを追求します。


デザイン哲学

色の原則

  • 暖色系ニュートラルカラー: すべてのサーフェスカラーは、純粋なグレースケールではなく、わずかに暖かい色調(hue 24-38)を持ちます
  • ダークモード: 純粋な黒ではなく、暖かみのあるチャコールを使用します
  • ライトモード: 純粋な白ではなく、暖かみのあるクリームを使用します
  • 寒色系グレー禁止: 背景やボーダーに純粋なグレー(hsl(0, 0%, ...))を使用しないでください

角丸の原則

要素半径Tailwind Class
カード/コンテナ12pxrounded-xl
入力フィールド12pxrounded-xl
ボタン/バッジ6pxrounded-md
デフォルト8pxrounded-lg

4px 未満の角丸は避けてください(線の装飾を除く)。

タイポグラフィの原則

目的フォントTailwind Class
ディスプレイ/大きな見出しNoto Serif / Georgiafont-display
本文/インターフェースInterfont-sans
コードJetBrains Monofont-mono

見出しには font-bold ではなく font-semibold を使用し、tracking-tight と組み合わせてください。


カラーシステム

セマンティックトークン

すべての色は CSS 変数で定義し、Tailwind 設定でユーティリティクラスにマッピングします。ハードコードされた色値は絶対に使用しないでください。

// Don't do this
<div className="bg-white text-black border-gray-200">
<div style={{ backgroundColor: '#ffffff' }}>

// Correct
<div className="bg-surface-0 text-foreground border-border">
<div style={{ backgroundColor: 'var(--surface-0)' }}>

よく使うトークン参照

目的Tailwind ClassCSS Variable
ページ背景bg-backgroundvar(--background)
L0 背景bg-surface-0var(--surface-0)
L1 背景(カード/サイドバー)bg-surface-1var(--surface-1)
L2 背景(入力/セカンダリコンテナ)bg-surface-2var(--surface-2)
L3 背景(ポップオーバー)bg-surface-3var(--surface-3)
メインテキストtext-foregroundvar(--foreground)
セカンダリテキストtext-muted-foregroundvar(--muted-foreground)
補助テキストtext-text-subtlevar(--text-subtle)
ボーダーborder-bordervar(--border)
テーマカラーbg-primary / text-primaryvar(--primary)
成功text-successvar(--success)
エラーtext-destructivevar(--destructive)
警告text-warningvar(--warning)

ダーク/ライトモード

切り替えメカニズム

Tailwind の class 戦略(darkMode: 'class')を使用し、ThemeContext で一元管理します。

// Correct: get theme info via useTheme
import { useTheme } from '@/shared/state/themeStore';

function MyComponent() {
const { mode, resolvedMode, userTheme } = useTheme();
}

// Don't: manual detection
const isDark = localStorage.getItem('theme') === 'dark';
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

階層システム

ダークモードでは、階層の区別に明度を利用します。ユーザーに近い UI 要素ほど背景を明るくします。

レベルトークン明度目的参照色
L0--surface-07%ページのメイン背景#121212
L1--surface-112%サイドバー、カード#1E1E1E
L2--surface-217%セカンダリコンテナ、入力#2B2928
L3--surface-322%ポップオーバー、Dropdown#383635

視覚的な区別を確保するため、レベル間の明度差は 4-5% 以上にしてください。

ボーダー標準

ダークモードでは、人間の暗部に対する知覚感度が低くなるため、ボーダーにはより高い視認性が必要です。

シナリオライトモードダークモード
カードのボーダーborder-border/30 ~ /40border-border/50 ~ /70
区切り線border-border/20 ~ /30border-border/40 ~ /50
入力フィールドborder-border/40border-border/60 ~ border-border

WCAG アクセシビリティ

WCAG 2.1 標準に基づきます。

コントラスト要件

要素タイプ最小コントラスト説明
通常テキスト(< 18pt)4.5:1本文、説明、ラベル
大きなテキスト(>= 18pt または 14pt bold)3:1見出し
UI コントロール(アイコン、ボーダー)3:1入力ボーダー、アイコン、バッジ
無効状態適用除外、2.5:1 推奨完全に見えなくなることを避ける

テキストトークン使用ルール

トークン明度使用可能な背景代表的な用途
text-foreground (93%)最高すべてのサーフェスメイン見出し、本文テキスト
text-muted-foreground (65%)surface-0、surface-1セカンダリテキスト、説明
text-text-subtle (50%)surface-0 のみタイムスタンプ、メタデータ
// Good: description text uses text-muted
<p className="text-muted-foreground">5 models total</p>

// Bad: using text-subtle on surface-1 card (insufficient contrast)
<div className="bg-surface-1">
<span className="text-text-subtle">Hard to read</span>
</div>

色による情報伝達

ステータスを伝えるために色だけに依存してはいけません。必ずテキストラベルまたはアイコンと組み合わせてください。

// Bad: color only
<div className={status === 'error' ? 'border-red-500' : 'border-border'} />

// Good: color + icon + text
<div className={status === 'error' ? 'border-destructive' : 'border-border'}>
{status === 'error' && <AlertCircle className="text-destructive" />}
<span>{errorMessage}</span>
</div>

フォーカス状態

キーボードナビゲーションのユーザーにフォーカスボーダーを提供するため、focus-visible を使用してください。

// Good
<button className="focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none">
Button
</button>

// Don't: remove focus styles
<button className="outline-none focus:outline-none">Button</button>

モーション設定

システムの prefers-reduced-motion 設定を尊重してください。

<div className="motion-safe:animate-fadeIn motion-reduce:animate-none">
Content
</div>

UI コンポーネント標準

ネイティブコントロールを使わない

ネイティブコントロールプロジェクトコンポーネントパス
<select>Select@/components/ui/select
<input type="text">Input@/components/ui/input
<input type="checkbox">Switch / Checkbox@/components/ui/switch
<button>Button@/components/ui/button
window.confirm()ConfirmDialog@/components/ui/confirm-dialog
window.alert()Toast コンポーネント-

すべての Dropdown コンポーネントは、以下をサポートする必要があります。

  • ビューポートを考慮した位置決め(useDropdownPosition Hook を使用)
  • 外側クリックで閉じる
  • Escape キーで閉じる
  • 項目のテーマカラーとスタイル

壁紙透過システム

ユーザーが壁紙を設定した場合、bodydata-wallpaper-active="true" 属性を追加し、CSS の透過ルールを発火させます。

CSS ルール階層

優先度セレクタ効果目的
1.bg-background, .bg-surface-0完全に透明ページのメイン背景
2.wallpaper-blur35% 不透明 + blurメインコンテナ(WorkspaceShell)
3.wallpaper-blur .wallpaper-blur透明 + blur*0.67ネストされたコンテナ(重なりを避ける)
4.wallpaper-blur 内の bg-surface-0/XX透明レイアウトパネル
5.wallpaper-blur 内の bg-surface-1/XX12% + blurコンテンツカード(alpha バリアント)
6bg-surface-1, bg-popover15% + blurボタン/カード(input を除外)
7bg-surface-220% + blurセカンダリコンテナ(input を除外)
8.wallpaper-panel85% + blur*1.33フローティング Dropdown/menu
9.wallpaper-solid不透明な surface-0Dialog/Modal

階層スタッキングモデル

WorkspaceShell (wallpaper-blur, 35%)
+-- Sidebar (bg-surface-0/75 -> transparent) = 35%
+-- Main content (bg-background -> transparent) = 35%
| +-- Content cards (bg-surface-1/80 -> 12%) ~ 43%
| +-- Buttons (bg-surface-1 -> 15%) ~ 45%
| +-- Input (input -> solid) = Opaque
| +-- Dropdown panel (wallpaper-panel -> 85%) = 85%
+-- Bottombar (wallpaper-blur -> transparent) = 35%

壁紙 CSS クラス使用ガイド

シナリオ推奨
ページのメインコンテナbg-background または bg-surface-0(自動的に完全透明)
ボタン/カードbg-surface-1 または bg-surface-1/80(自動的に半透明)
セカンダリコンテナbg-surface-2(自動的に 20% 半透明)
メインレイアウトコンテナwallpaper-blur クラスを追加
フローティング Dropdown/menuwallpaper-panel クラスを追加
Dialog/Modalwallpaper-solid クラスを追加
入力フィールド<input> / <textarea> + bg-surface-1(自動除外、ソリッドを維持)

壁紙サポートが組み込まれたコンポーネント

半透明のフロストガラス(wallpaper-panel):

  • Select Dropdown パネル
  • DropdownMenuContent / DropdownMenuSubContent
  • ContextMenuContent / ContextMenuSubContent

完全に不透明(wallpaper-solid):

  • DialogContent

禁止事項

  • カード背景として素の bg-surface-0 を使用しないでください(壁紙モードでは完全に透明になって消えます)
  • bg-whitebg-blackbg-gray-*bg-neutral-* のようなハードコード色を使用しないでください
  • 不透明な効果が必要な場合は、同時に wallpaper-solid クラスを追加してください

ユーザーがカスタマイズ可能なカラーオーバーレイレイヤー(0.1.11+)

上記の「基本的な壁紙透過システム」に加えて、壁紙パネルはユーザーが調整可能な 3 種類のカラーレイヤーを公開します。それぞれ body data 属性 + CSS 変数 によって駆動され、CSS セレクタがレイヤーごとにデフォルトのサーフェス動作を上書きします。すべての書き込みは themeUtils.applyWallpaperToDocument で一元管理し、コンポーネントが直接 body.style.setProperty してはいけません。

1. 調光レイヤー(body::before 疑似要素)

Data AttributeトリガーCSS Variable
data-wallpaper-active="true"任意の壁紙ソースが準備完了--wp-dimming (0–1), --wp-dim-{h,s,l}
data-wp-dim-gradient="true"wallpaperDimmingGradient が設定済み--wp-dim-gradient(HSL を上書き)

明度のフォールバック: --wp-dim-l が設定されていない場合、ライトモードでは既定で 100%、ダークモードでは 0% になります(元の白/黒の動作に相当)。

2. 要素サーフェスレイヤー(サイドバー / カード / タブ / コンテキストメニュー)

Data AttributeトリガーCSS Variable
data-wp-element-tint="true"wallpaperElementTint が有効な hex--wp-elem-{h,s,l}
data-wp-element-gradient="true"wallpaperElementGradient が設定済み--wp-elem-gradient-{15,20,35}(サーフェス階層ごとの alpha バージョン)

設計上の要点: 各サーフェス階層は独立した alpha を維持します(surface-1 = 15%、surface-2 = 20%、.wallpaper-card = 35%)。そのため、同じ色調に変更された場合でも、視覚的な階層は区別可能なままです。Input/Textarea と wallpaper-solid / wallpaper-panel セレクタは :not(...) で除外されます。色の一貫性よりも可読性を優先します。

3. メッセージバブルレイヤー(user / assistant を分離)

Data AttributeトリガーCSS Variable
data-wp-bubble-override="true"wallpaperBubbleOverride === true--wp-bubble-alpha (0–1)
data-wp-bubble-tint-user="true"User バブルの hex が有効--wp-bubble-user-{h,s,l}
data-wp-bubble-tint-assistant="true"Assistant バブルの hex が有効--wp-bubble-asst-{h,s,l}
data-wp-bubble-gradient-{user,assistant}="true"対応する gradient が設定済み--wp-bubble-{user,asst}-gradient

CSS セレクタは 2 つのレイヤーでカスケードします。

/* Layer 1: when override off, bubbles follow element tint (inherit) */
body[data-wp-element-tint="true"]:not([data-wp-bubble-override="true"]) .chat-bubble-user,
body[data-wp-element-tint="true"]:not([data-wp-bubble-override="true"]) .chat-bubble-assistant {
background: hsl(var(--wp-elem-h) var(--wp-elem-s) var(--wp-elem-l) / var(--wp-alpha-35, 0.35)) !important;
}

/* Layer 2: when override on, per-side tint + custom alpha applies */
body[data-wp-bubble-override="true"][data-wp-bubble-tint-user="true"] .chat-bubble-user {
background: hsl(var(--wp-bubble-user-h) var(--wp-bubble-user-s) var(--wp-bubble-user-l) / var(--wp-bubble-alpha, var(--wp-alpha-35, 0.35))) !important;
}

Layer 2 セレクタはより高い詳細度を持ち、後に出現するため、!important が同じ場合でも優先されます。

新しい user-tint フィールドを追加する場合

  1. ThemePreferencessettings-types.ts)と ThemePreferencesSchemaconfigSchema.ts)の両方にフィールドを追加する
  2. ThemeService.setWallpaperPreferences に setter 分岐を追加し、readPreferences/importProfile/resetTheme/mergePreferences にデフォルト値を追加する
  3. ThemeRouter の両方の Zod スキーマ(themeProfileSchematheme:setWallpaperPreferences 内のインラインスキーマ)にフィールドを追加する
  4. ThemeContext に context value、setWallpaperPreferences のパラメータ、commitState のフォールバックを追加する
  5. themeUtils.applyWallpaperToDocument にパラメータ、_prev* キャッシュ、body 属性/CSS 変数の書き込みを追加する
  6. WallpaperPanel に UI(toggle/color picker/slider)と 3 言語の i18n を追加する
  7. index.css にセレクタを追加する(階層順序と !important の優先度に注意)
  8. パススルーチェーン: AppearanceTabSettings.tsx / ThemeStudioPage.tsx(DraftState + effective + Apply submit を含む)
  9. Agent スタブ: desktop-api.ts(preload contract)、shared/agent/types/settings.ts(shared interface)、shared/agent/web/theme.ts(HTTP 実装)
  10. この表、architecture-index SKILL.md のフィールドクイックリファレンス、ipc-channels.mdtheme:setWallpaperPreferences フィールド表、appearance.md ユーザードキュメントを更新する

テーマ互換の開発ルール

セマンティックトークンのみを使用する

#fff/rgb() や Tailwind のデフォルト色を直接書かないでください。

ThemeContext を統一的に使用する

コンポーネントがテーマ情報を必要とする場合は、useTheme() から読み取ってください。

設定可能なフォントを尊重する

テキスト/コード領域には CSS 変数を使用してください。

<div style={{ fontFamily: 'var(--font-ui)' }}>Normal text</div>
<code style={{ fontFamily: 'var(--font-code)' }}>Code</code>

// Or use Tailwind classes
<div className="font-ui">Normal text</div>
<code className="font-code">Code</code>

customCss の上書きを許可する

!important や大きなインラインスタイルは避け、className + CSS 変数を優先してください。


セルフテストチェックリスト

新しい UI コンポーネントを作成する場合:

  • セマンティックトークンのみを使用する(ハードコード色なし)
  • useTheme() からテーマ情報を取得する
  • ダーク/ライトモード切り替えをテストする
  • 壁紙透過効果をテストする
  • 通常テキストのコントラスト >= 4.5:1
  • ダークモードのボーダーには dark:border-border/50 以上を使用する
  • text-subtlesurface-0 背景でのみ使用する
  • インタラクティブ要素にはセマンティックタグを使用するか、role + tabIndex を追加する
  • フォーカススタイルには focus-visible:ring-2 を使用する
  • 画面輝度 30% でもテキストとボーダーを判別できる
  • ウィンドウを 200% に拡大してもレイアウトが崩れない