theme.ts

view source

Declarations
#

10 declarations

color_schemes
#

ColorScheme
#

compose_themes
#

theme.ts view source

(base: { name: string; variables: { name: string; light?: string | undefined; dark?: string | undefined; summary?: string | undefined; }[]; summary?: string | undefined; scheme?: "dark" | "light" | "dual" | undefined; scheme_mirror?: { ...; }[] | undefined; }, ...overlays: { ...; }[]): { ...; } import {compose_themes} from '@fuzdev/fuz_css/theme.js';

Composes a base theme with overlay fragments by flatten + last-wins: later variables replace same-named earlier ones wholesale, a dark-only overlay keeping the light slot beneath it (see overlay_style_variable). Any knob-only theme is already a valid fragment - the contrast modifiers in contrast_modifiers are the canonical overlays. This is the hand-flatten precursor to a first-class extends, with the same merge semantics.

The base's scheme stance wins; when the base is single-scheme, each overlay variable is re-slotted to the stanced scheme's value so a dual-slot fragment can't leak the other scheme's appearance past the stance. The base's scheme_mirror carries through minus any names the overlays set - the mirror was computed against the base's own variables, so entries for newly composed names would shadow nothing but still render

  • and overlay values win over the remaining mirror by source order.

The composed name appends the overlay names so name-keyed pickers and renderers treat the composition as its own theme, and the base's summary carries through.

base

type Theme

overlays

type Theme[]

returns

Theme

FUZ_BAKED_THEME_LAYER
#

theme.ts view source

"fuz.theme.baked" import {FUZ_BAKED_THEME_LAYER} from '@fuzdev/fuz_css/theme.js';

The sublayer a build-time theme's own overlay renders into. A layer's direct styles outrank its sublayers, so a runtime theme rendered into fuz.theme wins over the baked one whatever their selectors' specificity, while the baked overlay still sits above fuz.preferences.

FUZ_LAYER_ORDER_STATEMENT
#

theme.ts view source

"@layer fuz.base, fuz.preferences, fuz.theme, fuz.utilities;" import {FUZ_LAYER_ORDER_STATEMENT} from '@fuzdev/fuz_css/theme.js';

The fuz_css cascade layer order: defaults (variables + element styles) in fuz.base, OS user-preference mappings (prefers-contrast, prefers-reduced-motion) in fuz.preferences, theme overrides in fuz.theme, generated utility classes in fuz.utilities. Layer order beats specificity and source order, so the preference mappings win over the defaults, theme overrides win over both regardless of head insertion order, and consumers' unlayered styles beat everything.

Two deliberate exceptions, both !important declarations in a layer, which outrank every later layer and unlayered styles: the prefers-reduced-motion mapping (a theme's durations must not re-enable motion the user turned off) and [hidden].

overlay_style_variable
#

theme.ts view source

(existing: { name: string; light?: string | undefined; dark?: string | undefined; summary?: string | undefined; } | undefined, overlay: { name: string; light?: string | undefined; dark?: string | undefined; summary?: string | undefined; }): { ...; } import {overlay_style_variable} from '@fuzdev/fuz_css/theme.js';

Overlays one variable onto the same-named variable beneath it, the merge every theme composition shares: the overlay replaces wholesale, except that a dark-only overlay keeps the light slot beneath it. A dark slot only shadows under .dark, so dropping the light slot would leave the variable without its light-scheme value.

existing

the variable being overlaid, if any

type StyleVariable | undefined

overlay

the variable that wins

returns

StyleVariable

pick_stance_slot
#

theme.ts view source

(v: { light?: string | undefined; dark?: string | undefined; } | undefined, stance: "dark" | "light"): string | undefined import {pick_stance_slot} from '@fuzdev/fuz_css/theme.js';

Picks the value a single-scheme stance renders from a dual-slot shape: the stanced scheme's slot, dark falling back to the light/base position. Shared by compose_themes and the theme editor so the re-slot semantics can't drift.

v

type { light?: string | undefined; dark?: string | undefined; } | undefined

stance

type "dark" | "light"

returns

string | undefined

render_theme_style
#

theme.ts view source

(theme: { name: string; variables: { name: string; light?: string | undefined; dark?: string | undefined; summary?: string | undefined; }[]; summary?: string | undefined; scheme?: "dark" | "light" | "dual" | undefined; scheme_mirror?: { ...; }[] | undefined; }, options?: RenderThemeStyleOptions): string import {render_theme_style} from '@fuzdev/fuz_css/theme.js';

Renders a theme's variables as CSS, wrapped in the fuz.theme cascade layer by default.

Renders exactly what the theme carries - an empty variables array renders nothing (inheriting the fuz.base defaults) unless a scheme stance needs pinning. To render the full default set, pass it: `render_theme_style({name: 'base', variables: default_variables})`. To render a single-scheme theme faithfully, resolve it through resolve_theme_stance first.

Total over any JSON value, because a theme restored from storage reaches this without passing the schema: whatever isn't the type Theme declares is dropped instead of thrown on or coerced into the stylesheet. A variables or scheme_mirror that isn't an array renders as empty, an entry that isn't an object is skipped, a name or slot that isn't a contained string drops its declaration, and a summary that isn't one drops its comment (see css_containment.ts).

theme

the theme to render

type Theme

options

default {}

returns

string

the theme CSS, or an empty string when there's nothing to render

RenderThemeStyleOptions
#

theme.ts view source

RenderThemeStyleOptions import type {RenderThemeStyleOptions} from '@fuzdev/fuz_css/theme.js';

comments?

type boolean

id?

Scopes the rendered variables to #id instead of :root. Dark slots render for both placements of the scheme class - on the scope element itself (#id.dark) and on the root (:root.dark #id), the ecosystem convention - so a scoped theme's dark appearance follows the page's.

Only literal-valued variables take effect in a scope: the derived color stops (--palette_a_50, --shade_50, ...) resolve their calc() on :root, and a descendant inherits the computed color, so a curve knob like --chroma_scale set at #id changes nothing. Knob-only themes need :root.

Any string works: it's escaped into the selector, so it matches the element whose id attribute is that string and can't end the rule or the <style> element. An empty string renders to :root.

type string | null

layer?

The cascade layer wrapping the rendered variables. Theme overrides default to fuz.theme so they beat the fuz.base defaults by layer order; pass null to render unlayered.

type string | null

to_theme_stance
#

theme.ts view source

(scheme: unknown): "dark" | "light" | null import {to_theme_stance} from '@fuzdev/fuz_css/theme.js';

Reads the single-scheme stance a theme's scheme names, or null for a dual-scheme theme. Total over any value, so it reads unvalidated data too.

scheme

a theme's scheme

type unknown

returns

"dark" | "light" | null

Depends on
#

Imported by
#