Lumeo

Theme Overrides

Lumeo's theme system is a layered set of CSS custom properties. The library ships seven built-in themes (Zinc, Blue, Green, Rose, Violet, Amber, Teal) in both light and dark mode. You override the theme at three levels: globally with CSS variables, per-mode with dark-mode swaps, and per-component with the Class parameter. Every Lumeo component reads colors from variables — there are no hard-coded hex values anywhere in the library — so a variable change cascades to every component on the page.

The variable system

Variables are declared on :root for light mode and re-declared on .dark for dark mode. They come in foreground / background pairs — every surface variable has a matching -foreground for readable text on top.

Variable Used by
--color-background / --color-foreground Page surface and primary text.
--color-card / --color-card-foreground Card, Sheet, Popover, Dropdown, Dialog body.
--color-primary / --color-primary-foreground Primary button, active links, accent fills.
--color-secondary / --color-secondary-foreground Secondary button, subtle chips.
--color-muted / --color-muted-foreground Muted surfaces (skeletons, code blocks) and secondary text.
--color-accent / --color-accent-foreground Hover backgrounds for menu items, navigation links.
--color-destructive / --color-destructive-foreground Destructive buttons, invalid form rings, error alerts.
--color-border Default border for inputs, cards, dividers.
--color-input Form input borders.
--color-ring Focus-visible ring color.
--radius Base border-radius. Components derive --radius-sm / --radius-xl from it.

Global override

To re-skin the entire app, override the variables in your own stylesheet loaded after lumeo.css. Use OKLCH for colors — the built-in themes do — so contrast stays predictable when you tweak lightness.

/* my-app.css — loaded after lumeo.css */
:root {
    --color-primary: oklch(0.55 0.22 250);
    --color-primary-foreground: oklch(0.98 0 0);
    --color-ring: oklch(0.55 0.22 250);
    --radius: 0.5rem;
}

Dark mode swaps

Lumeo does not use Tailwind's dark: prefix anywhere internally. Dark mode flips the same variables under the .dark class on <html>. Your overrides must declare both blocks or dark mode will fall back to the library defaults for anything you didn't redeclare.

:root {
    --color-background: oklch(1 0 0);
    --color-foreground: oklch(0.15 0 0);
    --color-primary: oklch(0.55 0.22 250);
    --color-primary-foreground: oklch(0.98 0 0);
}

.dark {
    --color-background: oklch(0.15 0 0);
    --color-foreground: oklch(0.98 0 0);
    --color-primary: oklch(0.65 0.18 250);
    --color-primary-foreground: oklch(0.15 0 0);
}

The .dark class is toggled by the ThemeService. Don't toggle it yourself unless you also persist the choice.

Scoped override

Variables cascade, so you can re-declare them on any ancestor element to re-skin only the subtree below it. Useful for an embedded widget that should look "branded" while sitting inside a neutral app shell.

<div class="embedded-widget" style="--color-primary: oklch(0.6 0.2 30); --color-ring: oklch(0.6 0.2 30);">
    <Card>
        <Stack Gap="3" Class="p-4">
            <Heading Level="3">Promo</Heading>
            <Button>Get started</Button>
        </Stack>
    </Card>
</div>

Per-component class overrides

Every Lumeo component accepts Class, appended to the component's base classes via $"{BaseClasses} {Class}".Trim(). Use it to add utilities (margins, hover states, sizing). For colors, prefer pointing at theme variables — bg-primary, text-foreground — so the override still respects light and dark mode.

<!-- good: theme-aware utilities -->
<Button Class="bg-success text-success-foreground hover:bg-success/90">Mark complete</Button>

<!-- good: spacing / sizing -->
<Card Class="max-w-md mx-auto shadow-xl">...</Card>

<!-- avoid: raw hex breaks in dark mode -->
<Button Class="bg-[#1e40af] text-white">Don't do this</Button>

Avoid raw hex / rgb in Class. They look right in light mode and break in dark mode because they don't participate in the variable swap.

Picking the right layer

Goal Layer
Brand color across the whole app Global override of --color-primary + dark variant.
Different look for an embedded marketing widget Scoped override on the widget's wrapper.
One specific button needs a custom hue Per-component Class with theme utilities.
Switch users between built-in themes ThemeService + ThemeSwitcher.
Round all corners more Global override of --radius.

Demo: scoped theme

Wrap any subtree in a div that re-declares the variables. The card and button below pick up a violet accent without affecting anything else on this page.

Scoped accent

This card lives inside an override block.

See also