はじめに:ダークモードトグルは意外と奥が深い

Webサイトのヘッダーにダークモードトグルを1つ置くだけ——一見シンプルに見えますが、実装を始めると悩みどころが出てきます。ライト/ダーク/システムの3つの選択肢をすべて見せるべきか、それとも2つで足りるのか。

最近、CSSコミュニティでこのテーマが活発に議論されました。結論から言うと、常時表示されるトグルでは2状態で十分という見解に落ち着きつつあります。単なる「シンプルだから」ではなく、ユーザーの認知負荷と実際の利用文脈を考慮した論理的な理由があります。

本記事ではその議論の要点を整理し、実務でそのまま使える実装パターンまで共有します。詳細はCSS-Tricksの原文をご参照ください。

UI designer comparing two-state and three-state dark mode toggle designs on a whiteboard IT Technology Image

核心となる論理:なぜ3状態トグルが問題なのか

1. 2状態トグルで3つの状態をすべて表現できる

最も強力な主張はこれです。**「システム設定を尊重し、必要なときだけオーバーライドする」**というパターンであれば、「System」ボタンを露出させる必要はありません。

  • ユーザーが何も設定していない → システム設定に従う(localStorageは空)
  • ユーザーがトグルを押した瞬間 → 明示的なオーバーライド(localStorageに保存)

つまり、「設定なし」という状態自体をUIで表現する必要がないという観点です。

2. 現在の状態に応じて反対のオプションだけ見せればよい

さらに、現在どのテーマがアクティブか分かれば、トグル自体を1つだけ表示することも可能です。

  • デフォルトがライト → 「ダークに切り替え」ボタンのみ表示
  • デフォルトがダーク → 「ライトに切り替え」ボタンのみ表示
// システムテーマ検出 + localStorageオーバーライドのパターン
const getPreferredTheme = () => {
  // 1. ユーザーが明示的に設定した値があれば優先
  const stored = localStorage.getItem('theme');
  if (stored) return stored;

  // 2. なければシステム設定に従う
  return window.matchMedia('(prefers-color-scheme: dark)').matches
    ? 'dark'
    : 'light';
};

const applyTheme = (theme) => {
  document.documentElement.setAttribute('data-theme', theme);
};

// トグル:現在のテーマの逆に切り替えて保存
const toggleTheme = () => {
  const current = getPreferredTheme();
  const next = current === 'dark' ? 'light' : 'dark';
  localStorage.setItem('theme', next);
  applyTheme(next);
};

// 初期適用
applyTheme(getPreferredTheme());

// システム設定の変更を検知(オーバーライドがない場合のみ反映)
window.matchMedia('(prefers-color-scheme: dark)')
  .addEventListener('change', (e) => {
    if (!localStorage.getItem('theme')) {
      applyTheme(e.matches ? 'dark' : 'light');
    }
  });

このコードのポイントは、**localStorageに値があるかどうかがそのまま「オーバーライドの有無」**になる点です。別途「System」状態を保存する必要がありません。

Developer implementing dark mode toggle with localStorage in a code editor Technical Structure Concept

注意点と発展的なTips

1. 「システムに戻す」はどう扱うか

ここでよく出る疑問があります。「ダークに変えてから再びライトに戻した場合、それはシステム設定への復帰なのか、それともライト固定なのか」という点です。

結論としては、単純にライト固定として扱って問題ありません。理由は次のとおりです。

  • テーマトグルはユーザーの主目的とは無関係な付加機能です。
  • システム設定とオーバーライドが食い違うのはOSのテーマが変わったタイミングですが、そのときにもう一度クリックすれば済みます。
  • この程度の摩擦はユーザーが認識することはほぼありません。

2. 3状態トグルが正当化される例外

とはいえ、3状態が常に悪いわけではありません。次の2ケースでは3状態のほうが適切です。

  • 独立した設定パネル内に配置する場合(ヘッダーの常時トグルではなく設定ページ内)
  • システム設定に応じて異なるカラースキームを提供する場合(例:ライト/ダーク/ハイコントラストなど3種類以上)

つまり、「常時表示されるヘッダートグル」という文脈が2状態を正当化する鍵となります。

3. トグル自体をなくす選択肢

最も過激な(そしておそらく最良の)解決策は、トグルを一切置かないことです。prefers-color-schemeに従うだけにすればUIも減り、ユーザーも迷いません。ただしユーザー層が多様なサービスであれば、オーバーライド手段は必要でしょう。

💡 実務Tips:FOUC(Flash of Unstyled Content)を防ぐには、<head>内のインラインスクリプトでテーマを即時適用してください。Reactであればnext-themesのようなライブラリがこの問題をうまく処理してくれます。

Laptop screen showing a website header with a simple light/dark toggle switch Coding Session Visual

まとめ:少ないほうが良い、ただし文脈を理解した上で

整理すると次のとおりです。

  1. 常時表示トグルには2状態で十分です。「System」オプションは認知負荷を増やすだけです。
  2. localStorageの有無がそのままオーバーライド状態なので、別途状態管理は不要です。
  3. 設定パネルや3種類以上のスキームを提供する場合にのみ3状態を検討してください。
  4. 究極的にはトグルなしのデザインも真剣に検討する価値があります。

日本の開発現場での適用文脈

日本の受託開発では、クライアント要望で「3つ全部入れてほしい」と言われることがあります。そんなときは本記事の論理を根拠に「2状態で十分であり、ユーザー離脱を減らせる」と説得してみてください。特に管理画面のように設定項目が多い画面と、ユーザー向けヘッダーは分けて考えるのが良いでしょう。

次のステップ学習方向

  • prefers-color-schemeメディアクエリとcolor-scheme CSSプロパティの違い
  • CSS変数を活用したテーマシステム設計(StyleXガイドも大きな枠組みで繋がります)
  • FOUC防止のためのSSR環境でのテーマ注入戦略

あわせて読みたい記事

本コンテンツは、信頼性の高い情報源をもとにAIツールを活用して作成され、編集者によるレビューを経て公開されています。専門家によるアドバイスの代替となるものではありません。