refract v0.1 · beta
Getting started / Scaffold a theme

Start from a colour, not a blank file

Answer a few questions and get a complete RawTheme — palettes with tonal ladders, semantic colours, a type scale with derived leading, a spacing ramp. Every colour is checked against WCAG contrast before the file is written. The generator runs once; what it produces is an ordinary theme file you own and edit.

1 · Two ways in

SituationCommandWrites
You have a project, it needs a themerefract createtheme.raw.ts · .js · .json
Nothing exists yetnpm create refract-themea whole publishable theme package
Tokens already exist elsewhererefract importsee DTCG interop

The first two run the same interview and the same generator — the difference is only whether a project is created around the result.

2 · The interview

Ten prompts, most of them Enter-through. Every one has a flag, and with no TTY each takes its default instead of blocking — so this is safe in a script or in CI.

Terminal
npx refract create

  ? Primary colour › #4c6ef5
      parsed · lightness 59% — lands at ≈400 on the ladder
  ? How many brand colours? › 2   → complement #9a7800
  ? Also add › semantic colours · neutral ramp · shadow tints
  ? Contrast target › WCAG AA
  ? Base font size › 16      ? Type scale › major-third
  ? Overall feel › Neutral   ? CSS reset › preflight
  ? Format › theme.raw.ts

  Contrast · 7 pairings checked
    primary     4.32  −1  4.51 AA
    secondary   4.15  −2  4.54 AA
    success     3.45  −7  4.54 AA
    warning     6.04      unchanged

  theme.raw.ts   7 subsystems · 151 tokens · 0 recipes

Or skip the questions entirely:

Terminal
npx refract create --yes                              # every default
npx refract create --seed "#e64980" --colors 3 \
                    --scheme triadic --feel editorial   # fully scripted

3 · What it writes

One file. Literal where a value came from the generator's judgement, declarative where the engine already synthesizes — so retuning a scale stays a one-word edit instead of a regenerated table:

theme.raw.ts (excerpt)
const ladder = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];

export const raw = {
  colors: {
    // each brand colour is its own palette, with its own ladder
    primary:   { base: "#496bf1", text: "#ffffff", steps: ladder },
    secondary: { base: "#937200", text: "#ffffff", steps: ladder },
    success:   { base: "#09882e", text: "#ffffff", steps: ladder },
    /* … info · warning · danger · neutral · shadow … */
  },
  typography: {
    // a declaration — the engine synthesizes xs…4xl from these two
    fontSize: { base: 16, ratio: "major-third" },
    // derived per step, and named after the step they were tuned for
    lineHeight: { base: 1.5, variants: { "4xl": 1.17, "3xl": 1.23, xs: 1.65 } },
  },
  // linear curve — every stop lands on the 4px grid
  layout: { spacing: { base: 4, step: 4, steps: { xs: 1, sm: 2, md: 4, lg: 6, xl: 8 } } },
} satisfies RawTheme;

The derived leading and tracking are named after the size step, so the pairing documents itself with no recipe holding it together:

your CSS
.hero h1 {
  font-size:      var(--dt-typography-fontsize-4xl);
  line-height:    var(--dt-typography-lineheight-4xl);
  letter-spacing: var(--dt-typography-letterspacing-4xl);
}

4 · What it doesn't write

No recipes. A scaffolded theme is tokens only — variables, not classes. Nothing composes into a className yet, and that's deliberate: which components you need and how they compose is design work, not something to guess. Turning these tokens into classes is the next step — see Recipes and Components.

Also absent by design: no @keyframes (nothing would reference one without a recipe), and no component definitions.

5 · Decisions baked in

Worth knowing before you edit, so you don't undo something deliberate:

DecisionWhy
Brand colours 2–5 are top-level palettesA harmony member is a single flat leaf — you can't build a UI on one. Each derived hue is promoted to its own family with a full ladder.
base is the brand colourThe ladder is absolute lightness, so the seed lands wherever it falls — often between two stops. Nothing snaps; the hex you typed survives. The 50…900 stops are for surfaces, borders and states.
Semantics are hue-anchoredRotating off the seed would make “danger” whatever lands at +150° — from a red seed, green. The four semantic colours start from fixed hues and borrow only the seed's character.
Contrast runs before writingEvery text pairing is scored and failing colours are darkened in OKLCH points until they clear the bar. A mid green at the primary's lightness reads fine as a chip and fails as a button.
Spacing uses the linear curvestep: 4 keeps every stop on a 4px grid. The geometric curve gives 8 · 12 · 18 · 27 · 40.5 — right for type, wrong for space.
Change a base later and re-run refract audit — the palette was generated to clear AA, and it's easy to lose that by hand.

6 · Retuning it

You wantChange
A different brand colourcolors.primary.base — every step re-derives
A tighter type scaletypography.fontSize.ratio"major-second"
More generous spacingthe multipliers in layout.spacing.steps
Softer cornersborders.radius.base

Don't replace fontSize: { base, ratio } with a hand-listed ladder, or the spacing curve with literal pixels — that discards the intent the engine synthesizes from.

7 · Wiring the build

refract init looks for a theme.raw.* and writes a config that imports it, rather than carrying a starter palette of its own. Run create first, then:

Terminal
npx refract init     # found theme.raw.ts — wired it up
npx refract build    # → dist/theme/theme.css
npx refract audit    # 7/7 pass, 0 fail

From here, Quickstart walks the same ground by hand if you'd rather understand the grammar before generating it, and Recipes is where these tokens become classes.

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.