ダークモード

Tailwind v4 パッケージは、セマンティックトークンシステムを通じてダークモードをサポートしています。すべての色は CSS 変数を参照しているため、ダークモードへの切り替えは、これらの変数を再定義するだけで完了します。

仕組み

コンポーネントは bg-primarytext-foregroundborder-border のようなセマンティックユーティリティクラスを使用します。これらは、プロジェクトの CSS で定義する CSS 変数 (--color-primary--color-foreground など) に解決されます。ダークモードは、ダークセレクタの下でこれらの変数に代替値を提供することで機能します。

セットアップ

data-theme 属性を使用する

data-theme セレクタを使用して、ライトとダークのトークン値を定義します:

@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
--color-popover: oklch(1 0 0);
--color-popover-foreground: oklch(0.145 0.004 285.823);
--color-primary: oklch(0.205 0.006 285.885);
--color-primary-foreground: oklch(0.985 0.001 285.823);
--color-muted-foreground: oklch(0.556 0.01 285.823);
--color-accent: oklch(0.96 0.003 285.823);
--color-accent-foreground: oklch(0.205 0.006 285.885);
--color-destructive: oklch(0.577 0.245 27.325);
--color-border: oklch(0.922 0.004 285.823);
--color-input: oklch(0.922 0.004 285.823);
--color-ring: oklch(0.87 0.006 285.823);
}
[data-theme="dark"] {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
--color-popover: oklch(0.145 0.004 285.823);
--color-popover-foreground: oklch(0.985 0.001 285.823);
--color-primary: oklch(0.985 0.001 285.823);
--color-primary-foreground: oklch(0.205 0.006 285.885);
--color-muted-foreground: oklch(0.556 0.01 285.823);
--color-accent: oklch(0.269 0.006 285.885);
--color-accent-foreground: oklch(0.985 0.001 285.823);
--color-destructive: oklch(0.577 0.245 27.325);
--color-border: oklch(0.269 0.006 285.885);
--color-input: oklch(0.269 0.006 285.885);
--color-ring: oklch(0.369 0.006 285.885);
}

属性を設定してダークモードを切り替えます:

function App() {
const [dark, setDark] = useState(false);
return (
<div data-theme={dark ? "dark" : "light"}>
<button onClick={() => setDark(!dark)}>Toggle Theme</button>
<DatePicker value={value} onChange={setValue} />
</div>
);
}

.dark クラスを使用する

フレームワークが .dark クラスを使用している場合 (例: Next.js と next-themes)、そのクラスの下にトークンを定義します:

@theme {
/* Light tokens */
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
/* ... */
}
.dark {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
/* ... */
}

システム設定を使用する

@media (prefers-color-scheme: dark) を使用して OS の設定に従います:

@theme {
/* Light tokens */
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
/* ... */
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
/* ... */
}
}

next-themes との統合

Next.js と next-themes を使用する場合の一般的なパターンです:

app/layout.tsx
import { ThemeProvider } from "next-themes";
export default function RootLayout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
</body>
</html>
);
}
globals.css
@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0.004 285.823);
--color-primary: oklch(0.205 0.006 285.885);
--color-primary-foreground: oklch(0.985 0.001 285.823);
/* ... all light tokens */
}
.dark {
--color-background: oklch(0.145 0.004 285.823);
--color-foreground: oklch(0.985 0.001 285.823);
--color-primary: oklch(0.985 0.001 285.823);
--color-primary-foreground: oklch(0.205 0.006 285.885);
/* ... all dark tokens */
}

テーマが変更されると、ピッカーは自動的に正しいトークン値を取得します。

ダークモードでのハイライトドット

ハイライトドットは、両方のモードで良好な視認性を確保するために after:bg-amber-500 dark:after:bg-amber-400 (トークンではなく具体的な Tailwind カラー) を使用します。これはコンポーネント内で唯一の非セマンティックカラーです。変更する必要がある場合は、カスタムの Day レンダリングで Compound Component API を使用します:

<DatePicker.Day date={date}>
{(props) => (
<button /* ... */>
{props.day}
{props.isHighlighted && (
<span className="absolute bottom-0.5 w-1 h-1 rounded-full bg-emerald-500 dark:bg-emerald-400" />
)}
</button>
)}
</DatePicker.Day>

特定のモードを強制する

システム設定に関係なくライトモードまたはダークモードを強制するには、テーマを明示的に設定します:

{
/* Always light */
}
<div data-theme="light">
<DatePicker value={value} onChange={setValue} />
</div>;
{
/* Always dark */
}
<div data-theme="dark">
<DatePicker value={value} onChange={setValue} />
</div>;