【ITニュース解説】Why MUI Avoids Theme Flash on First Load (Dark/Light Mode)
2025年09月29日に「Dev.to」が公開したITニュース「Why MUI Avoids Theme Flash on First Load (Dark/Light Mode)」について初心者にもわかりやすく解説しています。
ITニュース概要
MUIは、React処理前に小さなスクリプトでユーザーのテーマ設定をHTMLに適用。CSS変数を使い、ページ初回表示時からダーク/ライトモードのちらつきなく正しいテーマを表示する。他のUIフレームワークがハイドレーション後に切り替えることで発生するちらつきを防ぐ。
ITニュース解説
Webサイトでダークモードを使っていると、ページを再読み込みした際に一瞬だけライトモードの画面が表示されて、その後にダークモードに切り替わる現象を目にすることがある。これを「テーマフラッシュ」と呼ぶ。この一瞬のちらつきは、ユーザー体験を損ねる原因となる。しかし、人気のあるUIフレームワークであるMUI(Material-UI)は、このテーマフラッシュを巧妙に回避している。なぜMUIはスムーズなテーマ切り替えを実現できるのか、その背後にある技術的な仕組みを解説する。
MUIがテーマフラッシュを回避できる理由は、大きく分けて三つの重要なアイデアに基づいている。
一つ目は、「プリハイドレーションでのカラースキーム初期化」である。Webサイトが複雑になると、サーバー側でHTMLを生成し、それをブラウザに送る「サーバーサイドレンダリング(SSR)」という技術が使われることがある。ブラウザはそのHTMLを一度表示し、その後、クライアント側のJavaScriptフレームワーク(Reactなど)が、そのHTMLにイベントハンドラや動的な振る舞いを「組み込む」ことで、インタラクティブなWebページとして機能させる。このプロセスを「ハイドレーション」と呼ぶ。MUIは、このハイドレーションが始まるよりもずっと前、HTMLファイルの読み込みが始まったばかりの段階で、小さなインラインスクリプト「getInitColorSchemeScript」をHTMLの <head> タグ内に挿入する。このスクリプトは、ブラウザがページを初めて描画する(ファーストペイント)よりも前に実行される。スクリプトの役割は、ユーザーが以前設定したテーマモード(例えばダークモード)をブラウザの localStorage から読み込んだり、あるいはOSの設定(システムがダークモードかライトモードか)をチェックしたりすることである。そして、その情報に基づいて、HTMLのルート要素である <html> タグに data-mui-color-scheme="dark" あるいは data-mui-color-scheme="light" という属性を設定する。これにより、ブラウザが実際にページを描画する時点で、すでに正しいテーマが指定された状態になっているため、不適切なテーマが一瞬表示されることがない。
二つ目は、「CSS変数を利用した両スキームの定義」である。MUIは CssVarsProvider というコンポーネントを使用することで、ライトモードとダークモード、両方のテーマに必要なスタイル(色やフォントなど)をCSS変数として定義する。CSS変数とは、CSS内で再利用可能な値を定義するための仕組みであり、例えば --primary-color: #007bff; のように値を名前で管理できる。MUIでは、背景色や文字色といったプロパティに対して、ライトモード用の値とダークモード用の値の両方をCSS変数として準備する。例えば、ライトモードの背景色を示す --mui-palette-background-default には白を、ダークモードのそれには黒を定義する。そして、ブラウザは <html> タグに設定された data-mui-color-scheme 属性の値に応じて、どちらのCSS変数のセットを適用するかを即座に判断する。これにより、ページのロード時に最適なテーマのスタイルが直接適用されることになる。
三つ目は、「SSR(サーバーサイドレンダリング)との互換性」である。MUIでは、サーバー側で生成されるHTMLに、ライトモードとダークモード両方のテーマで必要となるCSS変数がすべて含まれた状態でクライアントに送信される。そして、前述したクライアント側の初期化スクリプトが、ユーザーの好みやシステム設定に基づいて正しいカラースキームを瞬時に選択し、<html> タグに適用する。この仕組みのおかげで、サーバーから送られてきたHTMLがクライアントで初めて描画される際にも、すでにユーザーの好みに合ったテーマが適用されているため、テーマが切り替わる際のちらつきが一切発生しない。
これらのアイデアが組み合わさることで、MUIは初回ロード時にテーマフラッシュを起こさない、非常にスムーズなユーザー体験を提供している。
では、具体的に何が起きているのか、もう少し詳しく見ていこう。
まず、「CSS変数によるスタイルの定義」についてである。MUIは静的なCSSクラスの切り替えに依存するのではなく、CSS変数を用いる。CSSの :root セレクタ(HTML文書のルート要素、つまり <html> タグを指す)内で、ライトモード(デフォルト)のスタイルのCSS変数を定義する。例えば、--mui-palette-background-default に白(#fff)を、--mui-palette-text-primary に黒(#000)を設定する。次に、[data-mui-color-scheme='dark'] というセレクタを使って、<html> タグに data-mui-color-scheme="dark" 属性が付いている場合に、これらのCSS変数の値をダークモード用のものに上書きする。例えば、--mui-palette-background-default に黒(#000)を、--mui-palette-text-primary に白(#fff)を設定する。このように、ライトとダーク両方の定義がCSS内に存在し、data-mui-color-scheme 属性の値によってどちらが有効になるかが決まるのである。
次に、「初期化スクリプトの役割」である。これは、ブラウザがJavaScriptコードを解析・実行するよりも早く動作するように、HTMLの <head> タグの先頭付近に直接埋め込まれた小さなJavaScriptコードである。このスクリプトは、まず localStorage からユーザーが保存したテーマモード(例えば mui-mode というキーで保存されている)を読み込む。もし保存されたモードが「システム」設定であれば、window.matchMedia('(prefers-color-scheme: dark)') を使って、OSのシステム設定がダークモードかどうかをチェックする。そして、最終的に決定されたモード(「dark」か「light」)を document.documentElement.setAttribute('data-mui-color-scheme', finalMode); という命令で <html> タグにセットする。この処理がブラウザが画面を描画する直前に行われるため、ユーザーは最初から正しいテーマの画面を見ることになる。
さらに、「SSRとハイドレーションの安全な連携」も重要である。MUIのこのアプローチは、サーバーとクライアントの間でDOM(Document Object Model、Webページの構造)のミスマッチが発生しないという利点がある。サーバーで生成されるHTMLの構造(タグやコンテンツ)は、クライアント側でReactがハイドレーションを行う際にも同じである。異なるのは、CSS変数の値が、<html> タグに付与された data-mui-color-scheme 属性によって決定されるという点だけである。Reactのハイドレーションは、DOMの構造がサーバーとクライアントで一致していることを前提とするため、CSS変数の変更だけではミスマッチとは認識されない。このため、Reactはスムーズにハイドレーションを完了させ、結果としてちらつきは発生しない。
他の多くのUIフレームワーク、例えばAnt Designの場合、テーマフラッシュが発生する典型的な理由は、MUIとは異なるアプローチをとっているためである。Ant Designでは、通常、Reactのコンポーネントがクライアント側で完全に描画され、ハイドレーションが完了した後(例えば useEffect フックの中で)、JavaScriptを使って document.body.classList.add('dark'); のように <body> タグにダークモード用のCSSクラスを追加する。この場合、サーバーから送られてくるHTMLは常にデフォルトの(ライト)テーマで生成されている。したがって、ブラウザが最初の描画を行う時点ではライトモードの画面が表示される。その後、クライアントサイドのJavaScriptが実行されてダークモードのクラスが <body> に適用されると、二度目の描画が行われてテーマがダークモードに切り替わる。この「ライトモードでの最初の描画」と「ダークモードへの切り替え」の間に時間差が生じるため、ユーザーには一瞬のライトモードの表示、つまりテーマフラッシュが視覚的に認識されてしまうのである。
Next.jsのようなフレームワークのApp RouterでMUIのテーマシステムをセットアップする際は、app/layout.tsx のようなルートレイアウトファイルで、HTMLの <head> タグ内に {getInitColorSchemeScript()} を配置する。これは、Reactが実行されるよりも前に、正しいテーマ設定を <html> タグに適用し、テーマフラッシュを防ぐための必須のステップである。そして、<body> タグの中では、CssVarsProvider コンポーネントでアプリケーション全体を囲むことで、ライトモードとダークモードに対応したCSS変数システムを有効にする。CssVarsProvider に defaultMode="system" や modeStorageKey="mui-mode" のようなプロパティを設定することで、デフォルトのテーマモードや、テーマ設定を保存するための localStorage キーを指定できる。
まとめると、MUIがテーマフラッシュを回避できるのは、単にCSS変数を活用しているだけでなく、そのCSS変数を制御する仕組みを、Webページの初回描画より前に実行される小さな初期化スクリプトと組み合わせているからである。このアプローチにより、ユーザーは常に自分の設定したテーマのページを最初から見ることができ、視覚的なストレスを感じることなく、MUIによるダークモードをネイティブで瞬時に利用できるのである。