Dark mode
How it works
defineConfig wires TailwindPlugin, which generates two CSS layers. The static @theme layer is consumed by Tailwind at build time. The dynamic @layer theme layer scopes variables that can be refreshed at runtime on the web by React’s ThemeProvider:
/* Static — Tailwind build-time values */
@theme {
--color-primary: #6750a4;
--color-surface: #fffbfe;
}
/* Dynamic — scoped to .dynamic; ThemeProvider can refresh it on the web */
@layer theme {
.dynamic {
--color-primary: #6750a4; /* light */
--color-surface: #fffbfe;
}
}
@layer theme {
/* Generated for darkMode: 'class', darkSelector: '.dark' */
.dark .dynamic,
.dark.dynamic {
--color-primary: #d0bcff; /* dark */
--color-surface: #1c1b1f;
}
}Every element inside .dynamic inherits whichever set of variables is active. For a static theme, switching dark mode is only a CSS class or media-query change; no JavaScript theme recalculation is required.
Configuration
darkMode: 'class' (default)
Dark mode activates when a CSS class is present on an ancestor element.
// theme.config.ts
import { defineConfig } from '@udixio/tailwind';
export default defineConfig({
sourceColor: '#6750A4',
darkMode: 'class',
darkSelector: '.dark', // default value
});Add the class to <html> or <body>:
<html class="dark"></html>
<!-- dark mode on -->
<html></html>
<!-- light mode -->darkMode: 'media'
Dark mode follows the system preference automatically — no class management needed.
// theme.config.ts
import { defineConfig } from '@udixio/tailwind';
export default defineConfig({
sourceColor: '#6750A4',
darkMode: 'media',
});/* Generated */
@media (prefers-color-scheme: dark) {
.dynamic {
--color-primary: #d0bcff;
}
}Dynamic theme updates on the web (React)
The generated CSS is enough for a fixed light/dark theme. In a React app that also supports runtime changes to sourceColor, variant, palettes or other configuration, mount ThemeProvider with the shared config. It injects the dynamic layer and regenerates it when the config prop changes:
import { useState, useEffect } from 'react';
import themeConfig from './theme.config';
import { ThemeProvider } from '@udixio/ui-react';
export function App() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
document.documentElement.classList.toggle('dark', isDark);
}, [isDark]);
return (
<>
<ThemeProvider config={themeConfig} />
<button onClick={() => setIsDark((d) => !d)}>Toggle dark mode</button>
</>
);
}
ThemeProvideritself does not manage the dark class — it only generates CSS for both modes. You control when dark mode is active by toggling the selector class.
Runtime API note
isDark is a runtime context value used by the headless loader() API and advanced callbacks. It is not a defineConfig option in the Tailwind contract: defineConfig generates both schemes, while darkMode and darkSelector control which one is active in the page.
Custom darkSelector
Any valid CSS selector works:
darkSelector: '[data-theme="dark"]';<html data-theme="dark"></html>The .dynamic selector
.dynamic is the scope boundary for live theme updates. Elements outside it still receive Tailwind theme values from the static @theme layer — but those don’t update when the user changes the source color interactively.
For a standard app, put .dynamic on <body>:
<body class="dynamic dark bg-surface text-on-surface"></body>For isolated theming in a specific component, apply it to that element only:
<div class="dynamic theme-ocean rounded-xl p-4">
<!-- only this section uses the ocean theme variables -->
</div>