ブログにダークモードを実装した
このブログには元々ダークモードがありませんでした。
ブログをはてなブログから独自のシステムに移行した2019年は、Android 10やiOS 13でダークモードへの切り替えがリリースされて、ようやくダークモードが一般層にも広がる素地ができた年でした。
このブログにも2020年5月の段階でダークモードを導入したいとissueは作っていました。そこから構想5年を経て、重い腰を上げダークモードを実装しました。嘘です。ずっと構想していたわけではありません。
今回の実装で考慮した点や、今後の課題についてまとめます。
モードの切り替え実装
今回は data-theme 属性をhtml要素に付与し、CSSでライトモードとダークモードのスタイルを定義する方法を採用しました。
/* 実装サンプル */
[data-theme='light'] {
--color-neutral-background: oklch(0.97 0 0);
--color-neutral-text: oklch(0.21 0 0);
}
[data-theme='dark'] {
--color-neutral-background: oklch(0.28 0 0);
--color-neutral-text: oklch(0.97 0 0);
}
読者がブログのテーマを選べるようにしたかったため、data-theme 属性でモードを切り替える実装を採用しました。OSの設定に従う「システム」と、明示的な「ライト」「ダーク」の計3つから表示モードを選択できます。
テーマを切り替えるUIではPopover APIを使っています。
<button type="button" popovertarget="theme-popover">
<!-- アイコン -->
</button>
<div id="theme-popover" popover>
<button data-theme-value="system">システム</button>
<button data-theme-value="light">ライト</button>
<button data-theme-value="dark">ダーク</button>
</div>
Popover APIはJavaScriptを使わなくともポップオーバーの表示・非表示を切り替えられます。他とえば前述のコードだと popover="auto" を指定した状態になり、ポップオーバー領域の外側やEscキーを押すことでポップオーバーを非表示にできます。
またポップオーバーの位置決めにCSS Anchor Positioningを使っています。Baseline 2026なのでようやく主要ブラウザーの安定版で動作するようになった機能です。
.TriggerButton {
anchor-name: --theme-trigger;
}
.Popover {
position-anchor: --theme-trigger;
top: anchor(bottom);
left: anchor(left);
}
ポップオーバーの位置は今までJavaScriptで位置計算をして実装するしかなかったですが、Anchor Positioningが出てきたことでCSSだけを使ってポップオーバーの配置を決められるようになったのは便利だと感じました。dialog要素が登場した時と同じ感覚です。
モードの設定はCookieに保存しています。ページ読み込み時にCookieからテーマを読み取り、html要素の data-theme 属性に反映しています。
<html lang="ja" data-theme="system">
<script>
(() => {
const match = document.cookie.match(/(?:^|; )theme=([^;]*)/);
const theme = match ? match[1] : 'system';
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<!-- ... -->
</html>
OKLCH色空間の採用
色の定義にはOKLCHを採用しました。従来のHEX値やHSLではなく、OKLCHを選んだ理由は知覚的な均一性を得られるためです。
OKLCHの色空間を使うことで、色を比較したときに一方の色が沈んで見えたり、色が違って見えるといった問題が起きにくくなります。OKLCH Color Picker & Converterといった直感的な色調整ツールがあるのも良いです。
ただ、色の選定には想定以上に時間がかかりました。ライトモードの色をそのまま反転させても違和感のある配色になります。結局、ライトモードとダークモードの両方とも一から色を選び直しました。
コントラストの調整
コントラストの基準はWCAG 3で採用予定のAPCAを満たすようにしました。測定にはAPCA Contrast Calculatorを使いました。
今回の調整で一番苦労したのはメインの色です。今までは #003760 ——OKLCHでは oklch(0.329 0.0888 248.18) を使っていましたが、ダークモードの背景 oklch(0.28 0 0) と合わせたときにコントラストがLc 0となってしまうため色を変えなくてはいけませんでした。
個人的には深みのある青色の象徴として #003760 という値を暗唱できるくらいには気に入っていましたが、コントラストの関係で色を調整せざるを得なくなり、ライトモードでは oklch(0.43 0.119 253) を使い、ダークモードでは oklch(0.51 0.141 253) を使うようにしました。とはいえ、新しい色もすでに慣れました。
まとめ
ダークモード対応は思った以上に奥が深いものでした。結果的に配色を一から考え直すことになりました。その分、個人的には気にいった見た目になりました。もしダークモードを使っている人がいれば、見え方のフィードバックをもらえると嬉しいです。
参考リンク
- Popover API
- HTML popover グローバル属性 - HTML | MDN
- CSS アンカー位置指定 - CSS | MDN
- Anchor Positioningが全対応。HTML・CSSだけのポップオーバーが完全体に
- oklch() - CSS | MDN
- OKLCH Color Picker & Converter
- Charcoal 2.0: デザインシステムの基盤を再構築 - Speaker Deck
- 第2回 WCAG3のコントラスト基準APCAの考え方と実例 | gihyo.jp
- APCA in a Nutshell | APCA