Theme Engine
Runtime theming system: CSS variable override, scoped themes, localStorage persistence, and cascade strategy.
Runtime Token Override
CSS custom properties cascade like any other inherited value. Override
any token at runtime by writing directly to element.style.setProperty()
— no build step, no class toggling. Changes propagate instantly to every
component inside the scope.
Component Preview Live
All colors, radius, and surface tokens below are injected as CSS custom properties onto this container. Any nested component inherits them automatically.
Second Card
Same tokens, different content — demonstrating consistent cascade across sibling elements inside the same scope.
Active token values
Scoped Themes
Apply a data-theme attribute to any element to scope a DaisyUI
theme to just that subtree. Both panes below exist in the same DOM — no
separate stylesheet, no JavaScript toggle.
data-theme="light"
Card component
Uses DaisyUI's base-100, base-content
semantic tokens — resolves to the light palette here.
data-theme="dark"
Card component
Same markup, same classes — resolves to the dark palette
because the ancestor declares data-theme="dark".
Mix custom themes the same way — data-theme="brand-warm" on a section,
data-theme="high-contrast" on a modal. DaisyUI resolves each
element to the nearest ancestor theme scope.
localStorage Persistence
Persist the active theme across sessions using localStorage.
The pattern below applies the theme before the first paint to avoid flash.
Active theme:
Saved to localStorage — persists on reload.
<!-- In <head>, before any stylesheet -->
<script>
(function() {
var t = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', t);
})();
</script>
The IIFE runs synchronously before DaisyUI's stylesheet is parsed — no flash of incorrect theme on first paint.
Cascade Strategy
When multiple scopes define the same token, the nearest ancestor wins. Understand the cascade order to reason about where a resolved value comes from.
Inline style has the highest specificity for custom properties.
Use it for one-off runtime overrides. Prefer data-theme scopes
for reusable, named contexts — they stay in CSS and don't mix styling
concerns into JavaScript.
SSR-Safe Hydration
Server-rendered pages can echo the correct data-theme from a
session/cookie so the HTML arrives already themed — Alpine.js then syncs
its own state from the DOM on mount, not the other way around.
<?php $theme = $_COOKIE['theme'] ?? 'light'; ?>
<html data-theme="<?= htmlspecialchars($theme) ?>">
// Alpine reads from DOM, never overwrites on init
x-data="{ theme: document.documentElement.dataset.theme }"
x-init="$watch('theme', t => {
document.documentElement.dataset.theme = t;
document.cookie = 'theme=' + t + ';path=/';
})"
Server echoes theme
PHP reads cookie/session and sets data-theme on before any JS runs.
CSS resolves instantly
DaisyUI reads data-theme during stylesheet parsing — zero JS required for initial paint.
Alpine syncs on mount
x-init reads the existing DOM value; watcher fires only when the user changes theme.