Skip to content

Theming

GolemUI ships with a default theme, a bundled alternative (Clay), opt-in dark mode, and a clear pattern for creating and scoping your own themes.

The default theme is included in @golemui/gui-components/index.css. Import it once in your project’s CSS or SCSS entry point:

@import '@golemui/gui-components/index.css';

That’s all that’s needed — every widget renders with the default look and the design tokens documented in Customization are available for overrides. Dark mode is opt-in; the section below covers it.

Dark mode is opt-in, picked via a data-theme attribute on any ancestor element (most often <html> or <body>). The library never silently flips your page based on system preference — a component-library CSS import that quietly restyles a host page in dark systems is a surprise nobody wants, so you say what you want explicitly.

data-theme valueEffect
(no attribute)Default light tokens everywhere.
"auto"Default theme, system-follow. Reads prefers-color-scheme — light by day, dark by night.
"dark"Default theme, forced dark regardless of system preference.
"clay"The bundled Clay theme, always light.
"clay-auto"Clay theme, system-follow.
"clay-dark"Clay theme, forced dark.
<!-- Always light (default) -->
<html lang="en">
<!-- Follow the user's system preference -->
<html lang="en" data-theme="auto">
<!-- Force dark for everyone, ignore system preference -->
<html lang="en" data-theme="dark"></html>
</html>
</html>

The attribute can sit on <html>, <body>, or any container — the cascade decides what each form picks up. See Scoped themes below for mixing modes on the same page.

All colors, backgrounds, borders, and status indicators adjust for dark backgrounds. You can see the exact light and dark values for every token in the Customization reference.

Clay is a warm, terracotta-inspired theme that replaces the neutral slate palette with earthy tones and uses terracotta as the primary accent color. It mirrors the base theme’s opt-in light/dark/auto split.

Loading the Clay theme:

@import '@golemui/gui-components/index.css';
@import '@golemui/gui-components/themes/clay.css';

Applying the theme:

<!-- Clay, always light -->
<div data-theme="clay">
<gui-form ...></gui-form>
</div>
<!-- Clay, follow the user's system preference -->
<div data-theme="clay-auto">
<gui-form ...></gui-form>
</div>
<!-- Clay, forced dark -->
<div data-theme="clay-dark">
<gui-form ...></gui-form>
</div>

Follow the same light/dark/auto split as the base theme and Clay — three data-theme values for one theme, so consumers can pick exactly which mode they want.

  1. Pick a base name (e.g. sunset) and reserve three data-theme values: sunset (always light), sunset-dark (forced dark), sunset-auto (system-follow):

    [data-theme='sunset'],
    [data-theme='sunset-dark'],
    [data-theme='sunset-auto'] {
    /* light tokens shared by all three */
    }
  2. Override the tokens you need (light variant)
    Section titled “Override the tokens you need (light variant)”

    Define the light palette on the selector list above. All three variants start here; the dark variant later only overrides what actually changes.

    [data-theme='sunset'],
    [data-theme='sunset-dark'],
    [data-theme='sunset-auto'] {
    /* Override primitives for a warm palette */
    --gui-color-primary-400: #fb923c;
    --gui-color-primary-500: #f97316;
    --gui-color-primary-600: #ea580c;
    --gui-color-primary-700: #c2410c;
    --gui-color-primary-800: #9a3412;
    /* Override semantic tokens */
    --gui-intent-primary: var(--gui-color-warning-600);
    --gui-intent-primary-hover: var(--gui-color-warning-700);
    --gui-intent-primary-active: var(--gui-color-warning-800);
    --gui-border-focus: var(--gui-color-warning-500);
    /* Adjust backgrounds */
    --gui-bg-default: #fff7ed;
    --gui-bg-surface: #ffffff;
    /* Adjust borders */
    --gui-border-default: var(--gui-color-warning-300);
    --gui-shadow-focus:
    0 0 0 2px var(--gui-bg-default),
    0 0 0 4px color-mix(in srgb, var(--gui-border-focus) 50%, transparent);
    }
  3. Apply dark token overrides via two selectors: [data-theme='sunset-dark'] for the forced-dark variant, and [data-theme='sunset-auto'] inside a prefers-color-scheme: dark media query for the system-follow variant.

    [data-theme='sunset-dark'] {
    --gui-bg-default: #1c1210;
    --gui-bg-surface: #271a16;
    --gui-border-default: var(--gui-color-warning-600);
    --gui-intent-primary: var(--gui-color-warning-500);
    --gui-intent-primary-hover: var(--gui-color-warning-400);
    }
    @media (prefers-color-scheme: dark) {
    [data-theme='sunset-auto'] {
    --gui-bg-default: #1c1210;
    --gui-bg-surface: #271a16;
    --gui-border-default: var(--gui-color-warning-600);
    --gui-intent-primary: var(--gui-color-warning-500);
    --gui-intent-primary-hover: var(--gui-color-warning-400);
    }
    }

    If repeating the dark overrides feels noisy, factor them into a Sass @mixin (or a PostCSS custom property block) and include it in both selectors — that’s what the base theme and Clay do internally.

  4. Pick one of the three values when applying:

    <!-- Sunset, always light -->
    <div data-theme="sunset">
    <gui-form ...></gui-form>
    </div>
    <!-- Sunset, follow the user's system preference -->
    <div data-theme="sunset-auto">
    <gui-form ...></gui-form>
    </div>
    <!-- Sunset, forced dark -->
    <div data-theme="sunset-dark">
    <gui-form ...></gui-form>
    </div>

Because data-theme is a standard CSS attribute selector, themes can be scoped to a specific container rather than applied page-wide. The same page can host multiple themes side by side, and switching themes is just a DOM-attribute change.

Place each data-theme on a different container:

<div data-theme="clay">
<gui-form ...></gui-form>
</div>
<div data-theme="sunset">
<gui-form ...></gui-form>
</div>

Each form renders with its own theme. The CSS cascade handles everything naturally — no JavaScript needed.

A common pattern is a default page theme with one section opted into a different look — for example, a “preview” panel that always uses the dark theme:

<body>
<main>
<gui-form ...></gui-form>
<!-- default theme -->
</main>
<aside data-theme="clay">
<gui-form ...></gui-form>
<!-- clay theme inside this aside -->
</aside>
</body>

Because the theme is a DOM attribute, swapping it is a single line of JavaScript:

document.querySelector('[data-theme]').dataset.theme = 'sunset';

Every widget inside that subtree updates immediately. No re-render of the form definition is needed; the CSS cascade does the work.