---
title: "ブログにダークモードを実装した"
published: 2026-02-02
tags: [CSS, JavaScript, ブログ, ダークモード]
author: kubosho
---
# ブログにダークモードを実装した
このブログには元々ダークモードがありませんでした。
ブログを[はてなブログから独自のシステムに移行した2019年](https://blog.kubosho.com/entries/migrating-from-contentful-to-markdown-file)は、Android 10やiOS 13でダークモードへの切り替えがリリースされて、ようやくダークモードが一般層にも広がる素地ができた年でした。
このブログにも2020年5月の段階でダークモードを導入したいと[issue](https://github.com/kubosho/blog.kubosho.com/issues/298)は作っていました。そこから構想5年を経て、重い腰を上げダークモードを実装しました。嘘です。ずっと構想していたわけではありません。
今回の実装で考慮した点や、今後の課題についてまとめます。
## モードの切り替え実装
今回は `data-theme` 属性をhtml要素に付与し、CSSでライトモードとダークモードのスタイルを定義する方法を採用しました。
```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を使っています。
```html
```
Popover APIはJavaScriptを使わなくともポップオーバーの表示・非表示を切り替えられます。他とえば前述のコードだと `popover="auto"` を指定した状態になり、ポップオーバー領域の外側やEscキーを押すことでポップオーバーを非表示にできます。
またポップオーバーの位置決めにCSS Anchor Positioningを使っています。Baseline 2026なのでようやく主要ブラウザーの安定版で動作するようになった機能です。
```css
.TriggerButton {
anchor-name: --theme-trigger;
}
.Popover {
position-anchor: --theme-trigger;
top: anchor(bottom);
left: anchor(left);
}
```
ポップオーバーの位置は今までJavaScriptで位置計算をして実装するしかなかったですが、Anchor Positioningが出てきたことでCSSだけを使ってポップオーバーの配置を決められるようになったのは便利だと感じました。[dialog要素](https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/dialog)が登場した時と同じ感覚です。
---
モードの設定はCookieに保存しています。ページ読み込み時にCookieからテーマを読み取り、html要素の `data-theme` 属性に反映しています。
```html
```
## OKLCH色空間の採用
色の定義にはOKLCHを採用しました。従来のHEX値やHSLではなく、OKLCHを選んだ理由は知覚的な均一性を得られるためです。
OKLCHの色空間を使うことで、色を比較したときに一方の色が沈んで見えたり、色が違って見えるといった問題が起きにくくなります。[OKLCH Color Picker & Converter](https://oklch.com/)といった直感的な色調整ツールがあるのも良いです。
ただ、色の選定には想定以上に時間がかかりました。ライトモードの色をそのまま反転させても違和感のある配色になります。結局、ライトモードとダークモードの両方とも一から色を選び直しました。
## コントラストの調整
コントラストの基準はWCAG 3で採用予定の[APCA](https://gihyo.jp/article/2023/08/apca-02)を満たすようにしました。測定には[APCA Contrast Calculator](https://apcacontrast.com/)を使いました。
今回の調整で一番苦労したのはメインの色です。今までは `#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](https://developer.mozilla.org/ja/docs/Web/API/Popover_API)
- [HTML popover グローバル属性 - HTML | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Global_attributes/popover)
- [CSS アンカー位置指定 - CSS | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Guides/Anchor_positioning)
- [Anchor Positioningが全対応。HTML・CSSだけのポップオーバーが完全体に](https://zenn.dev/ubie_dev/articles/anchor-positioning-popover)
- [oklch() - CSS | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Values/color_value/oklch)
- [OKLCH Color Picker & Converter](https://oklch.com/#0.7,0.1,61,100)
- [Charcoal 2.0: デザインシステムの基盤を再構築 - Speaker Deck](https://speakerdeck.com/godlingkogami/charcoal-2-dot-0-tesainsisutemunoji-pan-wozai-gou-zhu)
- [第2回 WCAG3のコントラスト基準APCAの考え方と実例 | gihyo.jp](https://gihyo.jp/article/2023/08/apca-02)
- [APCA in a Nutshell | APCA](https://git.apcacontrast.com/documentation/APCA_in_a_Nutshell)