結論

プロのダークモードは「反転」ではなく「再設計」だ。土台に純黒#000000を使わずダークグレー**#121212**を基準サーフェスに置き、奥行きは影ではなく「上の面ほど明るくする」半透明白オーバーレイのランプ(0dp=0%→24dp=16%)で表現する。そのうえでアクセント色は彩度を落とした明トーンへ、本文は純白ベタをやめて白の不透明度87/60/38%で階層化する——この3点が揃って初めてダークモードは「映える」。

01 — 背景は純黒ではなく #121212

「ダークだから真っ黒」が最初の落とし穴。純黒#000000に純白#FFFFFFを乗せるとコントラストが過剰になり、文字がにじむハレーション/ブルーミング(縁が発光して振動する現象)を起こす。乱視・ディスレクシアのユーザーで可読性が落ち、OLEDではスメアも出る。基準を**#121212**にすると、影や標高オーバーレイが表現できるうえ、純白本文でも約15.8:1という十分なコントラストに収まる。

Aa 映え#000000 + #FFFFFF
✗ 純黒×純白 → 文字が発光してにじむ(ハレーション)
Aa 映え#121212 + 白87%
✓ #121212 ベース → ギラつかず長時間でも疲れない

primaryDark theme — Material Design (M2)

blogDark Mode Color Design — ColorArchive

02 — 奥行きは影ではなく「面の明るさ」で出す

ライトモードは影だけで重なりが伝わるが、ダークモードでは影は「光の反転=暗い値」なので暗背景上ではほとんど見えず、平坦で安っぽくなる。階層を伝える唯一の手はトーナル・エレベーション——#121212の上に白オーバーレイを重ね、標高が高いほど明るくする。正準ランプは 1dp=5%、3dp=8%、8dp=12%、24dp=16%。結果、card(1dp)≈#1E1E1E、dialog(3dp)≈#2C2C2C になる。

card
✗ #121212 のカードに黒い影 → 影が沈んで浮かない
card
✓ 面を #2C2C2C に明るく → 背景から面が浮き上がる

primaryDark theme — Material Design (M2)

bloggood dark mode shadows & elevation — parker.mov

03 — 上の面は必ず下の面より明るく

積層UI(ドロワー/シート/ポップオーバー)で前後関係を伝えるルールはシンプルだ。「上のサーフェスが下より明るいときだけ、重なり順が読める」。各レイヤーで背景に+4〜8%の明度(=上記オーバーレイ)を足す。下のbadは全レイヤー同色で影だけ足したもの——どれが手前か判然としない。goodは #1E1E1E→#272727→#323232 と上に行くほど明るく、奥行きが一目で伝わる。

1
2
3
✗ 全レイヤー同じ明度+影 → 階層が潰れる
1
2
3
✓ 上ほど明るく(+明度ランプ) → 重なり順が読める

blogDesigning a Scalable and Accessible Dark Theme — fourzerothree

primaryElevation — Atlassian Design

04 — アクセント色は彩度を落とした明トーンへ

ライトモードの飽和したブランド色をそのまま乗せると、暗背景で視覚的振動(vibration)を起こして眼精疲労を招き、WCAG AA 4.5:1も割って読みにくい。Materialはトーン200付近(範囲200〜50)の明るく彩度を落とした色を推奨する。目安は全体-10〜20%、青/シアンは-20〜30%、鮮やかな赤は70〜80%まで落とす。#6200EE→#B39DDB のように明トーンへ振り直す。

✗ 高彩度のまま(#6200EE / #3D5AFE) → 縁が振動して目が痛い
✓ 明トーンへ(#B39DDB / #9FA8FF) → 落ち着いて読める

blogDesign for the Dark Theme — Snapp Mobile

primaryDark theme — Material Design (M2)

05 — テキストは白の不透明度で階層化(87/60/38%)

本文に純白#FFFFFFをベタ塗りするとギラついて、しかも強調レベルの階層が作れない。プロは白を**不透明度87/60/38%**で運用する——高強調87%(見出し・本文)、中強調60%(補助・キャプション)、無効38%。ベタが要る場面は#E0E0E0〜#F0F0F0のオフホワイトに。これで過剰コントラストを避けつつ、見出し→本文→キャプションの濃淡が自然に立ち上がる。

見出しテキスト本文も同じ純白でベタ塗り。キャプションも純白。
✗ 全部 #FFFFFF → ギラつき+階層なしでのっぺり
見出しテキスト本文は白87%でしっとり。キャプションは白60%。無効状態は白38%。
✓ 87/60/38% → 濃淡で強調レベルが伝わる

secondaryDark Theme with MDC — Chris Banes

06 — 影を使うなら色付き多層か、1pxヘアラインで

オーバーレイだけで境界が曖昧なとき、または最上位レイヤーに追加の奥行きが欲しいとき——ライトモードの黒い影を流用してはいけない。暗背景で黒影は見えないか、見えても汚い。手は2つ。(1) 小さいUIやテーブルは1pxの淡いヘアライン(rgba(255,255,255,.08))で面を区切る。(2) 影を使うなら純黒透明ではなく彩度のある色味を付け(例 hsl(220deg 60% 50%))、低不透明度で多層に重ねる。Atlassianは最上位の標高にのみ影を残す。

card
✗ ライトの黒い影を流用 → 汚く沈むだけ
card
✓ 1pxヘアライン+青味の多層影 → 締まって浮く

primaryElevation — Atlassian Design

blogDesigning Beautiful Shadows in CSS — Josh W. Comeau

実装スニペット

標高サーフェスのトークン(白オーバーレイ正準ランプ)。影ではなくこの明度差で奥行きを出す。

:root[data-theme="dark"] {
  --surface-0:  #121212;  /* 0dp  base */
  --surface-1:  #1E1E1E;  /* 1dp  = 5%  white overlay */
  --surface-2:  #232323;  /* 2dp  = 7%  */
  --surface-3:  #252525;  /* 3dp  = 8%  -> cards/dialogs */
  --surface-4:  #272727;  /* 4dp  = 9%  */
  --surface-6:  #2C2C2C;  /* 6dp  = 11% */
  --surface-8:  #2E2E2E;  /* 8dp  = 12% -> app bars */
  --surface-12: #323232;  /* 12dp = 14% */
  --surface-16: #353535;  /* 16dp = 15% */
  --surface-24: #373737;  /* 24dp = 16% (max) */
}

オーバーレイをCSSで合成(任意のブランド面)。標高が上がるほど不透明度を上げる(最大16%)。

.card {
  /* base #121212 + 8% white overlay (≈3dp相当) */
  background-image:
    linear-gradient(rgba(255,255,255,.08), rgba(255,255,255,.08));
  background-color: #121212;
  border-radius: 12px;
}
.card--branded {
  /* #121212 + 8% primary でブランド面に (例: #1F1B24) */
  background-image:
    linear-gradient(rgba(124,77,255,.08), rgba(124,77,255,.08));
  background-color: #121212;
}

テキスト強調レベル(白の不透明度 87/60/38%)。#121212上で高強調はおよそ15:1級、AA 4.5:1を余裕で満たす。

:root[data-theme="dark"] {
  --text-high:     rgba(255,255,255,.87);  /* 見出し・本文 */
  --text-medium:   rgba(255,255,255,.60);  /* 補助・キャプション */
  --text-disabled: rgba(255,255,255,.38);  /* 無効状態 */
  --text-body:     #E6E6E6;                /* ベタが要る場面のオフホワイト */
}
body { background:#121212; color:var(--text-high); }
.caption { color:var(--text-medium); }

アクセント色のダーク再調整と、影/境界の代替。グローで標高表現はしない(Material非推奨)。

:root[data-theme="dark"] {
  --primary: #B39DDB;          /* not #6200EE。明トーン(~tone200)でAA 4.5:1を狙う */
  --shadow-color: 220deg 60% 50%;
}
.card {
  border: 1px solid rgba(255,255,255,.08);  /* 影が見えない時の境界補強 */
  box-shadow:
    0 1px 2px hsl(var(--shadow-color) / .2),
    0 2px 4px hsl(var(--shadow-color) / .2),
    0 4px 8px hsl(var(--shadow-color) / .2);
}
/* color-mix が使えるモダン環境ならトークン生成も1行 */
.surface-3 { background: color-mix(in srgb, #121212, white 8%); }

チェックリスト

  • 背景に純黒 #000000 を使っていない(基準は #121212)
  • カード/モーダルの奥行きを黒い影ではなく「面の明るさ」で出している
  • 積層したレイヤーは上に行くほど明るい(+4〜8%の明度ランプ)
  • アクセント/ブランド色を彩度を落とした明トーン(~tone200)へ再調整した
  • CTA・リンク・本文の主要な色で WCAG AA 4.5:1 を実測で確認した
  • 本文に純白ベタを使わず、白の不透明度 87/60/38% で階層化した
  • 影を使う箇所は黒の流用でなく色付き多層、または 1px ヘアラインにした
  • グロー(発光)で標高を表現していない(Material 非推奨)
  • color-mix / OKLCH を使う場合、レガシー環境向けに固定hexのフォールバックを併記した

限界 / 出典

注意:数値の正準ソースは Material Design M2 で、Material 3 では surface-container 系のトーナル設計に体系が変わっている。実装時はどちらのバージョンに準拠するか明示すること。近似hex(#1E1E1E 等)は #121212 に白を重ねた計算値で、実機/ブラウザのアルファ合成や色空間で 1〜2 刻みのズレが出る。15.8:1 や 18.75:1 はサーフェス対100%白文字の理論値であり、不透明度を落とした文字やアクセント色では別途 WCAG を実測すべき(最低 AA 4.5:1)。アクセント減彩度の具体%(青-20〜30%、赤70〜80%)と Josh Comeau の影レシピは実務ブログ由来の経験則で、ブランド/色相ごとに調整が要る。OLED 省電力で純黒が好まれる文脈はあるが、本記事は「映え/可読性」を最優先するため純黒は非推奨としている。color-mix / OKLCH はブラウザサポートに依存し、レガシー対応の LP/バナーではフォールバック必須。

primaryDark theme — Material Design (M2)

primaryapplyElevationOverlayColor — Flutter material API

primaryElevation — Atlassian Design

secondaryDark Theme with MDC — Chris Banes (Android Developers)

secondaryDesign for the Dark Theme — Snapp Mobile

blogDesigning Beautiful Shadows in CSS — Josh W. Comeau

bloggood dark mode shadows & elevation — parker.mov

blogDark Mode Color Design — ColorArchive

blogDesigning a Scalable and Accessible Dark Theme — fourzerothree

blogDark Mode UI: 11 Tips — Netguru