MDX Notes
A React hook that handles theme state, CSS variables, and dark/light mode — with system preference detection and persistence baked in.
import { Callout } from '../components/Callout' import { PropsTable } from '../components/PropsTable' import { CodeBlock } from '../components/CodeBlock' import { TabGroup, Tab } from '../components/Tabs' import { VersionBadge } from '../components/VersionBadge' import { BreadcrumbNav } from '../components/BreadcrumbNav' import { ApiSignature } from '../components/ApiSignature' import { SeeAlso } from '../components/SeeAlso' import { Playground } from '../components/Playground' import { TypeDefinition } from '../components/TypeDefinition' import { ChangelogEntry } from '../components/ChangelogEntry' import { LinkCard, LinkCardGrid } from '../components/LinkCard'
<BreadcrumbNav items={frontmatter.breadcrumb} />
<VersionBadge version="2.4.0" since="1.0.0" stability="stable" />
This hook gives you full control over theme state — dark mode, light mode, system preference detection, custom themes, CSS variables, and persistent user choices. One hook, everything you need.
npm install @acme/themeAPI Signature
function useTheme(options?: UseThemeOptions): UseThemeReturnParameters
<PropsTable title="UseThemeOptions" props={[ { name: "defaultTheme", type: "'light' | 'dark' | 'system' | string", default: "'system'", required: false, description: "The initial theme to use before user preference is loaded. Falls back to the provider's defaultTheme if not specified." }, { name: "storageKey", type: "string", default: "'acme-theme'", required: false, description: "The localStorage key used to persist the user's theme preference across sessions." }, { name: "enableSystem", type: "boolean", default: "true", required: false, description: "Whether to detect and respond to the operating system's color scheme preference via prefers-color-scheme." }, { name: "enableTransitions", type: "boolean", default: "true", required: false, description: "Whether to apply CSS transitions when switching themes. Set to false for instant theme changes." }, { name: "themes", type: "string[]", default: "['light', 'dark']", required: false, description: "Array of available theme names. Used for validation and by the themes property in the return value." }, { name: "attribute", type: "'class' | 'data-theme' | data-${string}", default: "'class'", required: false, description: "The HTML attribute used to apply the theme to the document element." }, { name: "onThemeChange", type: "(theme: string) => void", default: "undefined", required: false, description: "Callback fired when the theme changes. Useful for analytics or syncing with external systems." }, ]} />
Return Value
Here's what you get back from the hook:
Usage Examples
Basic Theme Toggle
A three-button toggle for light, dark, and system:
Custom Theme with CSS Variables
Want more than just light and dark? Add custom themes like "ocean", "forest", or "sunset":
```tsx title="CustomTheme.tsx" import { useTheme } from '@acme/theme';
export function CustomThemeDemo() { const { resolvedTheme, setTheme, themes } = useTheme({ themes: ['light', 'dark', 'ocean', 'forest', 'sunset'], });
return ( <div className="theme-selector"> {themes.map((t) => ( <button key={t} onClick={() => setTheme(t)} className={swatch swatch-${t}} aria-pressed={resolvedTheme === t} > {t} </button> ))} </div> ); } `` ``css title="themes.css" [data-theme="ocean"] { --color-primary: #0077b6; --color-secondary: #00b4d8; --color-background: #caf0f8; --color-surface: #ffffff; --color-text: #023e8a; }
[data-theme="forest"] { --color-primary: #2d6a4f; --color-secondary: #52b788; --color-background: #d8f3dc; --color-surface: #ffffff; --color-text: #1b4332; }
[data-theme="sunset"] { --color-primary: #e85d04; --color-secondary: #f48c06; --color-background: #fff3e0; --color-surface: #ffffff; --color-text: #6a040f; } `` ``typescript title="theme.config.ts" import { defineThemeConfig } from '@acme/theme';
export const themeConfig = defineThemeConfig({ defaultTheme: 'system', themes: ['light', 'dark', 'ocean', 'forest', 'sunset'], attribute: 'data-theme', storageKey: 'my-app-theme', enableTransitions: true, transitionDuration: 200, cssVariables: { prefix: '--color', mapping: { primary: 'primary', secondary: 'secondary', bg: 'background', surface: 'surface', text: 'text', }, }, }); ```
Server-Side Rendering (SSR)
When using SSR (Next.js, Remix, etc.), the theme can't be determined on the server. Use the isHydrated flag to prevent hydration mismatches, or inject a blocking script using the ThemeScript component.
The trick here is ThemeScript — it runs before React hydrates, so users never see a flash of the wrong theme:
import { ThemeProvider, ThemeScript } from '@acme/theme';
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
{/* Prevents flash of wrong theme */}
<ThemeScript
defaultTheme="system"
storageKey="acme-theme"
attribute="class"
/>
</head>
<body>
<ThemeProvider
defaultTheme="system"
enableSystem={true}
attribute="class"
>
{children}
</ThemeProvider>
</body>
</html>
);
}With React Server Components
Since this hook needs the browser, mark your component with 'use client':
'use client'; // This hook requires client-side JavaScript
import { useTheme } from '@acme/theme';
export function ThemeButton() {
const { toggleTheme, resolvedTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Switch to {resolvedTheme === 'dark' ? 'light' : 'dark'} mode
</button>
);
}Advanced Patterns
Theme-Aware Components
Swap assets based on the current theme. Logos are the classic example:
import { useTheme } from '@acme/theme';
export function Logo() {
const { resolvedTheme } = useTheme();
return (
<img
src={resolvedTheme === 'dark' ? '/logo-light.svg' : '/logo-dark.svg'}
alt="Company Logo"
width={120}
height={40}
/>
);
}Syncing with External Systems
Fire analytics events or update the browser's theme-color meta tag when the theme changes:
TypeScript Support
Full TypeScript definitions ship with the package. If you add custom themes, extend the types:
import '@acme/theme';
declare module '@acme/theme' {
interface ThemeMap {
ocean: true;
forest: true;
sunset: true;
}
}Troubleshooting
This error occurs when useTheme is called in a component that isn't wrapped by ThemeProvider. Ensure your provider is at the root of your component tree. In Next.js, place it in your root layout file.
If you see a flash when the page loads, add the ThemeScript component to your <head>. This injects a small blocking script that sets the theme attribute before React hydrates, eliminating the flash entirely.
| Issue | Cause | Solution |
|---|---|---|
| Flash on page load | Theme determined client-side | Add ThemeScript to <head> |
| Hydration mismatch | Server/client theme differs | Use suppressHydrationWarning |
| Theme not persisting | Storage key conflict | Check storageKey is unique |
| System theme not updating | enableSystem disabled | Set enableSystem: true |
| Transitions not working | CSS transitions missing | Add transition to :root |
Changelog
- Added
forceThemeto return value for preview/demo modes - Added
enableTransitionsoption for smoother theme switches - Fixed hydration issues with React 18 streaming SSR
- Added
onThemeChangecallback option - Improved system theme detection reliability
- Added
isHydratedflag to prevent SSR mismatches
- BREAKING: Renamed
colorModetothemein return value - BREAKING:
ThemeProvidernow requires explicitattributeprop - Added support for unlimited custom themes
- Added
ThemeScriptcomponent for SSR - Dropped support for React 16
See Also
<LinkCard title="ThemeProvider" description="Configure theme settings for your entire application" href="/docs/api/components/theme-provider" icon="component" /> <LinkCard title="ThemeScript" description="Prevent flash of incorrect theme during SSR" href="/docs/api/components/theme-script" icon="code" /> <LinkCard title="CSS Variables Guide" description="Learn how to use CSS variables with the theme system" href="/docs/guides/css-variables" icon="guide" /> <LinkCard title="Dark Mode Tutorial" description="Step-by-step guide to implementing dark mode" href="/docs/tutorials/dark-mode" icon="tutorial" />
export const metadata = { package: "@acme/theme", version: "2.4.0", peerDependencies: { react: ">=18.0.0" }, bundleSize: "1.2kb gzipped", };
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for useTheme Hook.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this MDX Tutorial topic.
Search Terms
mdx-tutorial, mdx tutorial, mdx, tutorial, examples, documentation, page, example
Related MDX Tutorial Topics