Section Themes
A theme does not have to own the whole page. Any element can carry one and everything inside it follows, so a single long page can change character section by section.
Naming a theme
theme('dark') puts data-theme="dark" on the element. Each band below carries
a different theme, on the same page, at the same time.
The default theme, restored explicitly, even inside a dark page.
Surfaces, text, borders and every intent follow the attribute.
An override object, resolved at runtime, with no config entry needed.
import { theme } from '@hyzer-labs/ui';
<section {@attach theme('dark')}>
<!-- everything in here is dark, whatever the page around it is -->
</section>It really is just an attribute
<!-- identical result, no import: this is all the attachment writes -->
<section data-theme="dark">…</section>Defining themes
Named themes come from the themes map in your config. Each entry becomes one [data-theme="…"] block in the generated sheet, graded for contrast the same way the
built-in dark theme is.
// hyzer.config.ts
import { defineConfig } from '@hyzer-labs/ui/config';
export default defineConfig({
themes: {
dark: { palette: { primary: '#60a5fa' } }, // the built-in, yours to extend
ocean: { palette: { primary: '#0ea5e9' } }, // data-theme="ocean"
'ocean-dark': { color: { surface: '#0b1120' } }
}
});dark is an entry like any other. It merges over what the library already authors
rather than replacing it. light is reserved as a name: the default theme is the :root block you author through tokens, and [data-theme='light'] re-asserts that default for a reader whose system prefers dark.
One attribute holds one value, so themes are mutually exclusive. There is no
“ocean, but dark” unless you define it. The 'ocean-dark' entry above is that definition.
Themes without a config entry
Pass an override object instead of a name and it is resolved in the browser, then written to the element as inline custom properties. Reach for this when the theme comes from data, like a per-tenant accent or a color the user picked, where a build-time entry is not an option.
import { theme } from '@hyzer-labs/ui';
const warm = {
palette: { primary: '#b45309', gray: '#78716c' },
color: { surface: '#fffbeb', text: '#1c1917' }
};
<section {@attach theme(warm)}>…</section>Two trade-offs worth knowing
When to use a class instead
Scoping a generated sheet under a class is the other way to do this, and for one case it is
the better way. A class composes with data-theme, so a themed region still has a
light and a dark form. A themes entry cannot, because it occupies the same attribute
dark does.
// A class scope is the other mechanism, and the more expressive one:
// it COMPOSES with dark mode, which a themes entry cannot.
generateCss(resolveConfig(oceanConfig), { selector: '.theme-ocean' });
<div class="theme-ocean"> <!-- ocean, in whichever mode is active -->
<section data-theme="dark">…</section>
</div>Reach for a themes entry when a section should look one specific way. Reach for a
class when a whole region needs its own palette and still has to answer to the page's light/dark
state.