@theme-registry/refract 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/_createTheme-255ff48e.esm.js +2 -0
- package/dist/_createTheme-255ff48e.esm.js.map +1 -0
- package/dist/_createTheme-41c01033.cjs.js +2 -0
- package/dist/_createTheme-41c01033.cjs.js.map +1 -0
- package/dist/_errors-be768b25.cjs.js +2 -0
- package/dist/_errors-be768b25.cjs.js.map +1 -0
- package/dist/_errors-d75a5c74.esm.js +2 -0
- package/dist/_errors-d75a5c74.esm.js.map +1 -0
- package/dist/adapter-kit.cjs.js +2 -0
- package/dist/adapter-kit.cjs.js.map +1 -0
- package/dist/adapter-kit.d.ts +88 -0
- package/dist/adapter-kit.esm.js +2 -0
- package/dist/adapter-kit.esm.js.map +1 -0
- package/dist/build/auditCommand.d.ts +19 -0
- package/dist/build/buildCommand.d.ts +23 -0
- package/dist/build/cli.d.ts +2 -0
- package/dist/build/config.d.ts +58 -0
- package/dist/build/diff.d.ts +56 -0
- package/dist/build/diffCommand.d.ts +29 -0
- package/dist/build/emit.d.ts +20 -0
- package/dist/build/emitTheme.d.ts +39 -0
- package/dist/build/guide.d.ts +34 -0
- package/dist/build/importCommand.d.ts +42 -0
- package/dist/build/index.d.ts +33 -0
- package/dist/build/init.d.ts +32 -0
- package/dist/build/paths.d.ts +42 -0
- package/dist/build/skillsCommand.d.ts +53 -0
- package/dist/build/tokensCommand.d.ts +17 -0
- package/dist/build/vendor.d.ts +39 -0
- package/dist/build.cjs.js +2 -0
- package/dist/build.cjs.js.map +1 -0
- package/dist/build.esm.js +2 -0
- package/dist/build.esm.js.map +1 -0
- package/dist/cli.js +8250 -0
- package/dist/cli.js.map +1 -0
- package/dist/color-math.cjs.js +2 -0
- package/dist/color-math.cjs.js.map +1 -0
- package/dist/color-math.esm.js +2 -0
- package/dist/color-math.esm.js.map +1 -0
- package/dist/core/ThemeAdapter.d.ts +234 -0
- package/dist/core/createTheme.d.ts +96 -0
- package/dist/core/cssProperties.d.ts +8 -0
- package/dist/core/defineAdapter.d.ts +25 -0
- package/dist/core/derive/crossProperty.d.ts +31 -0
- package/dist/core/derive/index.d.ts +11 -0
- package/dist/core/derive/resolveTokens.d.ts +27 -0
- package/dist/core/errors.d.ts +24 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/media/containers.d.ts +20 -0
- package/dist/core/media/defaults.d.ts +3 -0
- package/dist/core/media/descriptors.d.ts +12 -0
- package/dist/core/media/index.d.ts +11 -0
- package/dist/core/media/queries.d.ts +20 -0
- package/dist/core/model/buildModel.d.ts +116 -0
- package/dist/core/model/index.d.ts +14 -0
- package/dist/core/model/interpreted.d.ts +37 -0
- package/dist/core/model/mergeRuleSet.d.ts +68 -0
- package/dist/core/model/model.d.ts +260 -0
- package/dist/core/noopAdapter.d.ts +26 -0
- package/dist/core/normalize/index.d.ts +15 -0
- package/dist/core/normalize/properties.d.ts +20 -0
- package/dist/core/normalize/recipeResolver.d.ts +19 -0
- package/dist/core/normalize/recipes.d.ts +38 -0
- package/dist/core/normalize/responsive.d.ts +40 -0
- package/dist/core/normalize/types.d.ts +261 -0
- package/dist/core/rawTheme.d.ts +53 -0
- package/dist/core/ref.d.ts +13 -0
- package/dist/core/subsystem.d.ts +115 -0
- package/dist/core/types.d.ts +31 -0
- package/dist/core/units.d.ts +93 -0
- package/dist/dtcg/export.d.ts +48 -0
- package/dist/dtcg/import.d.ts +43 -0
- package/dist/dtcg/index.d.ts +21 -0
- package/dist/dtcg/parse.d.ts +6 -0
- package/dist/dtcg/types.d.ts +69 -0
- package/dist/dtcg.cjs.js +2 -0
- package/dist/dtcg.cjs.js.map +1 -0
- package/dist/dtcg.esm.js +2 -0
- package/dist/dtcg.esm.js.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/subsystems/animation/index.d.ts +13 -0
- package/dist/subsystems/animation/recipes.d.ts +20 -0
- package/dist/subsystems/animation/types.d.ts +52 -0
- package/dist/subsystems/borders/index.d.ts +10 -0
- package/dist/subsystems/borders/recipes.d.ts +29 -0
- package/dist/subsystems/borders/types.d.ts +50 -0
- package/dist/subsystems/colors/audit.d.ts +67 -0
- package/dist/subsystems/colors/colorInput.d.ts +17 -0
- package/dist/subsystems/colors/index.d.ts +12 -0
- package/dist/subsystems/colors/keywords.d.ts +9 -0
- package/dist/subsystems/colors/normalize.d.ts +26 -0
- package/dist/subsystems/colors/recipes.d.ts +21 -0
- package/dist/subsystems/colors/types.d.ts +98 -0
- package/dist/subsystems/colors/utils.d.ts +104 -0
- package/dist/subsystems/components/index.d.ts +13 -0
- package/dist/subsystems/components/recipes.d.ts +22 -0
- package/dist/subsystems/components/types.d.ts +46 -0
- package/dist/subsystems/effects/index.d.ts +9 -0
- package/dist/subsystems/effects/recipes.d.ts +24 -0
- package/dist/subsystems/effects/types.d.ts +63 -0
- package/dist/subsystems/effects/utils.d.ts +57 -0
- package/dist/subsystems/globals/index.d.ts +21 -0
- package/dist/subsystems/globals/presets.d.ts +33 -0
- package/dist/subsystems/globals/types.d.ts +48 -0
- package/dist/subsystems/index.d.ts +21 -0
- package/dist/subsystems/layout/index.d.ts +14 -0
- package/dist/subsystems/layout/normalize.d.ts +35 -0
- package/dist/subsystems/layout/recipes.d.ts +16 -0
- package/dist/subsystems/layout/structural.d.ts +37 -0
- package/dist/subsystems/layout/types.d.ts +154 -0
- package/dist/subsystems/typography/index.d.ts +9 -0
- package/dist/subsystems/typography/normalize.d.ts +17 -0
- package/dist/subsystems/typography/recipes.d.ts +21 -0
- package/dist/subsystems/typography/types.d.ts +49 -0
- package/package.json +83 -0
- package/skills/adapter-scaffold/SKILL.md +85 -0
- package/skills/adapter-usage/SKILL.md +75 -0
- package/skills/build-config/SKILL.md +77 -0
- package/skills/colors/SKILL.md +134 -0
- package/skills/consuming-the-output/SKILL.md +62 -0
- package/skills/dtcg-import/SKILL.md +76 -0
- package/skills/layout/SKILL.md +65 -0
- package/skills/overrides-and-child-themes/SKILL.md +70 -0
- package/skills/recipes-and-composition/SKILL.md +110 -0
- package/skills/theme-authoring/SKILL.md +107 -0
- package/skills/theme-foundations/SKILL.md +127 -0
- package/skills/troubleshooting/SKILL.md +97 -0
- package/skills/typography/SKILL.md +66 -0
- package/skills/visual-effects/SKILL.md +96 -0
- package/src/subsystems/colors/utils.ts +299 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: colors
|
|
3
|
+
description: Author the `colors` slice of a refract theme — palettes as bare hex / `[r,g,b]` / extended colours, the OKLCH numeric step ladder, tonal + derivation-spec + harmony variants, and palette recipes. Also the `@theme-registry/refract/color-math` helper subpath for computing colours live in an app that match the emitted CSS. Use when writing or tuning a palette. Triggers: "add a palette", "colour steps / tonal ramp", "lighten / darken / alpha a colour", "colour harmony", "OKLCH steps", "colour math helpers".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Colors
|
|
8
|
+
|
|
9
|
+
The `colors` key is an **open** map: each key is a palette name (`primary`, `neutral`, …)
|
|
10
|
+
holding a colour value. The reserved `recipes` block holds palette recipes. Cross-cutting axes
|
|
11
|
+
(`responsive`, `modes`, refs) are in **theme-foundations**; recipe mechanics are in
|
|
12
|
+
**recipes-and-composition**. Exhaustive options live in `docs/authoring.md`.
|
|
13
|
+
|
|
14
|
+
## A colour value
|
|
15
|
+
|
|
16
|
+
Three forms, cheapest first:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
colors: {
|
|
20
|
+
accent: "#4dabf7", // bare hex (3- or 6-digit)
|
|
21
|
+
ink: [33, 37, 41], // [r, g, b] tuple, 0–255
|
|
22
|
+
brand: { // extended
|
|
23
|
+
base: "#4dabf7",
|
|
24
|
+
text: "#ffffff", // paired foreground (same hex/tuple rule)
|
|
25
|
+
steps: [50, 100, 200, /* … */ 900],
|
|
26
|
+
variants: { subtle: { modifiers: [{ alpha: 40 }] } },
|
|
27
|
+
modes: [{ mode: "dark", base: "#1c7ed6" }],
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
A base may be a **hex string, `[r,g,b]` tuple, or any CSS colour** — `oklch()`, `hsl()`/`hsla()`,
|
|
33
|
+
`rgb()`/`rgba()`, or a named keyword (`"rebeccapurple"`). All normalize to a canonical `rgb(...)`
|
|
34
|
+
string at build and derive in OKLCH. Only a `var(--…)` is rejected — it can't be tonally derived;
|
|
35
|
+
borrow it as an [external token](../overrides-and-child-themes/SKILL.md) instead. Alpha is never
|
|
36
|
+
authored on a base — use an `alpha` variant.
|
|
37
|
+
|
|
38
|
+
## Steps — the numeric OKLCH ladder
|
|
39
|
+
|
|
40
|
+
`steps` is **numeric only** and defines an **absolute** OKLCH lightness ladder: each Tailwind-style
|
|
41
|
+
label maps to a fixed lightness via `L = (1000 − label) / 10`, so **low labels are light, high are
|
|
42
|
+
dark**, and the same label reads at the same lightness across every palette. `0` / `1000` are pure
|
|
43
|
+
white / black; every other label clamps to `[5, 98]`. The exact authored colour stays at the
|
|
44
|
+
unnumbered `colors.<name>` token — a rung is never aliased to it, and `lightenBy` / `darkenBy` do
|
|
45
|
+
**not** apply to numeric steps.
|
|
46
|
+
|
|
47
|
+
With **no** `steps`, refract auto-generates the named tonal set `light` / `lighter` / `dark` /
|
|
48
|
+
`darker`, each compounding `lightenBy` / `darkenBy` (default `10`) as OKLCH ΔL points.
|
|
49
|
+
|
|
50
|
+
## Variants — derivation specs
|
|
51
|
+
|
|
52
|
+
Beyond literals, a variant can be a **derivation spec** — a `modifiers` **chain** of single-key
|
|
53
|
+
dials (`{ darken }` / `{ lighten }` / `{ alpha }` / `{ adjust }`), applied left-to-right, with an
|
|
54
|
+
optional `ref` naming the source (defaults to the colour's own base; may point at another
|
|
55
|
+
variant/step by name, or a cross-property token path like `colors.brand`):
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
variants: {
|
|
59
|
+
hover: { modifiers: [{ darken: 8 }] }, // −8 OKLCH ΔL from base
|
|
60
|
+
ghost: { modifiers: [{ alpha: 15 }] }, // absolute opacity: 15% opaque
|
|
61
|
+
onHover: { ref: "hover", modifiers: [{ lighten: 4 }] }, // read another variant, then transform
|
|
62
|
+
muted: { modifiers: [{ adjust: { l: 60, c: 0.4, h: -10 } }] }, // absolute L, chroma ×, hue rotation
|
|
63
|
+
tinted: { modifiers: [{ darken: 6 }, { alpha: 90 }] }, // a multi-dial chain (darken THEN alpha)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`adjust` dials: `l` = absolute OKLCH lightness `0–100`; `c` = chroma **multiplier** (`1` keeps, `0`
|
|
68
|
+
greys); `h` = signed hue rotation in degrees. The same `modifiers` chain is the value form for a
|
|
69
|
+
mode (`{ mode, modifiers }`) and a responsive entry. Every generated variant carries `derive`
|
|
70
|
+
metadata, so `override()` of the base — or of a **cross-property** source — re-bakes the whole set
|
|
71
|
+
for free (see **overrides-and-child-themes**).
|
|
72
|
+
|
|
73
|
+
## harmony
|
|
74
|
+
|
|
75
|
+
Rotate the base's hue around the perceptual wheel (lightness + chroma held). String form uses default
|
|
76
|
+
member names; object form renames positionally:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
brand: { base: "#4dabf7", harmony: "triadic" }, // → triadic1, triadic2
|
|
80
|
+
brand: { base: "#4dabf7", harmony: { triadic: ["mint", "coral"] } },
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Schemes and their default members: `complement` → `complement`; `analogous` → `analogous1/2`;
|
|
84
|
+
`split-complement` → `split1/2`; `triadic` → `triadic1/2`; `tetradic` → `tetradic1` / `complement`
|
|
85
|
+
/ `tetradic2`. Exactly one scheme per colour.
|
|
86
|
+
|
|
87
|
+
## recipes — palette recipes
|
|
88
|
+
|
|
89
|
+
The reserved `recipes` block declares colour-bearing style properties (`background`,
|
|
90
|
+
`backgroundColor`, `color`, `borderColor`, `outlineColor`, plus any extra property passed through as
|
|
91
|
+
a literal). Each value names a palette reference (`"primary"`, `"primary.text"`, `"neutral.light"`)
|
|
92
|
+
or a literal. Recipe structure (states, variants, composition) is in **recipes-and-composition**.
|
|
93
|
+
|
|
94
|
+
## color-math — live colours that match the CSS
|
|
95
|
+
|
|
96
|
+
The same OKLCH functions refract bakes with are published at `@theme-registry/refract/color-math`,
|
|
97
|
+
string-in / string-out, so a colour computed live in the app matches the emitted CSS variable:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { lighten, darken, setL, rotateHue, complement, adjust, alpha,
|
|
101
|
+
toHexColor, toOklchColor } from "@theme-registry/refract/color-math";
|
|
102
|
+
|
|
103
|
+
lighten("#4dabf7", 10); // shift OKLCH lightness +10
|
|
104
|
+
alpha("#4dabf7", 40); // absolute 40% opacity
|
|
105
|
+
adjust("#4dabf7", { l: 60, c: 0.4, h: -10 });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Also exported: the conversion primitives `parseColor`, `serializeColor`, `convertHexToRGB`,
|
|
109
|
+
`convertRgbToHex`, `rgbToOklch`, `oklchToRgb`, `isHexColor`, `coerceColorInput`.
|
|
110
|
+
|
|
111
|
+
## Contrast audit — `refract audit`
|
|
112
|
+
|
|
113
|
+
Colours are the flagship, so refract can score them. The audit checks every palette `base`↔`text`
|
|
114
|
+
pairing and every recipe foreground↔background (all subsystems, incl. `:hover`) against **WCAG 2**
|
|
115
|
+
contrast, with an advisory **APCA** Lc. It **reports** — it never rewrites a colour; a non-derivable
|
|
116
|
+
side (`transparent`, a `var()`, a keyword) is `skipped`.
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
refract audit # report (exit 0)
|
|
120
|
+
refract audit --strict # fail the run (exit 1) on any pairing below the bar
|
|
121
|
+
refract audit --min-wcag AAA # raise the bar (AA | AAA | AA-large)
|
|
122
|
+
refract audit --large # relax to large-text thresholds
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Programmatic — reads a built theme, same scores as structured data:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { audit } from "@theme-registry/refract";
|
|
129
|
+
const { pairings, summary, ok } = audit(theme, { minWcag: "AA" });
|
|
130
|
+
// each pairing: { label, fg, bg, wcagRatio, wcagLevel, apcaLc, pass } or { skipped }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
WCAG levels: `AAA` ≥ 7, `AA` ≥ 4.5, `AA-large` ≥ 3 (large text only). APCA is advisory — reported,
|
|
134
|
+
never gated. `--strict` throws a `RefractError` (code `REFRACT_E_AUDIT`) listing every failing pair.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: consuming-the-output
|
|
3
|
+
description: Wire a built refract theme into an app (per adapter), and publish a theme so others can consume it. Use after a theme is built, when integrating it into React/CSS/Sass or shipping it as a package or artifact. Triggers: "use the theme in my app", "ThemeProvider", "import theme.css", "runtime theme switching", "publish the theme", "consume the built output".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Consuming the output
|
|
8
|
+
|
|
9
|
+
How the *output* is used differs by adapter — different format, different integration. The theme
|
|
10
|
+
you built (runtime `createTheme(...)` or `refract build`) exposes the shapes below.
|
|
11
|
+
|
|
12
|
+
## CSS
|
|
13
|
+
|
|
14
|
+
The emitted `.css` is self-contained — no refract at runtime.
|
|
15
|
+
|
|
16
|
+
- Import the stylesheet once (`import "./dist/theme/theme.css"`), then use the recipe **class
|
|
17
|
+
names** on elements. Look them up with `theme.getClass(subsystem, group, recipe)` or the
|
|
18
|
+
`theme.classes` map at build time.
|
|
19
|
+
- Values are `var(--…)` custom properties, so **runtime theming = swap `:root`**: override the
|
|
20
|
+
variables under a selector/attribute and everything rebinds with no re-render.
|
|
21
|
+
- Container-query utilities emit as `.<prefix>-cq-<name>` classes.
|
|
22
|
+
|
|
23
|
+
## styled-components
|
|
24
|
+
|
|
25
|
+
The SC adapter emits **TS/JS modules**, not a stylesheet.
|
|
26
|
+
|
|
27
|
+
- Wrap the app once: `<ThemeProvider theme={theme.theme}>`. This supplies token *values*; it's set
|
|
28
|
+
once and never swapped (dark rides `scheme`, not a provider swap).
|
|
29
|
+
- Recipes are tree-shakeable `css` blocks that read the theme
|
|
30
|
+
(`${({ theme }) => theme.colors.primary}`): `styled.button\`${componentsButtonsPrimary}\``, or
|
|
31
|
+
import the grouped `recipes` barrel.
|
|
32
|
+
- `GlobalStyle` (from the globals `preset` + element rules) mounts once.
|
|
33
|
+
- `theme.media.<bp>.{min,max}` and `theme.scheme.dark` are tagged-template helpers for responsive
|
|
34
|
+
and dark blocks inside a recipe.
|
|
35
|
+
- `theme.d.ts` (ts only) augments `DefaultTheme`, so `props.theme.*` is typed.
|
|
36
|
+
|
|
37
|
+
## SCSS / JSON
|
|
38
|
+
|
|
39
|
+
- **SCSS** — `@use` the emitted partials; consume the `$variables` and mixins in your Sass build.
|
|
40
|
+
- **JSON** — `theme.json` is the whole model as data (tokens + rule-sets + refs); feed it to
|
|
41
|
+
tooling or a non-CSS renderer.
|
|
42
|
+
|
|
43
|
+
## Publishing a theme others consume
|
|
44
|
+
|
|
45
|
+
A built theme is just files under `outDir`, so it ships as an **npm package**, a **zip / CI
|
|
46
|
+
artifact**, or a **vendored folder** — the downstream consumer needs neither refract nor these
|
|
47
|
+
skills. Ship the emitted files plus, ideally, a machine-readable token export
|
|
48
|
+
(`refract tokens` → `tokens.json`, see **dtcg-import**) and a short README pointing at the entry
|
|
49
|
+
files.
|
|
50
|
+
|
|
51
|
+
For AI-assisted consumers, refract can emit a **self-documenting guide** into `outDir` (an
|
|
52
|
+
`llms.txt` describing this theme's real class names / exports / token paths, plus a
|
|
53
|
+
`manifest.json`) so a downstream agent can consume the theme from the folder alone — enable it on
|
|
54
|
+
the build target (see **build-config**). Because the guide sits in `outDir`, it travels with any
|
|
55
|
+
distribution form, package specifier or not.
|
|
56
|
+
|
|
57
|
+
While *authoring* against a live project, the **`@theme-registry/refract-mcp`** server exposes the
|
|
58
|
+
same information as **live tools + resources** (schema 1) — `getClass` / `resolveToken` / `renderRecipe`
|
|
59
|
+
and a `refract://manifest.json` resource — answering against the real compiled theme. Prefer it when
|
|
60
|
+
connected; the emitted `manifest.json` / `llms.txt` are the offline fallback that ships with the theme.
|
|
61
|
+
The tools, the emitted guide, and the refract skills all speak the same contract. See **theme-authoring
|
|
62
|
+
→ Agent tools**.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dtcg-import
|
|
3
|
+
description: Migrate existing design tokens into refract and export back out via the DTCG (W3C tokens.json) interop. Use when onboarding from Figma / Tokens Studio / Style Dictionary, or exporting a refract theme as standard tokens. Triggers: "import tokens.json", "Figma tokens", "Tokens Studio", "Style Dictionary", "DTCG", "refract import", "refract tokens", "export design tokens".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# DTCG import / export
|
|
8
|
+
|
|
9
|
+
DTCG (the W3C Design Token Community Group `tokens.json` format) is refract's **interop** surface,
|
|
10
|
+
not an adapter. It's how you get *into* refract from existing tokens and *out* to a standard
|
|
11
|
+
format. It is Model-first and **property-tokens-only** — recipes and composition are out of scope
|
|
12
|
+
(they have no DTCG equivalent).
|
|
13
|
+
|
|
14
|
+
## Import — seed a theme from tokens
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
refract import tokens.json
|
|
18
|
+
refract import tokens.json --out theme.raw.ts --breakpoints sm:576,md:768,lg:992
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Turns a DTCG document into a `createTheme` raw input (and, unless `--raw-only`, a starter
|
|
22
|
+
`theme.config`). Flags: `--out <file>`, `--raw-only`, `--force`, `--breakpoint-group <name>`,
|
|
23
|
+
`--breakpoints <n:px,…>` (DTCG has no breakpoint concept, so supply them). After importing, the
|
|
24
|
+
palettes/scales are plain tokens — layer refract's synthesis (`steps`, `ratio`), recipes, and
|
|
25
|
+
composition on top by hand.
|
|
26
|
+
|
|
27
|
+
## Export — emit standard tokens
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
refract tokens # → tokens.json
|
|
31
|
+
refract tokens --out design-tokens.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Walks the built theme's flat token map and writes a DTCG document. It's **adapter-free** (uses
|
|
35
|
+
core's built-in noop adapter), reading only `theme.tokens` / `resolveToken` / breakpoints — so it
|
|
36
|
+
ignores recipes and needs no output adapter. Handy for handing tokens back to design tooling, or
|
|
37
|
+
as the machine-readable companion when **publishing a theme** (see **consuming-the-output**).
|
|
38
|
+
|
|
39
|
+
## Lossless round-trip (opt-in) — `refract → DTCG → refract`
|
|
40
|
+
|
|
41
|
+
The **standard** DTCG surface carries property tokens as *resolved literals* — so appearance
|
|
42
|
+
`modes`, `responsive` overrides, derivation refs (`{ ref, modifiers }`), and recipes don't survive a
|
|
43
|
+
plain round-trip. For a lossless refract-to-refract trip, opt in:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { toDTCG, fromDTCGTheme } from "@theme-registry/refract/dtcg";
|
|
47
|
+
|
|
48
|
+
const doc = toDTCG(theme, { includeRecipes: true }); // stashes the whole built Model under a $extensions key
|
|
49
|
+
const restored = fromDTCGTheme(doc, { adapter: createCssAdapter() }); // byte-identical Model back
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `toDTCG(theme, { includeRecipes: true })` writes the built **property Model** (incl. modes /
|
|
53
|
+
responsive / derivations / external) **and** rule-set / keyframe / container IR under the
|
|
54
|
+
reverse-DNS `$extensions` key **`com.theme-registry.refract`** (with a `version`). Off by default →
|
|
55
|
+
standard output is byte-identical.
|
|
56
|
+
- `fromDTCGTheme(doc, { adapter })` is the lossless entry — `fromDTCG` for the portable tokens **plus**
|
|
57
|
+
re-injects the stashed Model, reproducing a byte-identical theme.
|
|
58
|
+
- **Not portable:** other DTCG tools ignore that extension and see the resolved property tokens only.
|
|
59
|
+
|
|
60
|
+
## External tokens ↔ DTCG aliases
|
|
61
|
+
|
|
62
|
+
An `external` token (a passthrough to a **parent-owned** var) is kept out of the portable token
|
|
63
|
+
surface (a `var(--…)` colour can't be re-imported) — it round-trips via the extension above. Going
|
|
64
|
+
the other way, a DTCG **alias** `"{group.token}"` whose target is **absent** from the document is
|
|
65
|
+
treated as genuinely external and imported as a refract `external` token (`{color.brand}` →
|
|
66
|
+
`{ external: "colors.brand" }`); a **resolvable** same-document alias flattens to its literal as before.
|
|
67
|
+
|
|
68
|
+
## Programmatic API
|
|
69
|
+
|
|
70
|
+
The same conversions are on the `@theme-registry/refract/dtcg` subpath:
|
|
71
|
+
|
|
72
|
+
- `fromDTCG(doc, opts?)` → a raw theme input.
|
|
73
|
+
- `toDTCG(theme, opts?)` → a DTCG document (`opts.includeRecipes` adds the round-trip extension).
|
|
74
|
+
- `fromDTCGTheme(doc, { adapter, ... })` → a built `Theme` with recipes restored (lossless round-trip).
|
|
75
|
+
- `readRefractExtension(doc)` → the `com.theme-registry.refract` payload, or `undefined`.
|
|
76
|
+
- `parseDTCGDocument(doc)` — low-level walker resolving `{ref}` chains.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: layout
|
|
3
|
+
description: Author the `layout` slice of a refract theme — the length scales (spacing/gutters/sizes/aspectRatio) with geometric or linear scale synthesis, the four structural generators (columns/grids/stacks/container), and layout recipes with sizing verbs. The only CLOSED subsystem (fixed keys). Use when writing spacing scales or layout primitives. Triggers: "spacing scale", "sizes / min-width scale", "columns / grid / stack / container", "4/8-pt grid", "layout recipe padding/gap".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Layout
|
|
8
|
+
|
|
9
|
+
`layout` is the only **closed** subsystem — its keys are fixed: the length-scale properties
|
|
10
|
+
(`spacing` / `gutters` / `sizes` / `aspectRatio`), the four structural generators (`columns` /
|
|
11
|
+
`grids` / `stacks` / `container`), and the `recipes` block. Cross-cutting axes are in
|
|
12
|
+
**theme-foundations**; recipe mechanics in **recipes-and-composition**. Exhaustive options live in
|
|
13
|
+
`docs/authoring.md`.
|
|
14
|
+
|
|
15
|
+
## Length scales — synthesis
|
|
16
|
+
|
|
17
|
+
`spacing`, `gutters`, and `sizes` can synthesize their variant ramp from a base + one curve (declare
|
|
18
|
+
`ratio` **or** `step`, never both). `aspectRatio` is a plain property (no ramp).
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
layout: {
|
|
22
|
+
// geometric: base × ratio^n, `steps` an ORDERED name array (index = exponent)
|
|
23
|
+
spacing: { base: 8, ratio: 1.5, steps: ["xs", "sm", "md", "lg", "xl"] },
|
|
24
|
+
|
|
25
|
+
// linear: step × mult, `steps` a name→multiplier map (the 4/8-pt grid)
|
|
26
|
+
gutters: { base: 0, step: 4, steps: { sm: 1, md: 2, lg: 4 } },
|
|
27
|
+
|
|
28
|
+
sizes: { base: "20rem", ratio: 1.25, steps: ["sm", "md", "lg"],
|
|
29
|
+
variants: { full: "100%", prose: "65ch" } }, // authored variants win, never synthesized
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Omitting `steps` uses the default ladder `xs sm md lg xl 2xl 3xl 4xl`. Each synthesized step is a
|
|
34
|
+
**derived** ref (`scaleStep`), so `override()` of the base re-derives the whole ramp for free (see
|
|
35
|
+
**overrides-and-child-themes**). Authored `variants` win over synthesized ones; `spacing` / `gutters`
|
|
36
|
+
always expose a forced `none` (`0`). A `responsive` entry carrying `ratio` / `step` regenerates the
|
|
37
|
+
**whole named scale** at that breakpoint (one `target` override per step).
|
|
38
|
+
|
|
39
|
+
## Structural generators
|
|
40
|
+
|
|
41
|
+
Each key drives a generator that emits layout primitives; declarations reference the length-scale
|
|
42
|
+
tokens by name.
|
|
43
|
+
|
|
44
|
+
- **`columns`** — `number` or `{ size, gutter?, inset? }`. Generates per-span × per-breakpoint
|
|
45
|
+
utilities: `col-<bp>-<n>` (`grid-column-end: span n`) and `offset-<bp>-<n>` (`grid-column-start`),
|
|
46
|
+
plus `columns--size/gutter/inset` config tokens.
|
|
47
|
+
- **`grids`** — `{ <name>: { templateColumns, templateRows, autoRows, autoColumns, justifyItems,
|
|
48
|
+
alignItems, justifyContent, alignContent, gap, responsive } }`. Each emits a `grid-<name>` recipe
|
|
49
|
+
(`display: grid` + template props; `gap` resolves to a `spacing` ref).
|
|
50
|
+
- **`stacks`** — `{ <name>: { direction: "row"|"column", align, justify, wrap, inline, gap,
|
|
51
|
+
responsive } }`. Each emits a `stack-<name>` recipe (flex / inline-flex, direction, align, justify,
|
|
52
|
+
wrap; `gap` → `spacing` ref).
|
|
53
|
+
- **`container`** — a mode string (`"fixed"` / `"fluid"` / a width) or `{ base, inset, gutter,
|
|
54
|
+
direction, align, justify, maxWidth, variants, responsive }`. Always emits a default container
|
|
55
|
+
(defaults to `"fixed"`). `fixed` steps `max-width` per breakpoint; `fluid` applies a `maxWidth` cap
|
|
56
|
+
(a `sizes` variant name → unit-aware ref, a breakpoint name → its px width, else a literal); a
|
|
57
|
+
custom width becomes that `max-width`. Emits `container--inset/gutter` config tokens.
|
|
58
|
+
|
|
59
|
+
## recipes — layout recipes
|
|
60
|
+
|
|
61
|
+
The reserved `recipes` block declares spacing-bearing properties (`paddingX` / `paddingY` / `marginX`
|
|
62
|
+
/ `marginY` / `gap` / `background`) plus **sizing verbs** — `width` / `minWidth` / `maxWidth` /
|
|
63
|
+
`height` / `minHeight` / `maxHeight`, each naming a `layout.sizes` variant and routing to its CSS
|
|
64
|
+
longhand. Non-scale dimensional CSS (`display`, `position`, …) stays in a component `css` delta.
|
|
65
|
+
Recipe structure is in **recipes-and-composition**.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: overrides-and-child-themes
|
|
3
|
+
description: Derive brand variants and child themes from a base theme with theme.override(partial). Use for multi-brand, white-label, or sub-brand setups where several themes share one structure. Triggers: "child theme", "brand variant", "white-label", "theme.override", "override a token", "multi-brand".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Overrides and child themes
|
|
8
|
+
|
|
9
|
+
`theme.override(partial)` returns a **new theme** by delta-merging a partial raw theme onto the
|
|
10
|
+
base. Use it for multi-brand / white-label setups: author one structural base, then derive brands
|
|
11
|
+
that change only what differs.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const base = createTheme(raw, { adapter: createCssAdapter() });
|
|
15
|
+
|
|
16
|
+
const acme = base.override({
|
|
17
|
+
colors: { brand: { base: "#d6336c" } }, // only the token(s) that differ
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
acme.css; // fully re-derived output
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What re-derives
|
|
24
|
+
|
|
25
|
+
The override re-runs the pipeline, so **derived values recompute** — a palette `base` change
|
|
26
|
+
re-derives its `light`/`dark`/`steps`, a layout scale re-synthesizes its ramp, and every recipe
|
|
27
|
+
that references those tokens picks up the new values. **Cross-property derivations re-derive too:**
|
|
28
|
+
a variant or mode that reads *another* property (`{ ref: "colors.brand", modifiers: [{ darken: 12 }] }`)
|
|
29
|
+
recomputes when you override its **source** — even though the deriving property isn't in your
|
|
30
|
+
partial. This is why tokens are stored as derived refs, not frozen literals. The adapter surface
|
|
31
|
+
(`.css`, `.classes`, …) recomputes for the child.
|
|
32
|
+
|
|
33
|
+
## Guidelines
|
|
34
|
+
|
|
35
|
+
- **Override the smallest delta.** Change the `base` of a palette, not each derived variant, and
|
|
36
|
+
let derivation do the rest.
|
|
37
|
+
- **Structure stays in the base.** Overrides are for *values* — keep recipes, breakpoints, and
|
|
38
|
+
composition in the base theme so every brand shares one shape.
|
|
39
|
+
- **Chain freely.** A child can itself be overridden (`acme.override({...})`), building a small
|
|
40
|
+
tree of brand → sub-brand.
|
|
41
|
+
- **See the blast radius first.** An override re-derives everything downstream — before you commit
|
|
42
|
+
one, run `diffTheme(partial)` (via the MCP server, or `refract diff` in CI) to see exactly which
|
|
43
|
+
tokens, classes and contrast pairings the delta moves. Plan, then apply. See **theme-authoring →
|
|
44
|
+
Agent tools**.
|
|
45
|
+
- For the merge semantics of individual fields (refs, `css`, `states`, `responsive`) the same
|
|
46
|
+
rules as recipe variants apply — see **recipes-and-composition**.
|
|
47
|
+
|
|
48
|
+
For emitting several brands to disk, give each its own build target (or run the build per
|
|
49
|
+
override) — see **build-config**.
|
|
50
|
+
|
|
51
|
+
## Extending a theme you don't own — external tokens
|
|
52
|
+
|
|
53
|
+
`override()` derives a child of a theme *you* built. To instead build ON TOP of a **published**
|
|
54
|
+
theme — reusing its variables without redefining them — declare the borrowed tokens as external:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
extends: { prefix: "dt" }, // the parent theme's CSS-variable prefix
|
|
58
|
+
colors: {
|
|
59
|
+
brand: { external: "colors.brand" }, // path form → var(--dt-colors-brand)
|
|
60
|
+
surface: { external: "--mat-sys-surface" }, // literal form (leading --) → any var, verbatim
|
|
61
|
+
accent: "#e64980", // your own new token
|
|
62
|
+
recipes: { solid: { cta: { background: "brand", color: "accent" } } },
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
An external token is a passthrough to the parent's CSS variable: referenceable everywhere, never
|
|
67
|
+
defined locally, never tonally derived, and it survives `override()`. Path form assumes the parent's
|
|
68
|
+
default naming (`extends.prefix`, any string incl. `""`); literal form (`--…`) points at any variable
|
|
69
|
+
(Material/Tailwind/hand-rolled). Best for individual tokens (colours, spacing, type). For a one-off,
|
|
70
|
+
`css: { color: "var(--dt-colors-brand)" }` also works — a bare string in a `css` block is a literal.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recipes-and-composition
|
|
3
|
+
description: How recipes work across every refract subsystem — the group → recipe → variant tiers, states, the variants modifier map, and cross-subsystem composition via components. Read this whenever authoring anything under a recipes block, not just colors or layout. Triggers: "add a recipe", "recipe variants", "compose a button/component", "components block", "recipe states", "variant vs recipe".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Recipes and composition
|
|
8
|
+
|
|
9
|
+
Every subsystem exposes the **same** recipe machinery under its `recipes` key (and `components`
|
|
10
|
+
is composition-only). Learn it once here; the domain guides just say "and it has recipes." For
|
|
11
|
+
the shared override axes (`responsive`, `variant:` / `target:`, `states`) see **theme-foundations**.
|
|
12
|
+
|
|
13
|
+
## Three tiers
|
|
14
|
+
|
|
15
|
+
`recipes: { <group>: { <recipe>: { …props } } }`
|
|
16
|
+
|
|
17
|
+
- **group** (`solid`) — a namespace of related recipes.
|
|
18
|
+
- **recipe** (`brand`) — the leaf that resolves to a class / export.
|
|
19
|
+
- **variant** (`muted`) — an optional modifier delta on a recipe (below).
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
colors: {
|
|
23
|
+
recipes: {
|
|
24
|
+
solid: {
|
|
25
|
+
brand: {
|
|
26
|
+
background: "brand", color: "brand.text",
|
|
27
|
+
states: [{ state: "hover", background: "brand.dark" }],
|
|
28
|
+
responsive: [{ breakpoint: "md", query: "min", state: "hover", background: "brand.darker" }],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Values that name a token become references (`var(--…)` in CSS) so overrides cascade; unknown
|
|
36
|
+
names pass through as literals (see **theme-foundations** → References).
|
|
37
|
+
|
|
38
|
+
## States
|
|
39
|
+
|
|
40
|
+
`states` is validated against the **adapter's** known set (CSS: `hover` / `focus` / `disabled` /
|
|
41
|
+
…); an unknown state throws at build. It's a **LIST of override entries**, each
|
|
42
|
+
`{ state, target?, …deltas }` — author them directly, or put a `state` on a `responsive` entry to
|
|
43
|
+
gate on a breakpoint too. An optional `target` scopes a state onto a recipe **variant** sibling
|
|
44
|
+
(`<item>-<variant>`), so two same-state entries can differ per variant:
|
|
45
|
+
`states: [{ state: "hover", … }, { state: "hover", target: "lg", … }]`.
|
|
46
|
+
|
|
47
|
+
## Variants — a modifier map on any recipe
|
|
48
|
+
|
|
49
|
+
Add a `variants` map of deltas to any recipe in any subsystem. Each desugars to a flat sibling
|
|
50
|
+
`<recipe>-<variant>`; the bare recipe still emits. **Opt-in + additive** — no `variants` key
|
|
51
|
+
means unchanged output.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
solid: {
|
|
55
|
+
brand: {
|
|
56
|
+
background: "brand", color: "brand.text",
|
|
57
|
+
variants: { muted: { background: "brand.light" } }, // → solid.brand + solid.brand-muted
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Merge rules, per field:
|
|
63
|
+
|
|
64
|
+
- a **ref** replaces the base ref;
|
|
65
|
+
- **`css`** shallow-merges by property;
|
|
66
|
+
- **`states`** merge by state name;
|
|
67
|
+
- **`responsive[]`** concatenates;
|
|
68
|
+
- a prop set to **`null`** drops an inherited ref (`borders: null`) — `null`, not `"none"`
|
|
69
|
+
(which stays a real value like `border: none`).
|
|
70
|
+
|
|
71
|
+
The join separator is `-` (matching token variants). A desugared name that collides with an
|
|
72
|
+
existing sibling throws. Variants are a flat axis — no nesting.
|
|
73
|
+
|
|
74
|
+
## Composition — the `components` subsystem
|
|
75
|
+
|
|
76
|
+
`components` owns no primitive tokens; each recipe **references other subsystems' recipes** and
|
|
77
|
+
adds an own `css` delta (which wins on conflict):
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { ref } from "@theme-registry/refract";
|
|
81
|
+
|
|
82
|
+
components: { recipes: { buttons: { primary: {
|
|
83
|
+
colors: "solid.brand", typography: "button.large", layout: "padding.button", borders: "outline.hairline",
|
|
84
|
+
css: { cursor: "pointer", border: "none", color: ref("colors.brand.text") }, // see the rule below
|
|
85
|
+
states: [{ state: "hover", css: { transform: "translateY(-1px)" } }],
|
|
86
|
+
} } } }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**The `css` block is literal-first:** a **bare string / number is a raw CSS literal** (`cursor:
|
|
90
|
+
"pointer"` just works), and a **token reference** uses `ref("…")` or the JSON-safe `{ ref: "…" }`
|
|
91
|
+
object. This differs from the composition fields above (`colors: "solid.brand"`), where a bare
|
|
92
|
+
string is always a reference — those compose tokens; the `css` block writes CSS. Prefer `ref()`
|
|
93
|
+
explicitly. (Full rule in **theme-foundations**.)
|
|
94
|
+
|
|
95
|
+
The referenced recipes are spread in (their resolved declarations), then the own `css` delta is
|
|
96
|
+
applied on top. In CSS this emits a class list; in styled-components it spreads the referenced
|
|
97
|
+
`css` blocks — see **adapter-usage** and **consuming-the-output** for how each adapter realizes
|
|
98
|
+
composition. `variants` works here too (a component recipe can carry its own modifier map).
|
|
99
|
+
|
|
100
|
+
Don't hand-derive the composed class name — read the **real** one: `theme.getClass("components",
|
|
101
|
+
"buttons", "primary")` returns the full class list, or, with an MCP server connected, the `getClass`
|
|
102
|
+
/ `renderRecipe` tools return the real class / emitted CSS live (configured prefix and all). See
|
|
103
|
+
**theme-authoring → Agent tools**.
|
|
104
|
+
|
|
105
|
+
**Precedence.** refract emits low, even specificity (single classes; zero-specificity `:where()` for
|
|
106
|
+
globals) and relies on **source order** at equal specificity — the delta class is emitted last, so it
|
|
107
|
+
wins. If you compose refract with an existing stylesheet or utility framework and want precedence that
|
|
108
|
+
doesn't depend on load order, wrap the output in a cascade **`@layer`** via the CSS adapter's `layer`
|
|
109
|
+
option (`createCssAdapter({ layer: true })` → `@layer refract`). Layered rules always lose to unlayered
|
|
110
|
+
app CSS, so your styles win by default. Single-file emit only; off by default (byte-identical).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theme-authoring
|
|
3
|
+
description: Entry point and hub for authoring a refract theme — frame a RawTheme (breakpoints + subsystem keys), then route to the focused domain guides. Use when starting a theme, adding a subsystem, or unsure which guide applies. Triggers: "author a theme", "write a raw theme", "set up refract", "add colors/typography/layout/a recipe", "which refract skill".
|
|
4
|
+
tier: core
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Theme authoring — hub
|
|
8
|
+
|
|
9
|
+
refract compiles **one `RawTheme`** into a format-neutral `ThemeModel`, then an **adapter**
|
|
10
|
+
lowers that model to a concrete output (CSS variables, SCSS, styled-components modules, JSON).
|
|
11
|
+
You author the `RawTheme`; the adapter and build config decide the output. This guide frames
|
|
12
|
+
the theme and routes to the focused guide for whatever you're actually doing.
|
|
13
|
+
|
|
14
|
+
**You are authoring input, not changing the engine.** Never edit the core or a subsystem to
|
|
15
|
+
make a theme work — if the theme can't express something, that's a real gap to surface, not a
|
|
16
|
+
patch. Full narrative reference lives in `docs/authoring.md`.
|
|
17
|
+
|
|
18
|
+
## Frame the theme
|
|
19
|
+
|
|
20
|
+
- **Type it.** `const raw = { … } satisfies RawTheme`, with
|
|
21
|
+
`import type { RawTheme } from "@theme-registry/refract/build"`. The `satisfies` gives
|
|
22
|
+
autocomplete and rejects bogus top-level keys — a red squiggle means the authored shape is
|
|
23
|
+
wrong; fix the theme, not the types.
|
|
24
|
+
- **Breakpoints first.** `breakpoints: { name: minWidthPx }`. Every responsive override
|
|
25
|
+
references these names, so settle them before anything else (a `0`-width base is fine).
|
|
26
|
+
Breakpoints, containers, and the shared responsive/variant/target/modes/states vocabulary
|
|
27
|
+
are all covered in **theme-foundations** — read it once; the domain guides build on it.
|
|
28
|
+
- **One key per subsystem**, each optional. Each holds properties plus an optional nested
|
|
29
|
+
`recipes` block:
|
|
30
|
+
|
|
31
|
+
| Key | Domain | Guide |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `colors` | palettes, steps, harmony, modes | **colors** |
|
|
34
|
+
| `typography` | font families, modular type scale | **typography** |
|
|
35
|
+
| `layout` | spacing/sizes scales, columns/grids/stacks/container | **layout** |
|
|
36
|
+
| `effects`, `borders`, `animation` | shadows/transitions/blur, stroke geometry, motion | **visual-effects** |
|
|
37
|
+
| `components` | cross-subsystem composition (recipes only; `css` is literal-first, `ref()` for tokens) | **recipes-and-composition** |
|
|
38
|
+
| `globals` | `preset` + themed bare-element rules | this guide (below) |
|
|
39
|
+
|
|
40
|
+
## Route to the right guide
|
|
41
|
+
|
|
42
|
+
- **Any recipe, states, `variants`, or `components` composition** → **recipes-and-composition**.
|
|
43
|
+
Recipes work identically across every subsystem, so that vocabulary lives in one place.
|
|
44
|
+
- **A specific token domain** (palettes, type scale, spacing, effects) → the domain guide above.
|
|
45
|
+
- **Picking or configuring an output format** → **adapter-usage** (which adapter) and
|
|
46
|
+
**build-config** (`theme.config.ts`, the `refract` CLI, emit modes, dark-mode strategy).
|
|
47
|
+
- **Using the built theme in an app**, or shipping a self-documenting theme package →
|
|
48
|
+
**consuming-the-output**.
|
|
49
|
+
- **Brand variants / child themes** → **overrides-and-child-themes**.
|
|
50
|
+
- **Importing existing design tokens** (Figma / Tokens Studio / Style Dictionary) →
|
|
51
|
+
**dtcg-import**.
|
|
52
|
+
|
|
53
|
+
## Agent tools — MCP, then the static fallback
|
|
54
|
+
|
|
55
|
+
When a **refract MCP server** is connected (a project can ship one — `@theme-registry/refract-mcp`,
|
|
56
|
+
auto-discovers `theme.config.*`), prefer its **live tools** over guessing. They answer against the
|
|
57
|
+
**real compiled theme**, per configured adapter — so class names carry the real prefix and validation
|
|
58
|
+
runs the real build, not a stand-in:
|
|
59
|
+
|
|
60
|
+
- `validateTheme(candidate)` — before writing an edit; returns **every** problem at once, per target.
|
|
61
|
+
- `diffTheme(candidate)` — the **blast radius** of an edit *before* you apply it: which tokens moved,
|
|
62
|
+
which classes changed, which contrast pairings crossed a threshold, which targets stopped building.
|
|
63
|
+
- `getClass` / `renderRecipe` — the real class / emitted CSS for a recipe.
|
|
64
|
+
- `resolveToken` / `searchTokens` / `listTokens` — a token's value + `varName` + `derivedFrom`, or discovery.
|
|
65
|
+
- `checkContrast` — a WCAG-2 audit of the theme's colour pairings.
|
|
66
|
+
- The server also serves the guide as MCP resources: `refract://manifest.json` (schema 1 — real names +
|
|
67
|
+
DTCG tokens) and `refract://llms.txt`.
|
|
68
|
+
|
|
69
|
+
**Fallback when no MCP server is connected:** read the emitted **`manifest.json`** / **`llms.txt`** (when
|
|
70
|
+
the build enabled the guide — see **consuming-the-output**), or call the runtime accessors on the built
|
|
71
|
+
theme (`theme.getClass(...)`, `theme.resolveToken(...)`). The MCP tools, the emitted guide, and these
|
|
72
|
+
skills all speak the same **schema 1** contract, so they don't disagree.
|
|
73
|
+
|
|
74
|
+
## Globals — themed element rules + reset
|
|
75
|
+
|
|
76
|
+
Bare-element styling lives in the `globals` key (no separate guide). `preset` is the
|
|
77
|
+
normalization base (`"preflight" | "normalize" | "reset" | false`) plus a default `h1`–`h6`
|
|
78
|
+
scale map; `elements` are themed rules — each selector is a recipe-item (flat literal-first leaves,
|
|
79
|
+
`ref()` for tokens, plus `states` / `responsive` / `variants`), no cross-subsystem composition refs. A variant
|
|
80
|
+
is a delta-only modifier the adapters scope higher (CSS `a.subtle`, SC/SCSS nested `&.subtle`).
|
|
81
|
+
There is no `modes` axis — light/dark rides the referenced token's own modes. Unknown refs fail
|
|
82
|
+
loud; preset default headings drop opportunistically.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
globals: {
|
|
86
|
+
preset: "preflight", // + default h1–h6 → type scale
|
|
87
|
+
elements: {
|
|
88
|
+
a: {
|
|
89
|
+
color: ref("colors.link"), // ref() = token reference
|
|
90
|
+
textDecoration: "underline", // bare string = literal (raw CSS)
|
|
91
|
+
states: [{ state: "hover", color: ref("colors.link.dark") }],
|
|
92
|
+
responsive: [{ breakpoint: "md", query: "min", fontSize: ref("typography.fontSize.lg") }],
|
|
93
|
+
variants: { subtle: { color: ref("colors.muted") } }, // → a.subtle
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Verify
|
|
100
|
+
|
|
101
|
+
- Build once and inspect: `createTheme(raw, { adapter: createCssAdapter() }).css` (plus
|
|
102
|
+
`.tokens` / `.classes`). See **adapter-usage** for the call surface.
|
|
103
|
+
- Building to disk? Run `refract build` and check the files under each `outDir` — see
|
|
104
|
+
**build-config**.
|
|
105
|
+
- Typecheck the `satisfies RawTheme`.
|
|
106
|
+
- MCP server connected? Run `validateTheme` (all problems at once) and `diffTheme` (the edit's blast
|
|
107
|
+
radius) against the real adapters *before* writing — see **Agent tools** above.
|