MaterializeCSS 2で作るダークモード・ボタン
MaterializeCSS 2.xを使用して、OSレベルのテーマ検出を行うダークモードボタンの実装方法を説明します。MaterializeCSSはバージョン2からMaterial Design M3への対応が進んでいます。
このようなボタンのコンポーネントを作成します。中央のボタンは、OSのテーマ設定に従うためのものです。

MaterializeCSSのテーマ設定についてのドキュメントでは、ライト/ダークのトグルボタンの作成について説明していますが、OSのテーマ設定を検出して従う方法については触れられていません。ここでは、その実現方法を紹介します。
実装の核心
MaterializeCSSがよくできているお陰で、基本的なコードは驚くほどシンプルです。このコードは、<html>タグにtheme="light"またはtheme="dark"属性を追加するか、ユーザーがOSモードを選択した場合は属性を削除します:
if (mode === 'dark' || mode === 'light') { document.documentElement.setAttribute('theme', mode);
} else if (mode === 'os') {
document.documentElement.removeAttribute('theme');
}
なぜこのコードは機能するのか?
このコードが機能する理由は、CSS変数とメディアクエリ(@media)が自動的に選択されたテーマに適応するためです。以下は、ライトモードとダークモードのCSS変数を設定するtheme.module.scssの抜粋です:
:root { color-scheme: light;
--md-sys-color-surface: var(--md-sys-color-surface-light);
--md-sys-color-on-surface: var(--md-sys-color-on-surface-light);
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--md-sys-color-surface: var(--md-sys-color-surface-dark);
--md-sys-color-on-surface: var(--md-sys-color-on-surface-dark);
}
}
:root[theme='light'] {
color-scheme: light;
--md-sys-color-surface: var(--md-sys-color-surface-light);
--md-sys-color-on-surface: var(--md-sys-color-on-surface-light);
}
:root[theme='dark'] {
color-scheme: dark;
--md-sys-color-surface: var(--md-sys-color-surface-dark);
--md-sys-color-on-surface: var(--md-sys-color-on-surface-dark);
}
変数--md-sys-color-surfaceは背景の色を表し、ライトモードでは白(#fcfcff)、ダークモードでは黒(#1a1c1e)になります。theme属性を切り替えることで、ブラウザはテーマに応じた色を自動的に選択します。
CSSにcolor-schemeを追加
両方のモード(ライトとダーク)を完全にサポートするために、CSSにcolor-scheme: light dark;を追加する必要があります:
:root { color-scheme: light dark;
}
これにより、ブラウザはユーザーやOSの設定に応じて、適切な色のスキームを自動的に適用します。
CSSはどう適用されるか
このような設定は以下のように機能します。
<html>タグにtheme="light"が設定されている場合、:root[theme='light']のCSS変数が適用されます。theme="dark"が設定されている場合、:root[theme='dark']のCSS変数が適用されます。- 属性
themeがなく、OSがダークモードの場合は、@media (prefers-color-scheme: dark)のCSS変数が適用されます。 - 属性
themeがなく、OSがライトモードの場合は、デフォルトの:rootのCSS変数が適用されます。
コード全体
以下が<dark-mode>コンポーネント全体のコードです。Aurelia (1)という、とても簡単に書けるフレームワークとAurelia-Materialize Bridgeを使用しています。方式自体は他のJavaScriptフレームワークでも適用可能です。
選択したモードはlocalStorageに保存され、アプリケーションの起動時にrestoreDarkMode()を呼び出すことで復元できます。.active-shadeはライトモードでもダークモードでもボタンに選択状態を表す影をつけます。
- app.html <style> :root { color-scheme: light dark; } </style> <require from="../resources/dark-mode"></require> <dark-mode></dark-mode>
- dark-mode.html
<template>
<style>
.active-shade .active {
background-color: rgba(128, 128, 128, 0.2);
}
</style>
<div class="center active-shade">
<button md-button="flat: true;" click.delegate="setDarkMode('light')" class="${mode=='light' ? 'active' : ''}" title="${'dark.light' & t}">
<i class="material-icons-outlined">light_mode</i>
</button>
<button md-button="flat: true;" click.delegate="setDarkMode('os')" class="${mode=='os' ? 'active' : ''}" title="${'dark.os' & t}">
<i class="material-icons-outlined">computer</i>
</button>
<button md-button="flat: true;" click.delegate="setDarkMode('dark')" class="${mode=='dark' ? 'active' : ''}" title="${'dark.dark' & t}">
<i class="material-icons-outlined">dark_mode</i>
</button>
</div>
</template>
- dark-mode.ts
type DarkModeType = 'light'|'os'|'dark';
/**
Manages dark mode.
*/
export class DarkMode {
mode: DarkModeType;
constructor() {
this.mode = getDarkMode();
}
/**
Sets light/dark mode.
@params mode Any of DarkModeType. If mode is not right, 'light' is used.
*/
setDarkMode(mode: DarkModeType) {
this.mode = setDarkMode(mode);
}
}
/**
Gets light/dark mode.
*/
export function getDarkMode(): DarkModeType {
return localStorage.darkMode || 'light';
}
/**
Sets light/dark mode.
@params mode Any of DarkModeType. If mode is not right, 'light' is used.
@returns A canonical mode.
*/
export function setDarkMode(mode: DarkModeType): DarkModeType {
if (!['light', 'os', 'dark'].includes(mode)) mode = 'light';
if (mode === 'dark' || mode === 'light') {
document.documentElement.setAttribute('theme', mode);
} else if (mode === 'os') {
document.documentElement.removeAttribute('theme');
}
localStorage.darkMode = mode;
return mode;
}
/**
Restores light/dark mode from localStorage.
*/
export function restoreDarkMode() {
setDarkMode(getDarkMode());
}
/* for explicit light mode */
.shaded {
background-color: rgba(128, 128, 128, 0.15) !important;
}
/* for explicit dark mode */
html[theme="dark"] .shaded {
background-color: rgba(128, 128, 128, 0.35) !important;
}
/* for OS dark mode and not explicit light mode */
@media (prefers-color-scheme: dark) {
html:not(html[theme="light"]) .shaded,
background-color: rgba(128, 128, 128, 0.35) !important;
}
}