@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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_createTheme-255ff48e.esm.js +2 -0
  3. package/dist/_createTheme-255ff48e.esm.js.map +1 -0
  4. package/dist/_createTheme-41c01033.cjs.js +2 -0
  5. package/dist/_createTheme-41c01033.cjs.js.map +1 -0
  6. package/dist/_errors-be768b25.cjs.js +2 -0
  7. package/dist/_errors-be768b25.cjs.js.map +1 -0
  8. package/dist/_errors-d75a5c74.esm.js +2 -0
  9. package/dist/_errors-d75a5c74.esm.js.map +1 -0
  10. package/dist/adapter-kit.cjs.js +2 -0
  11. package/dist/adapter-kit.cjs.js.map +1 -0
  12. package/dist/adapter-kit.d.ts +88 -0
  13. package/dist/adapter-kit.esm.js +2 -0
  14. package/dist/adapter-kit.esm.js.map +1 -0
  15. package/dist/build/auditCommand.d.ts +19 -0
  16. package/dist/build/buildCommand.d.ts +23 -0
  17. package/dist/build/cli.d.ts +2 -0
  18. package/dist/build/config.d.ts +58 -0
  19. package/dist/build/diff.d.ts +56 -0
  20. package/dist/build/diffCommand.d.ts +29 -0
  21. package/dist/build/emit.d.ts +20 -0
  22. package/dist/build/emitTheme.d.ts +39 -0
  23. package/dist/build/guide.d.ts +34 -0
  24. package/dist/build/importCommand.d.ts +42 -0
  25. package/dist/build/index.d.ts +33 -0
  26. package/dist/build/init.d.ts +32 -0
  27. package/dist/build/paths.d.ts +42 -0
  28. package/dist/build/skillsCommand.d.ts +53 -0
  29. package/dist/build/tokensCommand.d.ts +17 -0
  30. package/dist/build/vendor.d.ts +39 -0
  31. package/dist/build.cjs.js +2 -0
  32. package/dist/build.cjs.js.map +1 -0
  33. package/dist/build.esm.js +2 -0
  34. package/dist/build.esm.js.map +1 -0
  35. package/dist/cli.js +8250 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/color-math.cjs.js +2 -0
  38. package/dist/color-math.cjs.js.map +1 -0
  39. package/dist/color-math.esm.js +2 -0
  40. package/dist/color-math.esm.js.map +1 -0
  41. package/dist/core/ThemeAdapter.d.ts +234 -0
  42. package/dist/core/createTheme.d.ts +96 -0
  43. package/dist/core/cssProperties.d.ts +8 -0
  44. package/dist/core/defineAdapter.d.ts +25 -0
  45. package/dist/core/derive/crossProperty.d.ts +31 -0
  46. package/dist/core/derive/index.d.ts +11 -0
  47. package/dist/core/derive/resolveTokens.d.ts +27 -0
  48. package/dist/core/errors.d.ts +24 -0
  49. package/dist/core/index.d.ts +16 -0
  50. package/dist/core/media/containers.d.ts +20 -0
  51. package/dist/core/media/defaults.d.ts +3 -0
  52. package/dist/core/media/descriptors.d.ts +12 -0
  53. package/dist/core/media/index.d.ts +11 -0
  54. package/dist/core/media/queries.d.ts +20 -0
  55. package/dist/core/model/buildModel.d.ts +116 -0
  56. package/dist/core/model/index.d.ts +14 -0
  57. package/dist/core/model/interpreted.d.ts +37 -0
  58. package/dist/core/model/mergeRuleSet.d.ts +68 -0
  59. package/dist/core/model/model.d.ts +260 -0
  60. package/dist/core/noopAdapter.d.ts +26 -0
  61. package/dist/core/normalize/index.d.ts +15 -0
  62. package/dist/core/normalize/properties.d.ts +20 -0
  63. package/dist/core/normalize/recipeResolver.d.ts +19 -0
  64. package/dist/core/normalize/recipes.d.ts +38 -0
  65. package/dist/core/normalize/responsive.d.ts +40 -0
  66. package/dist/core/normalize/types.d.ts +261 -0
  67. package/dist/core/rawTheme.d.ts +53 -0
  68. package/dist/core/ref.d.ts +13 -0
  69. package/dist/core/subsystem.d.ts +115 -0
  70. package/dist/core/types.d.ts +31 -0
  71. package/dist/core/units.d.ts +93 -0
  72. package/dist/dtcg/export.d.ts +48 -0
  73. package/dist/dtcg/import.d.ts +43 -0
  74. package/dist/dtcg/index.d.ts +21 -0
  75. package/dist/dtcg/parse.d.ts +6 -0
  76. package/dist/dtcg/types.d.ts +69 -0
  77. package/dist/dtcg.cjs.js +2 -0
  78. package/dist/dtcg.cjs.js.map +1 -0
  79. package/dist/dtcg.esm.js +2 -0
  80. package/dist/dtcg.esm.js.map +1 -0
  81. package/dist/index.cjs.js +2 -0
  82. package/dist/index.cjs.js.map +1 -0
  83. package/dist/index.d.ts +27 -0
  84. package/dist/index.esm.js +2 -0
  85. package/dist/index.esm.js.map +1 -0
  86. package/dist/subsystems/animation/index.d.ts +13 -0
  87. package/dist/subsystems/animation/recipes.d.ts +20 -0
  88. package/dist/subsystems/animation/types.d.ts +52 -0
  89. package/dist/subsystems/borders/index.d.ts +10 -0
  90. package/dist/subsystems/borders/recipes.d.ts +29 -0
  91. package/dist/subsystems/borders/types.d.ts +50 -0
  92. package/dist/subsystems/colors/audit.d.ts +67 -0
  93. package/dist/subsystems/colors/colorInput.d.ts +17 -0
  94. package/dist/subsystems/colors/index.d.ts +12 -0
  95. package/dist/subsystems/colors/keywords.d.ts +9 -0
  96. package/dist/subsystems/colors/normalize.d.ts +26 -0
  97. package/dist/subsystems/colors/recipes.d.ts +21 -0
  98. package/dist/subsystems/colors/types.d.ts +98 -0
  99. package/dist/subsystems/colors/utils.d.ts +104 -0
  100. package/dist/subsystems/components/index.d.ts +13 -0
  101. package/dist/subsystems/components/recipes.d.ts +22 -0
  102. package/dist/subsystems/components/types.d.ts +46 -0
  103. package/dist/subsystems/effects/index.d.ts +9 -0
  104. package/dist/subsystems/effects/recipes.d.ts +24 -0
  105. package/dist/subsystems/effects/types.d.ts +63 -0
  106. package/dist/subsystems/effects/utils.d.ts +57 -0
  107. package/dist/subsystems/globals/index.d.ts +21 -0
  108. package/dist/subsystems/globals/presets.d.ts +33 -0
  109. package/dist/subsystems/globals/types.d.ts +48 -0
  110. package/dist/subsystems/index.d.ts +21 -0
  111. package/dist/subsystems/layout/index.d.ts +14 -0
  112. package/dist/subsystems/layout/normalize.d.ts +35 -0
  113. package/dist/subsystems/layout/recipes.d.ts +16 -0
  114. package/dist/subsystems/layout/structural.d.ts +37 -0
  115. package/dist/subsystems/layout/types.d.ts +154 -0
  116. package/dist/subsystems/typography/index.d.ts +9 -0
  117. package/dist/subsystems/typography/normalize.d.ts +17 -0
  118. package/dist/subsystems/typography/recipes.d.ts +21 -0
  119. package/dist/subsystems/typography/types.d.ts +49 -0
  120. package/package.json +83 -0
  121. package/skills/adapter-scaffold/SKILL.md +85 -0
  122. package/skills/adapter-usage/SKILL.md +75 -0
  123. package/skills/build-config/SKILL.md +77 -0
  124. package/skills/colors/SKILL.md +134 -0
  125. package/skills/consuming-the-output/SKILL.md +62 -0
  126. package/skills/dtcg-import/SKILL.md +76 -0
  127. package/skills/layout/SKILL.md +65 -0
  128. package/skills/overrides-and-child-themes/SKILL.md +70 -0
  129. package/skills/recipes-and-composition/SKILL.md +110 -0
  130. package/skills/theme-authoring/SKILL.md +107 -0
  131. package/skills/theme-foundations/SKILL.md +127 -0
  132. package/skills/troubleshooting/SKILL.md +97 -0
  133. package/skills/typography/SKILL.md +66 -0
  134. package/skills/visual-effects/SKILL.md +96 -0
  135. 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.