Vol.042026年5月9日
Design

トークン反転だけでダークモードを実装する

個別 CSS で `.dark` を上書きせず、`@theme` のセマンティックトークンを反転させるだけでダーク対応する設計。

2026年5月9日·2 min·SoraEndo
SoSoraEndo2026年5月9日2 min636

ダークモードの設計方針

ダークモードを後から追加するのは、ほぼ 作り直しと同義 になりがち。最初からトークン設計に組み込んでおくと、トークンの値を反転するだけで全体が切り替わる。

私のサイトでは、.dark クラスがついたときに 10 個程度の CSS 変数を反転するだけで、<html.dark> に切り替わった瞬間に全コンポーネントがダークになる。

構造

/* 通常: light */
:root {
  --color-paper:        #FAFAF7;
  --color-paper-alt:    #F1F1EC;
  --color-paper-sunken: #E8E8E2;
  --color-ink:          #0E0E0E;
  --color-ink-2:        #3A3A38;
  --color-ink-muted:    #6E6E6A;
  --color-rule:         #1F1F1D;
  --color-rule-soft:    #D9D9D2;
}

/* ダーク */
:root.dark, :root[data-theme="dark"] {
  --color-paper:        #0E0E0E;
  --color-paper-alt:    #1A1A1A;
  --color-paper-sunken: #232323;
  --color-ink:          #F0F0EC;
  --color-ink-2:        #B5B5B0;
  --color-ink-muted:    #7A7A75;
  --color-rule:         #F0F0EC;  /* ← 反転 */
  --color-rule-soft:    #2A2A28;
}

これだけ。bg-paper text-ink のような Tailwind utility は CSS 変数を参照する ので、変数の値が変われば自動で全部置き換わる。

カテゴリ色も少し明るくする

ダーク背景に純色の青を載せると蛍光感が出る。明度を上げる方向でカテゴリ色を別途定義する:

.dark {
  --color-cat-engineering: #5C82FF;  /* 元 #2D5BFF より明るく */
  --color-cat-design:      #FFC54D;  /* 元 #FFB020 */
  --color-cat-ai:          #33C28C;
  --color-cat-process:     #C99063;
  --color-cat-essay:       #FF6B73;
}

ポイントは 彩度をそのままで明度だけ上げる。彩度を上げると目に痛い。

FOUC を防ぐ初期化スクリプト

<html.dark> をクラスとして付ける場合、HTML 初回ロード時に「light で表示 → script 実行で dark に切り替え」の 一瞬の白い chカ が出る。これを防ぐには _document.tsx で React のレンダ前に同期で class を付けるスクリプトを inject する。

// pages/_document.tsx
const initScript = `(function(){
  try {
    var t = localStorage.getItem('theme') || 'system';
    var dark = t === 'dark' || (t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
    var root = document.documentElement;
    if (dark) {
      root.classList.add('dark');
      root.setAttribute('data-theme', 'dark');
    }
  } catch (e) {}
})();`;

<Head>
  <script dangerouslySetInnerHTML={{ __html: initScript }} />
</Head>

これで body 描画前に .dark クラスが付くので、初期表示で白チラがない。

トグルは 3 値

light / dark / system の 3 値トグルにした。system は OS の prefers-color-scheme を尊重する。

const toggleTheme = () => {
  const root = document.documentElement;
  const next = root.classList.contains('dark') ? 'light' : 'dark';
  root.classList.toggle('dark', next === 'dark');
  root.setAttribute('data-theme', next);
  localStorage.setItem('theme', next);
};

シンプルな 2 値(light/dark)でも実用上は十分。3 値は個人的なこだわり。

まとめ

ダークモードは「最初からトークン設計に組み込む」が正解。後付けでも組めるが、3〜5 倍の時間がかかる。

10 個程度の CSS 変数を反転するだけで全体が切り替わる、という構造を組んでおくと、後から色を増やしても自動で対応する。

良いダークモードは「気付かないくらい自然な切替」。違和感が出るのは、トークン設計が部分的にしか効いていない印。

Tags

Reaction

Share

X (Twitter)