# Theme generation

> createTheme turns a brand color into a full light and dark token set with guaranteed WCAG AA contrast.

Source: https://ui.jcsoftdev.com/docs/theming/theme-generator

`@jcsoftdev/ui-tokens` ships a pure function that derives every semantic token from a few seed colors. It has no dependencies and no DOM access, so it runs at build time, on the server, or in the browser.

```ts
import { createTheme, toCss } from "@jcsoftdev/ui-tokens";

const theme = createTheme({
  brand: "#6d28d9",
  accent: "#0ea5e9",
  neutral: "#64748b",
  contrast: "AA",
});

const css = toCss(theme, { selector: ":root" });
```

In the browser, `applyTheme` is the only function that touches the DOM:

```ts
import { applyTheme } from "@jcsoftdev/ui-tokens/dom";

applyTheme(theme, document.documentElement);
```

`toCss` writes every rule with the zero-specificity `:where()` around its scheme conditions, so a generated block weighs the same as your selector (`:root` is 0,1,0) and a `data-accent` preset (`:root[data-accent="menta"]`, 0,2,0) still overrides it in light and dark. It also re-declares the values under `[data-theme="light"]` and `[data-theme="dark"]` descendants, so a forced-scheme subtree inside the opposite scheme gets its own colors. `applyTheme` gives every call its own `<style>`; applying again to the same element replaces its theme, and the returned function removes only what that call added.

## Options

| Option     | Meaning                                                                        |
| ---------- | ------------------------------------------------------------------------------ |
| `brand`    | Required hex. The primary action color. Its hue and chroma drive the scale.    |
| `accent`   | Optional hex for the neighbour tone (`--accent-2`). Defaults to a hue shift.   |
| `neutral`  | Optional hex whose hue tints the grays. Defaults to a near-neutral from brand. |
| `danger`   | Optional hex that overrides the error color.                                   |
| `contrast` | `"AA"`. Text pairs are at least 4.5:1, UI pairs at least 3:1, in both schemes. |

The guarantee is checked for every foreground and background pair the theme declares, for light and dark. The brand color is used as the fill exactly as given. Everything that must read against it or against a surface (`on-accent`, `accent-ink`, `accent-word`, the status colors, chart colors, field borders) is solved: its OKLCH lightness moves until the WCAG ratio is met, and out-of-gamut colors lose chroma, never lightness. `auditTheme(theme)` returns the list of violations (empty when compliant) so you can assert it in your own tests.

## CLI

```bash
ui-theme generate --brand "#6d28d9" [--accent "#0ea5e9"] [--neutral "#64748b"] --out ./theme
```

merges the generated colors into the bundled default tokens (the `data-accent` presets are kept) and writes `tokens.json` (the same shape as `default.json`), `tokens.css`, `theme.css`, `tailwind.css` and `fonts.css`. Add `--danger <hex>` to override the error hue.
