Skip to content

ダークモード

現状の使い方(実装済み・JSでの切り替え)

Section titled “現状の使い方(実装済み・JSでの切り替え)”
import { ThemeProvider, defaultTheme, darkTheme } from "@hirasaki1985/web-ui";
const App = () => (
<ThemeProvider themes={{ light: defaultTheme, dark: darkTheme }} colorScheme="dark">
{/* ... */}
</ThemeProvider>
);

ThemeProviderthemes(ライト/ダーク2つのテーマ)と colorScheme"light" | "dark")を渡すと、配下のすべてのコンポーネントが colorScheme に応じたテーマで描画されます。colorScheme はReactの状態として自分で管理してください(OS設定の検知やユーザー切り替えUIはライブラリの外側の責務です)。

darkThemedefaultTheme と同じPrimitiveパレットを共有し、Semantic/Componentだけをダーク向けに再導出したものです(buildDarkSemanticColors / buildDarkComponentColors)。ニュートラル(gray)を反転しつつ、本文テキストの対象ロールは両モードともWCAG AA(4.5:1)を満たすステップに調整しています。アイコン/ボーダーは用途によって基準未達の値も含むため、実際の値と比率は を参照してください。

この仕組みは現在styled-componentsのReact Context経由でのみ動作します。CSSカスタムプロパティやHTML属性は使っていません。

計画中:CSS属性でのダーク切り替え

Section titled “計画中:CSS属性でのダーク切り替え”

CSSカスタムプロパティによる配布を追加する計画があり、そこでは属性ベースの切り替えを採用します。

/* 配布CSS(計画中のイメージ) */
:root { --wui-text-primary: var(--wui-color-gray-900); }
[data-wui-color-scheme="light"] { --wui-text-primary: var(--wui-color-gray-900); }
@media (prefers-color-scheme: dark) {
:root:not([data-wui-color-scheme="light"]) {
--wui-text-primary: var(--wui-color-gray-50);
}
}
[data-wui-color-scheme="dark"] { --wui-text-primary: var(--wui-color-gray-50); }
やりたいこと 書き方
OS設定に追従 属性を付けない
明示的にダーク <html data-wui-color-scheme="dark">
一部だけダーク <aside data-wui-color-scheme="dark">

優先順位: ① 要素自身の明示属性 → ② 祖先の明示属性 → ③ OSの prefers-color-scheme → ④ 既定のlight。CSSの詳細度と継承によってこの順序になります。

属性方式を選んだ理由: :root にJSで色を注入する方式だと、SSRの初回HTMLが必ずライトになり、ハイドレーション後に色がチラつきます。属性方式なら配布CSSに両方の定義があるため、サーバーが返すHTMLの時点で正しい色になります。

CSS属性を追加しても、ThemeProviderthemes / colorScheme というAPI自体は維持される計画です(useTheme() を使うコンポーネントが引き続き動くようにするため)。実装後は ThemeProvider が次の2つを同時に行います。

  1. colorScheme に応じて data-wui-color-scheme 属性をDOMへ出す → CSS側が切り替わる
  2. 同じ colorScheme の値でReact Contextにテーマを流す → useTheme() の戻り値と一致する

出所を ThemeProvider 1箇所にすることで、「CSSはダークなのに useTheme() がライトの実値を返す」というズレが起きない設計にする計画です。ThemeProvider を経由せず <html> へ直接属性を付けた場合、useTheme() は追従しません(CSSのみが対象になります)。

  • OS設定への追従だけでよい場合: 属性を出す処理はハイドレーション後に実行される想定のため、属性なしの初回HTMLでも prefers-color-scheme のメディアクエリでOS設定通りの色になります。何もする必要はありません。
  • ユーザーが画面で選んだ設定を使いたい場合: 保存先(cookie / DB / URL等)はアプリ側の都合のため、ライブラリはその保存・読み出しを提供しません。利用側がcookie等から読んで属性をSSR時に出力します。
// 利用側の実装イメージ(Next.js App Router の例)
export default async function Layout({ children }: { children: React.ReactNode }) {
const scheme = (await cookies()).get("color-scheme")?.value;
return <html data-wui-color-scheme={scheme}>{children}</html>;
}

localStorage を読むインライン<script>を注入する方式(一部のライブラリで見られる方式)は、Content-Security-Policyでinline scriptを禁じている環境で動かないため採用しない方針です。