mcpField
L
light-dark-mode

light-dark-mode

github.com/mgiffordデザイン・UI57160 回インストール6 週間前に更新
リポジトリ

light-dark-mode について

HTML、CSS、JavaScriptでライト/ダークテーマを実装・レビューするためのアクセシビリティスキルです。3択セレクター、安全な設定保存、コントラスト検証を扱い、フォーカス移動だけでテーマが変わらない設計を示します。

主な機能

  1. 01System、Light、Darkの3択セレクター
  2. 02明示的な操作後のみテーマを変更
  3. 03設定の安全な保存とシステム設定への対応
  4. 04WCAGコントラストと強制カラーの指針
  5. 05キーボード操作とフォーカス状態の要件

活用シーン

  1. 01テーマセレクターのキーボード操作を確認
  2. 02テーマ設定の保存を実装
  3. 03ライト、ダーク、強制カラーモードでコントラストを検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mgifford/accessibility-skills --skill light-dark-mode

インストール後、Claude Code で/light-dark-modeを入力して読み込まれたか確認します。

同梱ファイルSYNC.md

詳細

ライト/ダークモードのアクセシビリティスキル

正本:mgifford/ACCESSIBILITY.md の examples/LIGHT_DARK_MODE_ACCESSIBILITY_BEST_PRACTICES.md このスキルはそのファイルを基にしています。判断に迷った場合は、例を正としてください。

HTML、CSS、または JavaScript でカラーテーマを実装またはレビューする際は、必ずこれらのルールを適用してください。 プロジェクトがライト/ダークモードまたはユーザーによるテーマ切り替えをサポートしている場合に限り、このスキルを読み込んでください。


重要なルール:フォーカスはナビゲーションであり、選択ではありません

ユーザーがオプションを明示的に有効化するまで、テーマを変更してはいけません。

キーボードのフォーカスを移動したり、ポインターをテーマの選択肢に合わせたりしても、テーマをプレビュー、選択、または適用してはいけません。System、Light、Dark の間を Tab キーで移動しても、ユーザーが Enter または Space を押すまでは、ページの色を変更してはいけません。

これは重要な要件です。違反すると、キーボードで移動する際に色がちらつき、前庭障害、認知障害、弱視のあるユーザーが混乱する原因になります。


基本方針

すべてのカラーテーマは、ライトモードとダークモードの両方で、強制カラー/ハイコントラストモードも含め、WCAG 2.2 レベル AA のコントラスト要件を満たす必要があります。デフォルトだけでなく、3 つすべてをテストしてください。

必須:3 つの選択肢を表示するテーマセレクターのパターン

3 つの選択肢を常に同時に表示する、視認可能なセレクターを使用してください。

  • System
  • Light
  • Dark
操作パターンの要件
  • ラベル付きのグループ内に、ネイティブの <button type="button"> 要素を 3 つ使用する必要があります
  • 選択中のオプションには aria-pressed="true" を、その他のオプションには aria-pressed="false" を使用する必要があります
  • 1 つのボタンで順番に切り替える方式を使用してはいけません
  • ライト/ダークの 2 状態切り替えを使用してはいけません
  • 利用可能な選択肢を隠すメニューを使用してはいけません
  • カスタムの role="radio" 要素を使用してはいけません
  • 矢印キーでフォーカスを移動した際にテーマが変わるラジオグループを使用してはいけません
  • ホバーまたはフォーカスによるプレビューを行ってはいけません
  • ユーザーがオプションに Tab で移動しただけで、自動的に選択してはいけません
キーボード要件

このセレクターは、標準的なネイティブボタンの動作で機能する必要があります。ユーザーは次の操作を行える必要があります。

  • 各オプションに Tab で移動する
  • Shift+Tab で戻る
  • Enter でオプションを有効化する
  • Space でオプションを有効化する
  • マウス、タッチ、スイッチデバイス、音声コントロール、またはその他のポインターでオプションを有効化する

オプション間でフォーカスを移動しても、テーマを変更してはいけません。テーマを変更するのは、意図的に有効化した後だけにしてください。

ネイティブボタンの動作ですでに必要な機能が提供されている場合、不要なカスタムのキーボードハンドラーを追加しないでください。

キーボード操作中の色のちらつきを防ぐ

テーマを変更するのは、明示的に有効化した後だけにしてください。キーボードのフォーカスを移動したり、ポインターをテーマの選択肢に合わせたりしても、テーマをプレビュー、選択、または適用してはいけません。

System、Light、Dark の間を Tab キーで移動しても、ユーザーが Enter または Space を押すまでは、ページの色を変更してはいけません。


必須:HTML 構造

アクセシブルなラベルを持つグループ内に、ネイティブボタンを 3 つ配置してください。

<div role="group" aria-label="Colour theme">
  <button
    type="button"
    class="theme-mode-btn"
    aria-pressed="false"
    data-theme-value="system">
    <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20">
      <path fill="none" stroke="currentColor" stroke-width="2" d="M3 4h18v12H3zM8 20h8"/>
    </svg>
    <span>System</span>
  </button>

  <button
    type="button"
    class="theme-mode-btn"
    aria-pressed="false"
    data-theme-value="light">
    <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20">
      <circle cx="12" cy="12" r="5" fill="currentColor"/>
    </svg>
    <span>Light</span>
  </button>

  <button
    type="button"
    class="theme-mode-btn"
    aria-pressed="true"
    data-theme-value="dark">
    <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20">
      <path fill="currentColor" d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
    </svg>
    <span>Dark</span>
  </button>
</div>

必須:デフォルトの動作

ユーザーが明示的な選択をしていない場合は、system をデフォルトの設定として使用してください。

システム設定を、保存されたライトまたはダークの設定に変換しないでください。

たとえば、ユーザーが system を選択し、その時点でオペレーティングシステムがダークモードの場合は、次の値を保存してください。

system

次の値は保存しないでください。

dark

この区別は、後からオペレーティングシステムの設定が変わった場合にも、ページにその変更を反映させるために必要です。


必須:ストレージ要件

theme-mode など、内容が明確なストレージキーを使用してください。有効な保存値は system、light、dark のみです。

  • 保存値を使用する前に検証してください
  • すべての localStorage の読み取りと書き込みを try/catch で囲んでください
  • ストレージを利用できない場合でも、現在のページでテーマコントロールが機能する必要があります
  • ストレージのエラーによってスクリプトの実行を停止させないでください

ストレージは、次の環境では利用できない場合があります。

  • プライバシーが制限されたブラウザー
  • サンドボックス化されたコンテキスト
  • プライベートブラウジングモード
  • ストレージへのアクセス時に例外が発生する環境

必須:テーマ適用モデル

選択されたモードと解決されたテーマの値を分けて保持します。

selectedMode = "system" | "light" | "dark";
resolvedTheme = "light" | "dark";

明示的な属性を使って、両方をルート要素に適用します。

<html data-theme-mode="system" data-theme="dark">
  • data-theme-mode はユーザーが実際に選択した設定を表します
  • data-theme は現在表示されているライトまたはダークの配色を表します

システム設定が有効な場合は、次の変更を監視します。

window.matchMedia("(prefers-color-scheme: dark)")

この設定が変更されたら、解決されたテーマを更新します。システム設定の変更時に、保存されたモードを上書きしないでください。


必須:初期レンダリングとフラッシュの防止

メインのスタイルシートより前、またはページが視覚的に表示される前に実行される、早期初期化のパターンを用意します。このパターンでは、必ず次の処理を行います。

  1. 保存された設定を安全に読み取ります
  2. 保存された値を検証します
  3. system をデフォルトにします
  4. prefers-color-scheme を使って system を解決します
  5. 最初に描画される前に、ルート要素のテーマ属性を設定します
  6. 適切な color-scheme 値を設定します

<head> 内のできるだけ早い位置に含めます。

<meta name="color-scheme" content="light dark">

この早期実行スクリプトは、ページの読み込み中に誤った配色が一瞬表示されるフラッシュを軽減することを目的としている、と説明してください。あらゆるブラウザーで発生し得る視覚的な切り替わりをすべてスクリプトでなくせるとは主張しないでください。設定の適用が遅れたときに一般的に発生するフラッシュを軽減、または防止すると説明してください。


必須:CSS カスタムプロパティのパターン

CSS カスタムプロパティを使い、セマンティックなテーマトークンを適用します。例の中でコンポーネントの色を個別にハードコードしないでください。

:root {
  color-scheme: light dark;

  --color-text:       #1a1a1a;
  --color-background: #ffffff;
  --color-surface:    #f7f7f7;
  --color-link:       #0066cc;
  --color-focus:      #004499;
  --color-border:     #cccccc;
  --color-hover:      #f5f5f5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-text:       #e8e8e8;
    --color-background: #121212;
    --color-surface:    #1b1b1b;
    --color-link:       #66aaff;
    --color-focus:      #9bd1ff;
    --color-border:     #444444;
    --color-hover:      #262626;
  }
}

@supports (color: light-dark(#000, #fff)) {
  :root {
    --color-text:       light-dark(#1a1a1a, #e8e8e8);
    --color-background: light-dark(#ffffff, #121212);
    --color-surface:    light-dark(#f7f7f7, #1b1b1b);
    --color-link:       light-dark(#0066cc, #66aaff);
    --color-focus:      light-dark(#004499, #9bd1ff);
    --color-border:     light-dark(#cccccc, #444444);
    --color-hover:      light-dark(#f5f5f5, #262626);
  }
}

/* Manual overrides narrow the active scheme when a user chooses one. */
[data-theme="light"] {
  color-scheme: light;
}
[data-theme="dark"] {
  color-scheme: dark;
}

コンポーネントのスタイルでは、色の値をハードコードせず、必ず上記のトークンを参照します。


必須:選択状態とフォーカス状態のスタイル

選択状態を色だけで示してはいけません。次のような複数の手がかりを使います。

  • aria-pressed
  • 表示されるチェックマークまたはアイコン
  • より太い、または明確に異なるボーダー
  • フォントの太さ
  • テキストラベル

フォーカス状態と選択状態は、視覚的に区別できるようにします。フォーカスされているが選択されていないオプションが、選択済みのオプションと同じ見た目になってはいけません。

.theme-mode-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border: 2px solid var(--color-border);
  border-radius: 0.375rem;
  background: var(--color-surface);
  color: var(--color-text);
  font-size: 1rem;
  cursor: pointer;
}

/* Focus indicator — visible outline, not box-shadow */
.theme-mode-btn:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* Selected state — distinct from focus */
.theme-mode-btn[aria-pressed="true"] {
  border-color: var(--color-link);
  font-weight: 600;
  background: var(--color-background);
}

:focus-visible を使い、実際に視認できるアウトラインを表示します。同等以上に視認できるフォーカスインジケーターで置き換える場合を除き、アウトラインを削除しないでください。


必須:強制配色への対応

セレクターは、Windows ハイコントラストモードなどの強制配色環境でも引き続き使用できる必要があります。次のものだけに依存しないでください。

  • 背景色
  • グラデーション
  • ボックスシャドウ
  • 不透明度
  • SVG の塗りつぶし色
  • 色の違い

適切な場合は、@media (forced-colors: active) 内でシステムカラーを使用します。

次の点を確認してください。

  • ボタンの境界が視認できる
  • 選択されたオプションを識別できる
  • フォーカスされたオプションを識別できる
  • 選択状態とフォーカス状態を区別できる
  • currentColor を使うアイコンが視認できる

具体的かつ文書化された理由があり、色をテスト済みである場合を除き、forced-color-adjust: none を使用しないでください。

@media (forced-colors: active) {
  .theme-mode-btn {
    border: 2px solid ButtonText;
    color: ButtonText;
    background: ButtonFace;
  }

  .theme-mode-btn:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  .theme-mode-btn[aria-pressed="true"] {
    border-color: Highlight;
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }
}

必須:アクセシブルな名前とステータス

表示されるテキストラベルとして、System、Light、Dark を使います。アイコンをラベルに添えてもかまいませんが、ラベルの代わりにしてはいけません。

装飾目的のアイコンには、必ず次を使用します。

<svg aria-hidden="true" focusable="false">...</svg>

次のような表現で、安定したボタンラベルを動的に置き換えないでください。

  • ダークに切り替え
  • 現在はダークモード
  • システムのダークモードを使用

ボタンのラベルでは、そのボタンが選択する設定を説明することを推奨します。選択状態は aria-pressed で伝えます。

たとえば、解決されたシステムテーマを説明する別のステータスメッセージを含めてもかまいません。

<p class="visually-hidden" aria-live="polite" id="theme-status">
  System preference currently uses dark mode.
</p>

この通知を過度に冗長にしないでください。何も変わっていないのに、同じ状態を繰り返し通知するのは避けてください。


必須:動きを抑える設定

デフォルトでは、色のアニメーション付きトランジションを使用しないでください。テーマの変更はビューポートの大部分に影響し、不快感や混乱を招く可能性があります。

トランジションの例を含める場合は、必ず次の条件を満たしてください。

  • 控えめである
  • すべての CSS プロパティをトランジションしない
  • prefers-reduced-motion に従う
  • 実際の状態変更を遅らせない
  • コントラストの低い中間状態を作り出さない

標準的な例では、テーマ変更のアニメーションを使用しないことを推奨します。

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

必須:コントラスト要件

このスキルでは、関連するすべての状態で WCAG 2.2 レベル AA のコントラストテストを引き続き必須とする必要があります。少なくとも次の状態をテストしてください。

  • システム設定がライトに解決される場合
  • システム設定がダークに解決される場合
  • ライトを明示的に選択した場合
  • ダークを明示的に選択した場合
  • 強制カラーモード
  • キーボードフォーカス
  • 選択済みのオプション
  • ホバー
  • 無効化されたコントロール(ある場合)
  • リンク
  • フォームフィールド
  • 境界線とグラフィカルオブジェクト

ライトモードでコントラスト要件を満たす色が、ダークモードでも要件を満たすと想定してはなりません。実際のコントラストテストの代わりとして、実験的な CSS 関数を提示してはなりません。


必須:完全な実装例

次の内容を含む完全な例を記載してください。

  1. アクセシブルな HTML
  2. CSS
  3. 描画前のフラッシュ防止初期化処理
  4. メインの JavaScript
  5. 安全なストレージ処理
  6. システム設定の変更への対応
  7. 強制カラーモード用のスタイル
  8. 視認可能なフォーカススタイル
  9. 選択状態のスタイル

この例は内部で一貫していなければなりません。すべてのコードサンプルで、同じストレージキー、データ属性、クラス名、モード名、選択状態の仕組みを使用してください。互いに矛盾する断片を提示してはなりません。

完全な HTML
<!DOCTYPE html>
<html lang="en" data-theme-mode="system" data-theme="light">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="color-scheme" content="light dark">
  <title>Theme Selector Example</title>
  <style>
    /* Anti-flash: set theme as early as possible */
    html[data-theme="dark"] {
      color-scheme: dark;
    }
    html[data-theme="light"] {
      color-scheme: light;
    }
  </style>
  <script>
    /* Early initialization — runs before first paint */
    (function() {
      var stored = null;
      try { stored = localStorage.getItem('theme-mode'); } catch (e) {}
      var mode = (stored === 'light' || stored === 'dark') ? stored : 'system';
      var resolved = mode === 'system'
        ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
        : mode;
      document.documentElement.setAttribute('data-theme-mode', mode);
      document.documentElement.setAttribute('data-theme', resolved);
    })();
  </script>
</head>
<body>
  <header>
    <nav aria-label="Main navigation">
      <a href="/">Home</a>
      <a href="/about">About</a>
    </nav>

    <div role="group" aria-label="Colour theme" id="theme-selector">
      <button type="button" class="theme-mode-btn" aria-pressed="false" data-theme-value="system">
        <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20">
          <path fill="none" stroke="currentColor" stroke-width="2" d="M3 4h18v12H3zM8 20h8"/>
        </svg>
        <span>System</span>
      </button>

      <button type="button" class="theme-mode-btn" aria-pressed="false" data-theme-value="light">
        <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20">
          <circle cx="12" cy="12" r="5" fill="currentColor"/>
        </svg>
        <span>Light</span>
      </button>

      <button type="button" class="theme-mode-btn" aria-pressed="true" data-theme-value="dark">
        <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24" width="20" height="20">
          <path fill="currentColor" d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
        </svg>
        <span>Dark</span>
      </button>
    </div>
  </header>

  <main>
    <h1>Theme Selector Example</h1>
    <p>This page demonstrates a three-option theme selector with System, Light, and Dark modes.</p>
  </main>

  <p class="visually-hidden" aria-live="polite" id="theme-status"></p>

  <script>
    /* Main theme logic — runs after DOM ready */
    (function() {
      var STORAGE_KEY = 'theme-mode';
      var VALID_MODES = ['system', 'light', 'dark'];
      var buttons = document.querySelectorAll('.theme-mode-btn');
      var prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
      var statusEl = document.getElementById('theme-status');
      var currentMode = 'system';

      function getStoredMode() {
        try {
          var stored = localStorage.getItem(STORAGE_KEY);
          return VALID_MODES.indexOf(stored) !== -1 ? stored : 'system';
        } catch (e) {
          return 'system';
        }
      }

      function setStoredMode(mode) {
        try {
          localStorage.setItem(STORAGE_KEY, mode);
        } catch (e) {
          /* Storage unavailable; preference still works for current session. */
        }
      }

      function resolveTheme(mode) {
        if (mode === 'system') {
          return prefersDark.matches ? 'dark' : 'light';
        }
        return mode;
      }

      function updateButtons(activeMode) {
        for (var i = 0; i < buttons.length; i++) {
          var btn = buttons[i];
          var isActive = btn.getAttribute('data-theme-value') === activeMode;
          btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
        }
      }

      function applyMode(mode) {
        currentMode = mode;
        var resolved = resolveTheme(mode);
        document.documentElement.setAttribute('data-theme-mode', mode);
        document.documentElement.setAttribute('data-theme', resolved);
        updateButtons(mode);
      }

      function handleActivation(event) {
        var btn = event.currentTarget;
        var mode = btn.getAttribute('data-theme-value');
        setStoredMode(mode);
        applyMode(mode);
      }

      /* Attach click handlers — no keyboard handlers needed */
      for (var i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener('click', handleActivation);
      }

      /* Listen for OS preference changes */
      prefersDark.addEventListener('change', function() {
        if (currentMode === 'system') {
          applyMode('system');
        }
      });

      /* Initialize */
      applyMode(getStoredMode());
    })();
  </script>
</body>
</html>

必須:避けるべき実装パターン

次のパターンは不適切なものとして扱う必要があります。

2択の切り替え

ライトとダークのみを切り替え、システム設定に戻す方法がない単一のボタン。

フォーカスによるプレビュー

フォーカスまたは矢印キーのイベントハンドラーでテーマを変更する JavaScript。

誤ったラジオボタンの実装

矢印キーでフォーカスを移動すると同時に、ページ全体のテーマも変更するラジオグループ。

誤った設定の保存

ユーザーが実際にはシステム設定を選択したのに、システムがたまたまダークだったため dark を保存すること。

色の違いだけによる選択状態の表示

背景色を変えるだけで、選択されたテーマを示すこと。

安全でないストレージ操作

例外処理をせずに localStorage.getItem() や setItem() を使用すること。

遅すぎる初期化

保存されたテーマの適用を DOMContentLoaded まで待つことで、回避可能な誤ったテーマのフラッシュが発生すること。

動的なラベルによる混乱

選択状態を aria-pressed で示すのではなく、選択中のオプションのアクセシブルネームを変更すること。


重大度の尺度(このスキル)

レベル意味
重大カラーテーマにより、コンテンツまたは操作にまったくアクセスできなくなる
深刻コントラストまたはモードの不具合により、障害のあるユーザーのグループのアクセスが大きく損なわれる
中程度テーマによってユーザビリティが低下するが、コンテンツには部分的にアクセスできる
軽微ベストプラクティス上の不足。影響はわずか

検証とテスト

リリース前に、次のすべてを確認してください。

キーボード
  1. ページを読み込みます。
  2. Tab キーでテーマセレクターに移動します。
  3. Tab キーを押し続け、システム、ライト、ダークの順に移動します。
  4. フォーカスの移動に伴ってページのテーマが変わらないことを確認します。
  5. 未選択のオプションで Enter キーを押します。
  6. テーマが一度だけ変わることを確認します。
  7. 別のオプションで Space キーを押します。
  8. テーマが一度だけ変わることを確認します。
  9. フォーカスが視認可能な状態に保たれることを確認します。
  10. 選択状態とフォーカス状態を見分けられることを確認します。
システムモード
  1. システム設定を選択します。
  2. システム設定が保存されることを確認します。
  3. オペレーティングシステムのカラー設定を変更します。
  4. 再読み込みせずにページが更新されることを確認します。
  5. 保存された値がシステム設定のままであることを確認します。
設定の保持
  1. ライトを選択します。
  2. 再読み込みします。
  3. ライトが選択されたままであることを確認します。
  4. ダークを選択します。
  5. 再読み込みします。
  6. ダークが選択されたままであることを確認します。
  7. システム設定を選択します。
  8. 再読み込みします。
  9. システム設定が選択されたまま、正しく解決されることを確認します。
ストレージエラー

localStorage が例外をスローする状況をテストまたはシミュレートします。次のことを確認してください。

  • ページが引き続き描画される
  • セレクターが引き続き動作する
  • 選択した設定が現在のページで有効になる
  • 捕捉されない JavaScript エラーによって実行が停止しない
支援技術

最低限、以下を使用してテストしてください。

  • デスクトップ用スクリーンリーダーを1つ
  • キーボードのみ
  • ブラウザーのズームを200%および400%に設定
  • 強制配色またはWindowsのハイコントラストモード
  • 実用的な場合は、タッチ操作またはモバイルでの操作

支援技術で以下が読み上げられることを確認してください。

  • グループラベル
  • 各ボタンのラベル
  • 押下状態または非押下状態
視覚的な安定性

セレクター内でキーボードを移動したときの様子を記録または確認してください。タブキーで移動しても、配色がプレビューされないことを確認してください。

自動テスト

リポジトリの既存のテスト規約を確認し、プロジェクトが対応している場合はテストを追加してください。実用的な場合、テストでは以下を検証することを推奨します。

  • 3つすべての選択肢が存在する
  • すべての選択肢がネイティブのボタンである
  • aria-pressed="true"を持つ選択肢は1つだけである
  • フォーカスだけではテーマが変わらない
  • クリックで有効化するとテーマが変わる
  • キーボードで有効化するとテーマが変わる
  • システムがprefers-color-schemeを追跡する
  • LightまたはDarkを明示的に選択すると、その後のシステムの変更が反映されない
  • 保存された値が無効な場合、Systemにフォールバックする
  • ストレージの例外が発生してもコントロールがクラッシュしない
  • 選択されたモードと解決されたテーマで、ルート属性が別々に使用される

必要でない限り、新しいテストフレームワークを導入しないでください。リポジトリの既存の手法に従ってください。


完了条件チェックリスト

  • すべてのテキスト/UI要素がライトモードでWCAG 2.2 AAのコントラスト要件を満たしている
  • すべてのテキスト/UI要素がダークモードでWCAG 2.2 AAのコントラスト要件を満たしている
  • :rootでcolor-scheme: light dark;を宣言している
  • テーマトークンでlight-dark()を使用し、prefers-color-schemeはフォールバックとしてのみ使用している
  • 強制配色モードでコンテンツが理解可能であり、影、グラデーション、背景だけで意味を伝えていない
  • フォーカスリングにbox-shadowだけではなく、outlineを使用している
  • 色だけで情報を伝えていない — アイコン+テキスト+色を使用している
  • すべてのモードでフォーカスインジケーターが視認でき、コントラスト比が3:1以上である
  • System、Light、Darkの3つの選択肢を備えたセレクターがあり、すべて同時に表示されている
  • セレクターでaria-pressedを備えたネイティブの<button type="button">を使用している
  • 明示的に有効化(Enter、Space、クリック)した場合にのみテーマが変わる
  • フォーカスだけではテーマが変わらない
  • systemモードでは、テーマがprefers-color-schemeから決まり、自動的に更新される
  • localStorageへのアクセスをtry/catchで囲んでいる
  • 保存された値が無効な場合、systemにフォールバックする
  • localStorageが利用可能な場合、ユーザーの設定がセッションをまたいで保持される
  • テーマの切り替え時にprefers-reduced-motionを尊重している
  • SVGでcurrentColorを使用している
  • color-mix()およびcontrast-color()を明示的なフォールバック値とともに@supportsで制御している
  • ブラウザーのサポート、強制配色、ハイコントラスト、ライトモード、ダークモード、およびWCAGコントラストの手動確認を検証している
  • キーボードユーザーがページの色を変えることなく、すべての選択肢をタブキーで移動できる

主要なWCAG基準

  • 1.4.1 色の使用(A)
  • 1.4.3 コントラスト(最低限)(AA)— いずれかのモードで不適合の場合、重大
  • 1.4.11 非テキストのコントラスト(AA)
  • 2.4.11 フォーカスの外観(AA、WCAG 2.2)

参考資料

今後の標準化の見通し: WCAG 3.0 で提案されている APCA(Advanced Perceptual Contrast Algorithm)は、現在の 4.5:1 / 3:1 の輝度比モデルを、明るい背景の暗い文字と暗い背景の明るい文字を異なるものとして扱う知覚的コントラストモデルに置き換えるものです。これはライトモードとダークモードのコントラスト目標に直接影響します。 WCAG 3.0 が公開標準になるまで、APCA を本番環境の作業に適用しないでください。 以下を確認してください:https://www.w3.org/TR/wcag-3.0/ および https://git.apcacontrast.com/

コメント