ダークモードの設計方針
ダークモードを後から追加するのは、ほぼ 作り直しと同義 になりがち。最初からトークン設計に組み込んでおくと、トークンの値を反転するだけで全体が切り替わる。
私のサイトでは、.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 変数を反転するだけで全体が切り替わる、という構造を組んでおくと、後から色を増やしても自動で対応する。
良いダークモードは「気付かないくらい自然な切替」。違和感が出るのは、トークン設計が部分的にしか効いていない印。