0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ダークモードを data 属性 + CSS 変数に集約する — MUI と自前スタイルを揃える

0
Last updated at Posted at 2026-08-10

こんにちは。小学生向けのニュースサイト、こどもニュースをつくっています。

このサイトにはライトとダークの切り替えがあります。「システムに従う」も選べます。

MUI を使っているので配色はテーマで管理できるのですが、テーマの外にも色があります。ルビの点線、スクロールバー、フォーカスのリングです。この 2 つを揃えるのに少し手間が要りました。

MUI の色スキームと data 属性

MUI には色スキームの仕組みがあり、CSS 変数として出力できます。切り替えの目印になるセレクタも指定できます。

ダークテーマは MUI の colorSchemes + CSS 変数で実現する。
colorSchemeSelector: 'data-kn-theme' により、light 変数は `:root, [data-kn-theme="light"]`、
dark 変数は `[data-kn-theme="dark"]` に出力される。
cssVariables: { colorSchemeSelector: 'data-kn-theme' },

<html data-kn-theme="dark"> が付いていればダーク、という形になります。

この属性は、最初のペイント前にブートスクリプトが付けます。静的サイトなので、そうしないと一瞬ライトが見えます。hydration の後は MUI の仕組みが引き継ぎます。

初回ペイント前にブートスクリプトがスタンプし、hydration 後は MUI の CssVarsProvider が引き継ぐ。

同じ属性を 2 つの仕組みが触るので、解決される値が一致している必要があります。ずれると、hydration のタイミングで色が変わります。

テーマの外の色をどうするか

MUI のテーマに載らない色があります。

  • ルビの点線(漢字の下に引く線)
  • ルビのハイライト(読みが出ている漢字の背景)
  • フォーカスのリング
  • スクロールバーの色
  • 目次のダッシュ

これらは自前の CSS で当てているので、ダークのときの値も自前で持つことになります。

CSS 変数にまとめました。

:root {
  --kn-ruby-highlight: #ffffc9;
  --kn-ruby-line: ;
  --kn-rt-color: #1a1a1a;
  --kn-focus-ring: ;
  --kn-toc-dash: rgba(26,26,26,0.22);
  
}
[data-kn-theme="dark"] {
  --kn-ruby-highlight: #4d4718;
  --kn-rt-color: #e9e5dd;
  --kn-toc-dash: rgba(242,239,233,0.28);
  
}

同じ詳細度なので、記述順でダークが勝ちます。使う側は変数を参照するだけで、スキームを気にしません。

ruby.kn { border-bottom: 1px dotted var(--kn-ruby-line); }

重複する色を 1 か所にまとめる

問題は、テーマのパレットと CSS 変数に同じ色が現れることでした。

ルビの点線の色は、テーマの成功色と同じ緑です。フォーカスのリングも同じ系統。両方にリテラルで書いていたので、片方だけ変えると食い違います。

/** WebTheme の MUI palette と globalStyles の --kn-* 変数が共有する色トークン。
 *  片方だけ変えるとルビ点線・スクロールバー・focus リングが本体テーマとズレるため、
 *  両ファイルに重複していたリテラルはここだけで定義する(色名は DESIGN.md に対応)。
 *  どちらか一方でしか使わない色(--kn-ruby-highlight 等)は従来どおり各ファイルに残す。 */
export const KN_COLORS = {
  light: {
    meadowGreen: '#00ca48',     // success.main / --kn-ruby-line(ルビ点線)
    meadowGreenDeep: '#00913a', // success.dark / --kn-focus-ring
    stone: '#f2f0ed',           // divider / --kn-scrollbar-track
    subGray: '#6d6d6d',         // text.secondary / --kn-scrollbar-thumb
    pageBg: '#fbfaf9',          // background.default / layout.tsx の theme-color
    

共有するものだけをここに置き、片方でしか使わない色は元の場所に残しました。

全部をまとめようとすると、この定数が「色の一覧」になって、どこで使うかが分からなくなります。まとめる基準は「2 か所以上で同じ値を使っているか」にしました。

コメントに対応関係を書いてあるので、変えたときの影響範囲が読めます。

モバイルのブラウザ chrome にも渡す

pageBg は、ページの地色であると同時に、モバイルのブラウザの上部バーの色(theme-color)でもあります。

ここが揃っていないと、スクロールしたときに境目が見えます。同じ値を使うので、共有の定数に入れました。

ただし theme-color には制約があります。メタタグなので、サイト内の切り替えには追従しません。OS の設定にだけ反応します。この制約は受け入れて、light と dark の値をメディアクエリで書き分けています。

「サイト内の切り替えに追従しない」ことを知らずに実装すると、バグとして追いかけることになります。仕様上できないことは、コメントに書いておくのが親切でした。

「システムに従う」の扱い

3 択(ライト・ダーク・システム)にすると、保存する値が増えます。

このサイトでは cookie に light / dark / auto を保存しています。auto のときは、OS の設定に従って解決します。

注意したのは、既定値と明示的な選択を区別することでした。以前は既定が auto だったので、保存された auto が「何も選んでいない」なのか「システムを選んだ」なのかが分かりません。

既定を変えるときに、この区別が必要になりました。明示的に選んだ人はそのまま、選んでいない人は新しい既定に、としたいところです。

保存された値だけでは区別が付かないので、移行の処理を入れました。設定の既定を変える可能性があるなら、「未設定」と「明示的に既定と同じ値を選んだ」は別の状態として持つほうが安全です。

まとめ

  • MUI の色スキームは data 属性をセレクタにできる。静的サイトではその属性を最初のペイント前に付ける
  • テーマに載らない色は CSS 変数にまとめ、スキームごとに値を差し替える
  • パレットと CSS 変数で同じ色を使うなら、共有の定数に置く。まとめる基準は「2 か所以上で使うか」
  • 定数には対応関係をコメントで書く。変えたときの影響が読める
  • theme-color はサイト内の切り替えに追従しない。仕様上の制約はコメントに残す
  • 「システムに従う」を選べるなら、未設定と明示的な選択を区別できるようにしておく
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?