refract v0.1 · beta
Core concepts / createTheme & the Theme

createTheme & the Theme

createTheme(raw, options) is the one entry point. It normalizes your RawTheme into the format-neutral Model, binds the adapter, and returns a Theme — the object you read output and tokens from.

signature
createTheme(raw: RawTheme, options: { adapter: ThemeAdapter; media?: MediaConfig; units?: UnitsConfig; baseFontSize?: number }): Theme

Options

OptionTypeRequiredDescription
adapterThemeAdapterRequiredThe output target — createCssAdapter() / createScssAdapter() / … Core ships no default (that's what keeps the Model format-neutral). For a token-only consumer that reads theme.tokens/resolveToken/model and renders nothing, pass the built-in createNoopAdapter().
media{ unit?: "px"|"em"|"rem"; baseFontSize? }OptionalUnit for @media / @container thresholds (breakpoints are authored in px; default px). Distinct from units, which governs declaration values. Stable across override() children.
unitsUnitsConfigOptionalLength units for declaration values (§21). A token-path role map — units.default (global), units["<subsystem>"], units["<subsystem>.<property>"]; most-specific wins, over a built-in seed (length subsystems → px, lineHeight → none, letterSpacing → em). Resolved once, format-neutrally, onto every length leaf — every adapter then emits the same unit. See Length units.
baseFontSizenumberOptional16Divisor when a deferred length resolves to rem (§21). Distinct from media.baseFontSize.
propertiesOverlay · ruleSetsOverlay · keyframesOverlay · containersOverlayDTCG round-tripOptionalPrebuilt Model slices spliced in after the standard build, to restore the lossless bits a resolved DTCG token surface can't carry — appearance modes / responsive / derivation refs, and whole recipes/keyframes/containers. Set by fromDTCGTheme from the com.theme-registry.refract extension; rarely set by hand. Absent → the standard build is untouched.

The Theme surface

The base surface is format-neutral; the adapter adds its own output getters via extend (the CSS adapter adds css / variablesCss / recipesCss / nodes / classes).

MemberTypeWhat it is
modelThemeModelThe held state — the single format-neutral source of truth.
tokensRecord<string, Ref>Flat, lazy, cached path → Ref map of property tokens. Aliases / derived steps stay as refs ({ ref, fn, arg }), so it's override-safe.
resolveToken(path) => LiteralResolve one token path to its concrete value — following aliases and running derivations (lighten/darken/…). Throws on an unknown path.
override(partial) => ThemeDerive a child theme (delta merge — see below).
css / classes / …adapter-addedThe adapter's output surface (CSS adapter: css, variablesCss, recipesCss, nodes, classes).

theme.override() — child themes

A delta merge, not a re-run. Only the partial's changed subsystem slices are re-normalized and immutably merged into the current Model at property / rule-set-variant granularity; untouched branches keep their reference. The parent Model (and its bound output) is left byte-identical — so it's a real child theme. Overriding a colour base re-derives its synthesized steps for free.

override.ts
const theme = createTheme(raw, { adapter: createCssAdapter() });

// a dark child — only the touched slices re-normalize; parent untouched
const dark = theme.override({ colors: { brand: { base: "#8aa2ff", text: "#0b1020" } } });
dark.css;   // a full stylesheet for the child
theme.css;  // unchanged
The preset switcher on this site is exactly this: “Halcyon Noir” is a theme.override() child of Halcyon, swapped in live.
Technical documentation for @theme-registry/refract · all output is compiled client-side by the real library (CSS adapter). The chrome is theme-aware; the render panes carry each preset's own world. · MIT licensed.