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 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
The generated CSS is enough for a fixed light/dark theme. In an app that also supports runtime changes to sourceColor, variant, palettes or other configuration, mount ThemeProvider with the shared config. The provider from @udixio/ui-react, @udixio/ui-angular or @udixio/ui-svelte injects the dynamic layer and regenerates it when the config 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>