@motion-proto/live-tokens 0.71.0 → 0.72.1

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 (70) hide show
  1. package/.claude/skills/live-tokens-adjust-geometry/SKILL.md +1 -1
  2. package/.claude/skills/live-tokens-build-page/SKILL.md +3 -3
  3. package/.claude/skills/live-tokens-check-compliance/SKILL.md +18 -40
  4. package/.claude/skills/live-tokens-create-component/SKILL.md +40 -98
  5. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +99 -0
  6. package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +9 -3
  7. package/.claude/skills/live-tokens-create-component/references/token-naming.md +6 -0
  8. package/.claude/skills/live-tokens-fix-findings/SKILL.md +33 -80
  9. package/.claude/skills/live-tokens-generate-theme/SKILL.md +2 -2
  10. package/.claude/skills/live-tokens-pick-component/SKILL.md +3 -3
  11. package/CHANGELOG.md +109 -0
  12. package/README.md +1 -1
  13. package/bin/check-component.mjs +2 -11
  14. package/bin/check-page.mjs +3 -2
  15. package/bin/cli.mjs +2 -1
  16. package/bin/lib/catalogue.mjs +6 -2
  17. package/bin/lib/findings.mjs +17 -0
  18. package/bin/lib/report.mjs +1 -1
  19. package/bin/lib/tokenVocabulary.mjs +20 -0
  20. package/dist-plugin/adjust/index.cjs +33 -6
  21. package/dist-plugin/adjust/index.d.cts +2 -2
  22. package/dist-plugin/adjust/index.d.ts +2 -2
  23. package/dist-plugin/adjust/index.js +7 -6
  24. package/dist-plugin/{chunk-2YNERPXY.js → chunk-RIXO2E55.js} +1 -1
  25. package/dist-plugin/{chunk-RVE3MNKM.js → chunk-W6Y4BWFB.js} +3 -1
  26. package/dist-plugin/{chunk-GPIBU44G.js → chunk-YLCOIGQC.js} +25 -1
  27. package/dist-plugin/{dataPaths-bJTCEO4H.d.ts → dataPaths-BhWzd5cL.d.cts} +4 -0
  28. package/dist-plugin/{dataPaths-bJTCEO4H.d.cts → dataPaths-BhWzd5cL.d.ts} +4 -0
  29. package/dist-plugin/fontPairing/index.cjs +3 -1
  30. package/dist-plugin/fontPairing/index.d.cts +1 -1
  31. package/dist-plugin/fontPairing/index.d.ts +1 -1
  32. package/dist-plugin/fontPairing/index.js +1 -1
  33. package/dist-plugin/generateColorsAndType/index.cjs +28 -2
  34. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  35. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  36. package/dist-plugin/generateColorsAndType/index.js +2 -2
  37. package/dist-plugin/index.cjs +28 -2
  38. package/dist-plugin/index.d.cts +1 -1
  39. package/dist-plugin/index.d.ts +1 -1
  40. package/dist-plugin/index.js +3 -3
  41. package/dist-plugin/migrateData/index.cjs +28 -2
  42. package/dist-plugin/migrateData/index.d.cts +1 -1
  43. package/dist-plugin/migrateData/index.d.ts +1 -1
  44. package/dist-plugin/migrateData/index.js +3 -3
  45. package/dist-plugin/tokensCssMigrations/index.cjs +3 -1
  46. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  47. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  48. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  49. package/package.json +5 -1
  50. package/src/editor/component-editor/SectionDividerEditor.svelte +1 -11
  51. package/src/editor/component-editor/contract.ts +175 -0
  52. package/src/editor/component-editor/scaffolding/StateBlock.svelte +0 -8
  53. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +5 -0
  54. package/src/editor/component-editor/scaffolding/TypeEditor.svelte +0 -21
  55. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +2 -2
  56. package/src/editor/component-editor/scaffolding/types.ts +1 -7
  57. package/src/editor/core/components/aliasKinds.ts +6 -2
  58. package/src/editor/core/themes/migrations/2026-09-02-sectiondivider-drop-title-outline.ts +39 -0
  59. package/src/editor/core/themes/migrations/index.ts +2 -0
  60. package/src/live-tokens/data/themes/autumn.json +1 -7
  61. package/src/live-tokens/data/themes/halloween.json +1 -7
  62. package/src/live-tokens/data/themes/midnight-study.json +1 -7
  63. package/src/live-tokens/data/themes/ocean.json +1 -7
  64. package/src/live-tokens/data/themes/royal-velvet.json +1 -7
  65. package/src/live-tokens/data/themes/sketchy.json +1 -7
  66. package/src/live-tokens/data/themes/spring-meadow.json +1 -7
  67. package/src/live-tokens/data/themes/sunset.json +1 -7
  68. package/src/system/components/SectionDivider.svelte +3 -143
  69. package/src/system/components/FloatingTokenTags.css +0 -284
  70. package/src/system/components/FloatingTokenTags.svelte +0 -570
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: live-tokens-adjust-geometry
3
- description: Adjust corner radius, padding, gap, and border width across live-tokens components by moving each token alias along the shipped scales. Use when the user asks for pill or capsule buttons; rounded, rounder, sharp, sharper, square, softer, or harder corners; thicker or thinner borders; or density space it out, tighter, denser, more compact, airier, more breathing room. Also invoked by live-tokens-generate-theme for the geometry half of a whole look. Changes shape and space aliases per component, never color, fonts, or tokens.css. Not for editing a single token (use the editor) or for color (see live-tokens-generate-theme).
3
+ description: Adjust corner radius, padding, gap, and border width across live-tokens components by moving each token alias along the shipped scales. Use when the user asks for pill or capsule buttons; rounded, rounder, sharp, sharper, square, softer, or harder corners; thicker or thinner borders; or density: space it out, tighter, denser, more compact, airier, more breathing room. Also invoked by live-tokens-generate-theme for the geometry half of a whole look. Changes shape and space aliases per component, never color, fonts, or tokens.css. Not for editing a single token (use the editor) or for color (see live-tokens-generate-theme).
4
4
  ---
5
5
 
6
6
  # Adjusting geometry
@@ -16,7 +16,7 @@ Text inside a `Card` or a `CollapsibleSection` is typed by that container, not b
16
16
 
17
17
  ## Layout
18
18
 
19
- Pages sit inside the column grid via `--columns-count`, `--columns-gutter`, `--columns-max-width`. Toggle `ColumnsOverlay` (Cmd+G in dev) to visualise it while placing content.
19
+ Pages sit inside the column grid via `--columns-count`, `--columns-gutter`, `--columns-max-width`. The columns button in the overlay's header (the vertical-lines icon) draws the grid over the page while you place content.
20
20
 
21
21
  To place children at specific page-column positions, span the parent grid (`grid-column: 1 / -1`), redeclare `repeat(var(--columns-count), 1fr)` with `--columns-gutter`, then refer to children by real page-column numbers. Never fabricate a local `repeat(N, 1fr)` with a hardcoded count: the widths drift from the page grid and the numbers stop matching `ColumnsOverlay`.
22
22
 
@@ -61,8 +61,8 @@ npx live-tokens check-page src/pages/YourPage.svelte
61
61
  # or: npx @motion-proto/live-tokens check-page (every page under src/)
62
62
  ```
63
63
 
64
- It fails on a component outside the catalogue, a prop a component does not declare, a variant or size outside the prop's union, a deep import, a `var()` that resolves to nothing, a colour literal in any notation, a route under `/live-tokens/*`, and `site.css` imported from `main.ts`. It warns on a px or rem literal in spacing, stroke, radius, or shadow, a hardcoded column count of four or more, an absolute type value, and a route entry with no `source`. Inline `style=` attributes and `style:` directives are read the same way as the `<style>` block; a `var()` fallback is not the page's value and is never a finding.
64
+ It fails on a component outside the catalogue, a prop or value the component does not declare, a deep import, a `var()` that resolves to nothing, a colour literal in any notation, a route under `/live-tokens/*`, and `site.css` imported from `main.ts`. It warns on a px or rem literal in the geometry the theme owns, a hardcoded page-column count, a raw type axis, and a route entry with no `source`. Inline `style=` attributes and `style:` directives are read the same way as the `<style>` block; a `var()` fallback is never a finding. The recipe for each rule is in **live-tokens-fix-findings**.
65
65
 
66
66
  Warnings do not fail the run. `--strict` makes them fail, which is the setting to use when the page is meant to be fully tokenized. `--json` prints findings with a stable `rule` id, so you can work through one rule at a time and re-run. `--off=<rule>` silences a rule for a run; `"checks": { "rules": { ... } }` in `live-tokens.config.json` sets it for the project. A project scaffolded by `create` runs the checker, with `check-component`, as `npm run check:design` before every `vite build`, so the page has to pass before it can ship.
67
67
 
68
- Then in dev: change a colour in `/live-tokens/editor` and confirm your page repaints (proves token usage). The overlay's "Page Source" button on the new route opens the page in VS Code (proves the route's `source`). `ColumnsOverlay` (Cmd+G) shows content sitting inside `--columns-max-width`.
68
+ Then in dev: change a colour in `/live-tokens/editor` and confirm your page repaints (proves token usage). The overlay's "Page Source" button on the new route opens the page in VS Code (proves the route's `source`). The columns overlay shows content sitting inside `--columns-max-width`.
@@ -5,23 +5,20 @@ description: Check an existing @motion-proto/live-tokens project against its des
5
5
 
6
6
  # Checking a project against its design system
7
7
 
8
- The answer to "check this project" is a report, and every fact in it comes
9
- from one command. This skill runs it, reads it, and says what the facts mean
10
- and what fixing them would involve. It edits nothing. When the user wants the
11
- changes made, that is **live-tokens-fix-findings**, and the report is what it
12
- starts from.
8
+ The answer to "check this project" is a report. Every fact in it comes from one command; the reading of those facts, and what fixing them would cost, is yours. This skill edits nothing. When the user wants the changes made, that is **live-tokens-fix-findings**, and this report is what it starts from.
13
9
 
14
- ## Run the report
10
+ ## Workflow
15
11
 
16
- ```sh
17
- npx live-tokens report --json
18
- ```
12
+ 1. Run `npx live-tokens report --json`. It always exits 0: it is a reading, not a gate. Unknown command means the installed package predates it; upgrade `@motion-proto/live-tokens` first.
13
+ 2. Read each section against the table below. For every rule with findings, say in a line what the rule holds and whether the fix is mechanical or a judgement.
14
+ 3. Where a finding looks deliberate, name the config entry that would record the decision, and leave the decision to the user.
15
+ 4. Report in the order under Summary, each line carrying its count, and end by handing the list to **live-tokens-fix-findings**. Do not start applying fixes here, even one-liners, because the user asked how things stand.
19
16
 
20
- It always exits 0: it is a reading, not a gate. Unknown command means the
21
- installed package predates it; upgrade `@motion-proto/live-tokens` first. The
22
- sections, in the order the report gives them:
17
+ `npx live-tokens components <id>` and `npx live-tokens tokens --family <name>` (both take `--json`) answer any question the report raises about one component or one scale.
23
18
 
24
- | Section | Fact | What it means when it is not clean |
19
+ ## The report's sections
20
+
21
+ | Section | Fact | When it is not clean |
25
22
  | --- | --- | --- |
26
23
  | `migrations` | Whether `tokens.css` is behind the installed package | A stale file shows up downstream as unknown tokens. This is the first fix, and it is one command: `npx live-tokens migrate --check`, then `--write` (`--tokens <path>` for a tokens.css in an unusual place). |
27
24
  | `components[].unread` | Tokens a component declares that nothing in its file reads | An editor row that edits nothing. Each is a token to wire into the CSS or to remove. |
@@ -32,41 +29,22 @@ sections, in the order the report gives them:
32
29
  | `usage.customUnregistered`, `usage.customUnused` | The project's own components that are unregistered or unused | Dead or half-wired work. |
33
30
  | `findings.pages`, `findings.components` | Both checkers' findings by rule, under the project's severities and again under `--strict` | The errors are what fails the build today; the strict count is what a fully tokenized project would fail. |
34
31
 
35
- `npx live-tokens components <id>` and `npx live-tokens tokens --family <name>`
36
- (both take `--json`) answer any question the report raises about one
37
- component or one scale.
38
-
39
- ## Read it
40
-
41
- Facts are the report's; the reading is yours. For each rule with findings, say
42
- in a line what the rule holds and which of two kinds the fix is:
32
+ ## Mechanical or judgement
43
33
 
44
- - **Mechanical**: a spacing literal to its nearest `--space-*` step, a stroke
45
- to `--border-width-*`, a hardcoded column count to `var(--columns-count)`,
46
- `site.css` moved out of `main.ts`, a route given its `source`. Name any
47
- visible shift, such as a `14px` margin becoming `16px`.
48
- - **Judgement**: a colour literal mapped by the role it plays rather than its
49
- hue, a raw type axis set from a text style, a prop the component does not
50
- declare mapped or dropped. Say what the choice is, not what you would pick.
34
+ - **Mechanical**: a spacing literal to its nearest `--space-*` step, a stroke to `--border-width-*`, a hardcoded column count to `var(--columns-count)`, `site.css` moved out of `main.ts`, a route given its `source`. Name any visible shift, such as a `14px` margin becoming `16px`.
35
+ - **Judgement**: a colour literal mapped by the role it plays rather than its hue, a raw type axis set from a text style, a prop the component does not declare mapped or dropped. Say what the choice is, not what you would pick.
51
36
 
52
- Where a finding looks deliberate, a translucent overlay on an app shell or a
53
- layout size the project owns, say so and name the config entry that would
54
- record the decision: `"checks": { "rules": { "<rule>": "warn" } }` in
55
- `live-tokens.config.json`. Recording it is the user's call, not yours.
37
+ ## Deliberate findings
56
38
 
57
- ## Report
39
+ A translucent overlay on an app shell, or a layout size the project owns, may be a decision rather than a miss. Say so and name the entry that would record it: `"checks": { "rules": { "<rule>": "warn" } }` in `live-tokens.config.json`. Where a whole file is not a themed surface at all, hand-tuned artwork or vendored CSS, the entry is `"checks": { "exclude": ["src/art/hero.css"] }`: a project-relative path, a directory covering what is under it, and naming the file on the command line still checks it. Prefer the narrower one: an exclusion drops one file, a severity change drops a rule everywhere. Recording either is the user's call, not yours.
58
40
 
59
- In this order, each line carrying its count:
41
+ ## Summary
60
42
 
61
43
  1. Migrations pending, and the one command that clears them.
62
44
  2. What fails the build now: errors by rule, with the files.
63
45
  3. What `--strict` would add: warnings by rule.
64
46
  4. Components: unread tokens, unregistered, undescribed.
65
47
  5. Usage: what each page renders, and what is used nowhere.
66
- 6. Recommended fixes, in the order **live-tokens-fix-findings** would take
67
- them: migrations, then the largest group of errors, then the rest, then
68
- warnings. Mark each as mechanical or judgement.
48
+ 6. Recommended fixes, in the order **live-tokens-fix-findings** would take them: migrations, then the largest group of errors, then the rest, then warnings. Mark each as mechanical or judgement.
69
49
 
70
- End with the hand-off: "Run live-tokens-fix-findings to apply these", or the
71
- subset the user chooses. Do not start applying them here, even when the fix is
72
- one line, because the user asked how things stand.
50
+ End with the hand-off: "Run live-tokens-fix-findings to apply these", or the subset the user chooses.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: live-tokens-create-component
3
- description: Author a brand-new editable component for a @motion-proto/live-tokens project when nothing in the shipped catalogue fits runtime and editor Svelte files, registration, naming, state model, and verification. Use when the user asks to author, create, or build a new tokenized component; make an existing Svelte component editable in the live-tokens editor; add a component to the catalogue; register a custom component with the editor; or build a [Thing] component that does not exist in the shipped set. Not for placing an existing shipped component on a page (see live-tokens-build-page); read live-tokens-pick-component first to confirm nothing in the catalogue fits.
3
+ description: Author a brand-new editable component for a @motion-proto/live-tokens project when nothing in the shipped catalogue fits: runtime and editor Svelte files, registration, naming, state model, and verification. Use when the user asks to author, create, or build a new tokenized component; make an existing Svelte component editable in the live-tokens editor; add a component to the catalogue; register a custom component with the editor; or build a [Thing] component that does not exist in the shipped set. Not for placing an existing shipped component on a page (see live-tokens-build-page); read live-tokens-pick-component first to confirm nothing in the catalogue fits.
4
4
  ---
5
5
 
6
6
  # Authoring a component for a live-tokens project
@@ -9,22 +9,23 @@ The end state is a runtime Svelte file, an editor Svelte file, one registration,
9
9
 
10
10
  ## Worked examples ship inside the package
11
11
 
12
- For pattern reference, read any shipped component's source directly from the consumer's `node_modules`:
12
+ Read a shipped component's source from the consumer's `node_modules` rather than from memory, because the files are the contract and this skill is not:
13
13
 
14
14
  - Runtime files: `node_modules/@motion-proto/live-tokens/src/system/components/<Name>.svelte`.
15
15
  - Simplest reads (no state, no linked-block): `Card` (single variant with parts), `Badge` and `Callout` (multi-variant).
16
16
  - Multi-state (hover, disabled, focus): `Button`, `Input`.
17
17
  - Multi-part (overlay / header / body / footer): `Dialog`.
18
18
  - Multi-variant with linked siblings (`canBeLinked` + `groupKey`): `SegmentedControl`, `TabBar`. Composes another shipped component: `CodeSnippet`.
19
- - Editor files: `node_modules/@motion-proto/live-tokens/src/editor/component-editor/<Name>Editor.svelte`.
19
+ - Every rule below in the fewest lines: `Toggle`. Component states name themselves in the token (`--toggle-on-*`, `--toggle-disabled-*`), interaction states layer on top (`--toggle-hover-*`, `--toggle-on-hover-*`), disabled is terminal (no `--toggle-disabled-hover-*`), and each `:hover` selector has a `.force-hover` sibling so the editor's preview can paint hover tokens without a pointer.
20
+ - Editor files: `node_modules/@motion-proto/live-tokens/src/editor/component-editor/<Name>Editor.svelte`. `ToggleEditor` has no `groupKey` and no `canBeLinked`; for components that share base properties across variants, read `references/linked-siblings.md`.
20
21
 
21
- **File-location note.** Shipped editors live in `src/editor/component-editor/` because they're library-internal. For *your* component, **co-locate** both files in `src/system/components/`. Read the shipped files for pattern, ignore their location.
22
+ Shipped editors live in `src/editor/component-editor/` because they are library-internal. For *your* component, co-locate both files in `src/system/components/`. Read the shipped files for pattern, ignore their location.
22
23
 
23
24
  ## The recipe
24
25
 
25
- 1. **Runtime file** `src/system/components/MyWidget.svelte`. Declare every editable slot as a CSS custom property inside `:global(:root)`, defaulting to a theme token (never a raw value). The plugin parses `:global(:root)` to seed `component-configs/<id>/default.json`; variables declared anywhere else can't be edited.
26
- 2. **Editor file** `src/system/components/MyWidgetEditor.svelte`. In a `<script module>` block, declare `const component = 'mywidget'`, build a `states: Record<string, Token[]>` for each VariantGroup, and export the flat union as `allTokens: Token[]`. Components with linked siblings also build a `linkableContexts: Map<string, string>` (read `references/linked-siblings.md`). Components with structural/display controls that aren't token values (alignment, element visibility, layout position) also export an `intrinsics: IntrinsicSpec[]` (read `references/intrinsics.md`). In the runtime `<script>` block, mount `ComponentEditorBase` with one `VariantGroup` per variant.
27
- 3. **Register** pass the component to `bootLiveTokens` in `src/main.ts`. This is the standard boot the scaffold generates and the README documents; `bootLiveTokens` calls `registerComponent` internally at the right point — after its editor init hooks (`cssVarSync.init`, `editorStore.init`), before it seeds configs and mounts the app:
26
+ 1. **Runtime file**, `src/system/components/MyWidget.svelte`. Declare every editable slot as a CSS custom property inside `:global(:root)`, defaulting to a theme token (never a raw value). The plugin parses `:global(:root)` to seed `component-configs/<id>/default.json`; variables declared anywhere else cannot be edited.
27
+ 2. **Editor file**, `src/system/components/MyWidgetEditor.svelte`. In a `<script module>` block, declare `const component = 'mywidget'`, build a `states: Record<string, Token[]>` for each VariantGroup, and export the flat union as `allTokens: Token[]`. Components with linked siblings also build a `linkableContexts: Map<string, string>` (read `references/linked-siblings.md`). Components with structural or display controls that are not token values (alignment, element visibility, layout position) also export an `intrinsics: IntrinsicSpec[]` (read `references/intrinsics.md`). In the runtime `<script>` block, mount `ComponentEditorBase` with one `VariantGroup` per variant.
28
+ 3. **Register** by passing the component to `bootLiveTokens` in `src/main.ts`, the boot the scaffold generates:
28
29
  ```ts
29
30
  import { bootLiveTokens } from '@motion-proto/live-tokens';
30
31
  import App from './App.svelte';
@@ -41,41 +42,16 @@ For pattern reference, read any shipped component's source directly from the con
41
42
  }],
42
43
  });
43
44
  ```
44
- The schema side-effect happens inside `registerComponent` (which `bootLiveTokens` calls for you), so you don't call `registerComponentSchema` separately. **Do not place a standalone `registerComponent(...)` *before* `bootLiveTokens`** that registers before the editor's init hooks run, which is the wrong window and can leave editor changes disconnected from the live page. Only call `registerComponent` directly if your app mounts manually (no `bootLiveTokens`), in which case call it before `mount(App, ...)`.
45
- 4. **Say what it is for.** The runtime file's leading HTML comment is the
46
- component's description. `npx live-tokens components` prints it beside the
47
- id with the variants and props read from `interface Props` (`--json` for
48
- data); that is how **live-tokens-pick-component** weighs a project's own
49
- component against the shipped set, so no skill file is edited and nothing
50
- is lost when `setup-claude` refreshes the skills. Name the job it does and
51
- what it is not for. A directory other than `src/system/components` goes in
52
- `"componentDirs"` in `live-tokens.config.json`; a first-party component is
53
- also added to the picker's **Catalogue** line, which `check:skills` holds.
54
- 5. **Join the sketch layer** — the effect draws a fixed set of parts, so a new
55
- component stays crisp while the page around it goes hand-drawn until it opts
56
- in. A consumer component carries one of four reserved classes on its root and
57
- names the five `--sketch-*` values it is drawn with; a first-party component
58
- adds a `PartSpec` row instead. The layer also takes `background`,
59
- `border-color`, `box-shadow`, `overflow`, `position` and both pseudo-elements
60
- away from the element it draws, which constrains where the class can go. Read
61
- `references/sketch-mode.md`.
62
- 6. **Gate on the checker.** Run it, fix every error, and run it again. Do not
63
- call the component done while it reports one:
45
+ `bootLiveTokens` calls `registerComponent` for you after its editor init hooks and before it seeds configs, so a standalone `registerComponent(...)` placed *before* `bootLiveTokens` lands in the wrong window and can leave editor changes disconnected from the live page. Call `registerComponent` directly only when the app mounts manually, and then before `mount(App, ...)`. Registering against a built-in id wins with a console warning; the right call is a unique id.
46
+ 4. **Say what it is for.** The runtime file's leading HTML comment is the component's description. `npx live-tokens components` prints it beside the id with the variants and props read from `interface Props` (`--json` for data), which is how **live-tokens-pick-component** weighs a project's own component against the shipped set: no skill file is edited, and nothing is lost when `setup-claude` refreshes the skills. Name the job it does and what it is not for. A directory other than `src/system/components` goes in `"componentDirs"` in `live-tokens.config.json`. A first-party component is also added to the picker's **Catalogue** line, which `check:skills` holds.
47
+ 5. **Join the sketch layer.** The effect draws a fixed set of parts, so a new component stays crisp while the page around it goes hand-drawn until it opts in. A consumer component carries one of four reserved classes on its root and names the five `--sketch-*` values it is drawn with; a first-party component adds a `PartSpec` row instead. The layer also takes `background`, `border-color`, `box-shadow`, `overflow`, `position` and both pseudo-elements away from the element it draws, which constrains where the class can go. Read `references/sketch-mode.md`.
48
+ 6. **Gate on the checker.** Run it, fix every error, and run it again. Do not call the component done while it reports one:
64
49
  ```bash
65
50
  npx live-tokens check-component <id> --strict --json
66
51
  ```
67
- `--json` gives each finding a stable `rule` id and a line number, so work one
68
- rule at a time and re-run rather than guessing. `--strict` fails on warnings
69
- too, which is the right setting for a new component: every warning it raises
70
- is a naming or token decision that is cheaper to make now than to migrate
71
- later. Exit code 0 is the gate. With no id it checks every component under
72
- `src/system/components`; a project scaffolded by `create` runs that as
73
- `npm run check:design` before every `vite build`.
74
-
75
- If it rejects a suffix, do not invent a new name for the role. Find a shipped
76
- component that paints the same thing and use the name it uses: the catalogue
77
- is the worked reference, and `bin/check-component.test.ts` holds all 26 of
78
- them to this same contract.
52
+ `--json` gives each finding a stable `rule` id and a line number, so work one rule at a time. `--strict` fails on warnings too, the right setting for a new component: every warning is a naming or token decision that is cheaper to make now than to migrate later. Exit code 0 is the gate. With no id it checks every component under `src/system/components`; a project scaffolded by `create` runs that as `npm run check:design` before every `vite build`.
53
+
54
+ If it rejects a suffix, do not invent a new name for the role. Find a shipped component that paints the same thing and use the name it uses: every shipped component passes this same check, so the catalogue is the worked reference.
79
55
  7. **Verify** with the checklist at the bottom of this file, then place the component on a page with **live-tokens-build-page**.
80
56
 
81
57
  ## Token discipline
@@ -86,11 +62,11 @@ For pattern reference, read any shipped component's source directly from the con
86
62
  --<componentId>-<part|variant>[-<state>][-<element>]-<property>
87
63
  ```
88
64
 
89
- - `componentId` the literal id passed to `registerComponent()`. Lowercase, no dashes, no abbreviations (`segmentedcontrol` not `sc`). The file id matches: `MyWidget.svelte` id `mywidget`.
90
- - `part` or `variant` the sub-region (`bar`, `option`, `track`, `header`, `body`, `footer`, `overlay`, `value`, `label`), or, on a component whose variants differ in more than one property, the variant name: `--badge-accent-surface`, `--callout-danger-border`. A component with both stacks them outer to inner, so the bar in its small size is `--segmentedcontrol-bar-small-padding`.
91
- - `state` (optional) interaction or component state (`hover`, `disabled`, `selected`, `focus`). **Always before the property.**
92
- - `element` (optional) sub-element inside the part (`dot`, `icon`, `label`, `text`).
93
- - `property` theme role or CSS property. Always last.
65
+ - `componentId`: the literal id passed to `registerComponent()`. Lowercase, no dashes, no abbreviations (`segmentedcontrol` not `sc`). The file id matches: `MyWidget.svelte` is id `mywidget`.
66
+ - `part` or `variant`: the sub-region (`bar`, `option`, `track`, `header`, `body`, `footer`, `overlay`, `value`, `label`), or, on a component whose variants differ in more than one property, the variant name: `--badge-accent-surface`, `--callout-danger-border`. A component with both stacks them outer to inner, so the bar in its small size is `--segmentedcontrol-bar-small-padding`.
67
+ - `state` (optional): interaction or component state (`hover`, `disabled`, `selected`, `focus`). **Always before the property.**
68
+ - `element` (optional): sub-element inside the part (`dot`, `icon`, `label`, `text`).
69
+ - `property`: theme role or CSS property. Always last.
94
70
 
95
71
  ### Suffix vocabulary
96
72
 
@@ -121,12 +97,12 @@ control behind it.
121
97
  ### Rules that bite
122
98
 
123
99
  - **Fixed overlays must portal to `<body>`.** Any `position: fixed` layer is trapped by a transformed or `contain`ed ancestor, which real pages and the editor's preview pane both have. `check:overlay-portal` fails the build without it. Read `references/fixed-overlays.md` before authoring a modal, lightbox, or backdrop.
124
- - **State before property.** `--mywidget-button-hover-surface` `--mywidget-button-surface-hover` ✗ (breaks sibling matching). Disabled is terminal in the name too: `-disabled-hover-` and `-selected-disabled-` describe states that never paint, and `check-component` rejects both.
125
- - **Every default resolves to a theme token.** A component token names a semantic property; its default is the theme token that property reads. That is what makes the component repaint when the theme changes. `var(--surface-primary)` ✓, `#6a4ce8` ✗, `white` ✗, and `var(--surface-imaginary)` ✗: `check-component` fails on a colour literal in any notation, on a `var()` naming a token that does not exist, and on a default with no token behind it at all, so a raw `16rem` fails too. Composing tokens counts and is common: `color-mix(in srgb, var(--surface-neutral-lower) 70%, transparent)`, or `calc(var(--space-64) * 4)` for a width the spacing scale does not reach. The one value allowed without a token is a structural keyword (`contain`, `start`, `none`), and only when the editor declares it in `intrinsics`.
126
- - **No abbreviations.** `bg` `surface`; `fg` `text`; component ids are never abbreviated.
100
+ - **State before property.** `--mywidget-button-hover-surface` passes; `--mywidget-button-surface-hover` breaks sibling matching. Disabled is terminal in the name too: `-disabled-hover-` and `-selected-disabled-` describe states that never paint, and `check-component` rejects both.
101
+ - **Every default resolves to a theme token.** A component token names a semantic property; its default is the theme token that property reads, which is what makes the component repaint when the theme changes. `var(--surface-primary)` passes; `#6a4ce8`, `white`, `var(--surface-imaginary)`, and a bare `16rem` all fail, because `check-component` rejects a colour literal in any notation, a `var()` naming a token that does not exist, and a default with no token behind it. Composing tokens counts and is common: `color-mix(in srgb, var(--surface-neutral-lower) 70%, transparent)`, or `calc(var(--space-64) * 4)` for a width the spacing scale does not reach. The one value allowed without a token is a structural keyword (`contain`, `start`, `none`), and only when the editor declares it in `intrinsics`.
102
+ - **No abbreviations.** `bg` is `surface`; `fg` is `text`; component ids are never abbreviated.
127
103
  - **Text aliases.** Neutral scale is `--text-primary` / `--text-secondary` / `--text-tertiary` / `--text-muted` / `--text-disabled`. Family-tinted is `--text-primary-color`, `--text-accent`, `--text-success`. There is no `--text-neutral`.
128
- - **Typography `groupKey` on multi-slot components must include the slot prefix.** `groupKey: 'value-font-family'` and `groupKey: 'label-font-family'` bare `groupKey: 'font-family'` silently merges them into one link tree ✗. Single-slot components can use a bare typography `groupKey`; add the slot prefix the moment a second slot appears. The same trap applies to type-group **colors** (two slots ending in `-text` collapsing to one `text` key). Let the helpers handle both, below.
129
- - **Let the type-group helpers derive slot-scoped keys; never rely on the bare last-dash default.** When you build typography tokens with `buildTypeGroupColorTokens` / `buildTypeGroupTokens` / `buildTypeGroupFontTokens`, **pass `{ component, variants }`** so each slot gets a distinct, structural `groupKey`:
104
+ - **Typography `groupKey` on multi-slot components must include the slot prefix.** `groupKey: 'value-font-family'` and `groupKey: 'label-font-family'` stay distinct; a bare `groupKey: 'font-family'` silently merges the slots into one link tree. Single-slot components can use a bare typography `groupKey`; add the slot prefix the moment a second slot appears. The same trap applies to type-group colours (two slots ending in `-text` collapsing to one `text` key).
105
+ - **Let the type-group helpers derive slot-scoped keys.** When you build typography tokens with `buildTypeGroupColorTokens` / `buildTypeGroupTokens` / `buildTypeGroupFontTokens`, pass `{ component, variants }` so each slot gets a distinct, structural `groupKey`:
130
106
 
131
107
  ```ts
132
108
  // variants = the variant/state segment strings as they appear in the variable name
@@ -135,24 +111,23 @@ control behind it.
135
111
  ...buildTypeGroupFontTokens(typeGroups, { component, variants: [...VARIANTS] }),
136
112
  ```
137
113
 
138
- The helper strips the `--<component>-` prefix and those segments, keeping the rest: `--mywidget-header-default-text` `header-text`, `--mywidget-header-default-text-font-family` `header-text-font-family`. Two parts ending in the same word stay distinct; one slot across variants collapses to one key. To override a single derived key, set `colorGroupKey` on that type-group config it wins and is never recomputed, so your fix survives. There is no name-based fallback: a bare `buildTypeGroupColorTokens` call emits un-grouped (solo) colors rather than guessing, and a bare *font* helper across multiple slots is a `check-component` warning (its default `font-family`/… keys would merge the slots' fonts).
139
-
114
+ The helper strips the `--<component>-` prefix and those segments, keeping the rest: `--mywidget-header-default-text` becomes `header-text`, and `--mywidget-header-default-text-font-family` becomes `header-text-font-family`. Two parts ending in the same word stay distinct; one slot across variants collapses to one key. To override a single derived key, set `colorGroupKey` on that type-group config; it wins and is never recomputed. There is no name-based fallback: a bare `buildTypeGroupColorTokens` call emits un-grouped (solo) colours rather than guessing, and a bare *font* helper across multiple slots is a `check-component` warning, because its default keys would merge the slots' fonts.
140
115
 
141
116
  ## State model
142
117
 
143
- Components *can* have two state axes. Many don't: container and messaging components (Card, Badge, Callout, CollapsibleSection) have only variants, no hover/disabled. Skip the rest of this section for those.
118
+ Components *can* have two state axes. Many do not: container and messaging components (Card, Badge, Callout, CollapsibleSection) have only variants, no hover or disabled. Skip the rest of this section for those.
144
119
 
145
- When a component does have states, don't mix the two axes:
120
+ When a component does have states, keep the two axes apart:
146
121
 
147
- - **Component states** mutually exclusive top-level fieldsets: `default`, `selected`, `disabled` (names vary by component). One fieldset per component state.
148
- - **Interaction states** a select *inside* each component-state fieldset: `default`, `hover`. Add `focus`/`active` later if needed.
122
+ - **Component states** are mutually exclusive top-level fieldsets: `default`, `selected`, `disabled` (names vary by component). One fieldset per component state.
123
+ - **Interaction states** are a select *inside* each component-state fieldset: `default`, `hover`. Add `focus` or `active` later if needed.
149
124
 
150
125
  Rules:
151
126
 
152
- - **Disabled is terminal.** A disabled component can't be hovered or focused. The `disabled` fieldset is flat no interaction selector.
153
- - **`selected-disabled` is impossible.** Don't author tokens or fieldsets for it.
154
- - **Parts states.** Dialog's `overlay | header | body | footer` are *parts* (all present simultaneously), not states. The VariantGroup tab strip defaults its label to "Element" (neutral). If you label tabs anywhere, use **part** for structure and **state** for runtime conditions. Never call a footer a state.
155
- - **Don't call interaction states "option states" or "selected states"** in the UI. `selected` is a *component* state.
127
+ - **Disabled is terminal.** A disabled component cannot be hovered or focused. The `disabled` fieldset is flat, with no interaction selector.
128
+ - **`selected-disabled` is impossible.** Do not author tokens or fieldsets for it.
129
+ - **Parts are not states.** Dialog's `overlay | header | body | footer` are *parts* (all present at once), not states. The VariantGroup tab strip defaults its label to "Element" (neutral). If you label tabs anywhere, use **part** for structure and **state** for runtime conditions. Never call a footer a state.
130
+ - **Do not call interaction states "option states" or "selected states"** in the UI. `selected` is a *component* state.
156
131
 
157
132
  Token naming consequence:
158
133
 
@@ -161,14 +136,14 @@ Token naming consequence:
161
136
  --mywidget-option-disabled-surface ✗ implies disabled is an interaction state
162
137
  --mywidget-option-hover-surface ✓ default-component-state, hover-interaction
163
138
  --mywidget-selected-hover-surface ✓ selected-component-state, hover-interaction
164
- --mywidget-selected-disabled-text ✗ selected-disabled doesn't exist
139
+ --mywidget-selected-disabled-text ✗ selected-disabled does not exist
165
140
  ```
166
141
 
167
142
  ## User-facing copy
168
143
 
169
- Strings you author for the editor UI use periods and commas, never em-dashes. Em-dashes read as an AI tell. This applies to `title=` and `description=` on `ComponentEditorBase`, token row labels, info popovers, and any text inside `previewActions` / `canvasToolbarExtras` snippets. Code comments are unaffected.
144
+ Strings you author for the editor UI use periods and commas, never em-dashes, which read as an AI tell. This applies to `title=` and `description=` on `ComponentEditorBase`, token row labels, info popovers, and any text inside `previewActions` / `canvasToolbarExtras` snippets. Code comments are unaffected.
170
145
 
171
- If you add custom chrome inside an editor snippet (rare `ComponentEditorBase` and `VariantGroup` carry the standard chrome), keep it greyscale (no accent colors) and reference heading sizes via `--ui-font-size-md` / `-lg` / `-2xl` rather than pixel literals.
146
+ Custom chrome inside an editor snippet is rare, since `ComponentEditorBase` and `VariantGroup` carry the standard chrome. Where you add some, keep it greyscale (no accent colours) and reference heading sizes via `--ui-font-size-md` / `-lg` / `-2xl` rather than pixel literals.
172
147
 
173
148
  ## Public imports only
174
149
 
@@ -187,26 +162,6 @@ That covers everything the worked examples use. Additional primitives (`LinkedBl
187
162
 
188
163
  **Never deep-import `node_modules/@motion-proto/live-tokens/src/...`.** Reading those files for pattern reference is fine; importing them at runtime is not. If you need something not exported, file an issue rather than reaching in.
189
164
 
190
- ## Worked example: the shipped Toggle
191
-
192
- Toggle exercises every rule above in the fewest lines. Read both files from the package rather than from memory, because they are the contract and this skill is not:
193
-
194
- - `node_modules/@motion-proto/live-tokens/src/system/components/Toggle.svelte`
195
- - `node_modules/@motion-proto/live-tokens/src/editor/component-editor/ToggleEditor.svelte`
196
-
197
- What to notice in the runtime file:
198
-
199
- - Component states (`on`, `disabled`) name themselves in the token: `--toggle-on-*`, `--toggle-disabled-*`.
200
- - Interaction states layer on top: `--toggle-hover-*` for default+hover, `--toggle-on-hover-*` for on+hover.
201
- - Disabled is terminal: no `--toggle-disabled-hover-*`, no `--toggle-on-disabled-*`.
202
- - The `force-hover` class pairs with the editor's preview hook so hover tokens paint without a real pointer. Each `:hover` selector has a matching `.force-hover` sibling.
203
-
204
- What to notice in the editor file:
205
-
206
- - No `groupKey`, no `canBeLinked`: Toggle has no linked siblings. For components that share base properties across variants, read `references/linked-siblings.md`.
207
-
208
- For your own component, copy the pattern and substitute your id. Registering against a built-in id wins with a console warning, but the right call is a unique id.
209
-
210
165
  ## Extensions
211
166
 
212
167
  Read the sketch reference for every component; the other two only when they apply.
@@ -217,24 +172,11 @@ Read the sketch reference for every component; the other two only when they appl
217
172
 
218
173
  ## Verification checklist
219
174
 
220
- After saving, run the static validator first:
221
-
222
- ```bash
223
- npx live-tokens check-component <id>
224
- # or: npx @motion-proto/live-tokens check-component <id>
225
- ```
226
-
227
- It enforces the file layout, the `:global(:root)` block, token-suffix vocabulary, state-before-property rule, the terminal disabled state, public-imports rule, that every token an editor row names is declared in the runtime, and that the id is registered, via either `bootLiveTokens({ components: [{ id }] })` or a direct `registerComponent({ id })` call. On the value side it fails on a colour literal in any notation, on a default reading a token that does not exist, and on a default with no theme token behind it that the editor does not declare an intrinsic.
228
-
229
- It *warns* (non-fatal) when a token-backed default still carries a px or rem term, and when a type-group font helper is called bare across multiple slots, which would merge their fonts into one link tree.
230
-
231
- Exit code 0 means the static contract is met. Resolve warnings before shipping, or run with `--strict` to make them fail. `--json` prints findings with a stable `rule` id, so you can work through one rule at a time and re-run.
232
-
233
- **Then run the registry contract test.** If you're authoring inside the package itself, `src/editor/component-editor/registryContract.test.ts` runs `describe.each(getComponentRegistryEntries())` and verifies, per component, that the registration resolves to a real `sourceFile` and a non-empty schema, that schema variables are unique, that every editable token (excluding `hidden: true`, `kind: 'gradient'`, and padding-side suffixes) is declared in the runtime `<style>` block and seeded in `src/live-tokens/data/component-configs/<id>/default.json`, and that `setComponentAlias` round-trips the alias through the slice.
175
+ Step 6 of the recipe is the static gate: `npx live-tokens check-component <id>` at exit 0, with `--strict` clean or its warnings resolved. It enforces the file layout, the `:global(:root)` block, the suffix vocabulary, state-before-property, the terminal disabled state, public imports, that every token an editor row names is declared in the runtime, that every default reads a theme token, and that the id is registered through `bootLiveTokens({ components: [{ id }] })` or a direct `registerComponent({ id })` call.
234
176
 
235
- A new first-party component is auto-covered the moment it lands in `builtInRegistry` `npm test` will fail if any of the five checks miss. For a consumer-authored component, mirror this pattern in your own test suite if you want the same drift protection: `getComponentRegistryEntries` is exported from `@motion-proto/live-tokens` and returns every registration, shipped and custom, once your `main.ts` has run.
177
+ **Then run the registry contract test.** `checkRegistryEntry`, from `@motion-proto/live-tokens/component-editor/contract`, takes one registry entry and returns a violation line per failure, so a suite over your own components is a `describe.each` and one call. It verifies that the registration resolves to a real `sourceFile` and a non-empty schema, that schema variables are unique, that every editable token is declared in the runtime `<style>` block and seeded in `component-configs/<id>/default.json`, that a token declaring `minOpacity` seeds at or above its floor, and that `setComponentAlias` round-trips the alias through the slice. The test file and its path options are in `references/contract-tests.md`. Inside the package, `registryContract.test.ts` runs that same check over `builtInRegistry`, so a first-party component is covered the moment it lands there.
236
178
 
237
- **If your component declares `intrinsics`, the intrinsics contract test covers it too.** `src/editor/component-editor/intrinsicsContract.test.ts` iterates every entry with an `intrinsics` array and asserts, per (intrinsic, variant), that the runtime `:global(:root)` declares a default, the default is one of the spec's `values`, and the editor's `default` equals the runtime default. This is what would have caught a getter defaulting to `center` while `:global(:root)` says `start`. Same auto-coverage rule: declare `intrinsics` on the registry entry and the test picks it up.
179
+ **If your component declares `intrinsics`, the intrinsics contract test covers it too.** `intrinsicsContract.test.ts` asserts, per (intrinsic, variant), that the runtime `:global(:root)` declares a default, that it is one of the spec's `values`, and that the editor's `default` equals it. This is what would have caught a getter defaulting to `center` while `:global(:root)` says `start`.
238
180
 
239
181
  Finally navigate to `/live-tokens/components` and confirm the runtime behaviours no static check can see:
240
182
 
@@ -0,0 +1,99 @@
1
+ # The registry contract as a test in your own project
2
+
3
+ `checkRegistryEntry` is the contract the package holds its own 26 components
4
+ to, exported so a project outside the package can run it over its own. It takes
5
+ one registry entry and returns a violation line per failure; an empty array is
6
+ the pass.
7
+
8
+ ```ts
9
+ // tests/registryContract.test.ts
10
+ // @vitest-environment happy-dom
11
+ import { describe, it, expect } from 'vitest';
12
+ import { getComponentRegistryEntries, registerComponent } from '@motion-proto/live-tokens';
13
+ import { checkRegistryEntry } from '@motion-proto/live-tokens/component-editor/contract';
14
+ import MyWidgetEditor, { allTokens } from '../src/system/components/MyWidgetEditor.svelte';
15
+
16
+ registerComponent({
17
+ id: 'mywidget',
18
+ label: 'My Widget',
19
+ icon: 'fas fa-magic',
20
+ sourceFile: 'src/system/components/MyWidget.svelte',
21
+ editorComponent: MyWidgetEditor,
22
+ schema: allTokens,
23
+ });
24
+
25
+ const mine = getComponentRegistryEntries().filter((e) => e.origin === 'custom');
26
+
27
+ describe.each(mine.map((e) => [e.id, e] as const))('%s', (_id, entry) => {
28
+ it('meets the registry contract', () => {
29
+ expect(checkRegistryEntry(entry)).toEqual([]);
30
+ });
31
+ });
32
+ ```
33
+
34
+ Two lines there are load-bearing.
35
+
36
+ - **Register at the top of the test file**, rather than importing `main.ts`.
37
+ The entries have to exist before `describe.each` reads them, and
38
+ `bootLiveTokens` would mount the app.
39
+ - **Filter on `origin`.** The registry always carries the shipped components
40
+ too, and their `sourceFile` paths are relative to the package root, not
41
+ yours. Without the filter every built-in fails on a path that does not exist
42
+ in your project.
43
+
44
+ ## Setup
45
+
46
+ `vitest` and `happy-dom` as devDependencies, and the svelte plugin already in
47
+ `vite.config.ts` so the editor `.svelte` import resolves. The helper reads the
48
+ runtime file and `default.json` off disk, which is why it is node-only and has
49
+ its own subpath.
50
+
51
+ The package ships Svelte and TypeScript source, and `bootLiveTokens` imports the
52
+ FontAwesome stylesheet. Left external, Node meets that `.css` and stops with
53
+ `Unknown file extension ".css"`, before a single test runs. Inline both so Vite
54
+ transforms them:
55
+
56
+ ```ts
57
+ // vitest.config.ts
58
+ import { defineConfig, mergeConfig } from 'vitest/config';
59
+ import viteConfig from './vite.config';
60
+
61
+ export default mergeConfig(
62
+ viteConfig,
63
+ defineConfig({
64
+ test: {
65
+ server: { deps: { inline: [/@motion-proto\/live-tokens/, /@fortawesome/] } },
66
+ },
67
+ }),
68
+ );
69
+ ```
70
+
71
+ ## Paths
72
+
73
+ Paths resolve against `process.cwd()` and `src/live-tokens/data/component-configs`.
74
+ A project that moved either passes them:
75
+
76
+ ```ts
77
+ checkRegistryEntry(entry, { projectRoot, componentConfigsDir });
78
+ ```
79
+
80
+ `componentConfigsDir` is the same directory `live-tokens.config.json` names.
81
+
82
+ ## What it holds
83
+
84
+ 1. **Registration** — `sourceFile` resolves to a real file, the schema is non-empty.
85
+ 2. **Uniqueness** — no schema variable is declared twice.
86
+ 3. **Editor to runtime** — every editable token's CSS var is declared in the
87
+ runtime's `<style>` block, so an edit has something to repaint.
88
+ 4. **Editor to default config** — every editable token has a seed alias in
89
+ `component-configs/<id>/default.json`, so the component adopts with full
90
+ defaults. A component with no `default.json` is editor-only; this check and
91
+ the next one skip it.
92
+ 5. **Opacity floors** — a token declaring `minOpacity` ships a default at or
93
+ above it, so a floating panel starts out legible over page content.
94
+ 6. **Round-trip** — `setComponentAlias` persists into the slice under the same key.
95
+
96
+ Checks 3 and 4 exclude `hidden: true` tokens, `kind: 'gradient'` tokens (stored
97
+ as gradient objects, not vars), and `-padding-(top|right|bottom|left)` suffixes
98
+ (written on demand by the split-padding UI and read through the `themed-padding`
99
+ mixin's fallback chain, so they exist as neither `:root` declarations nor seeds).
@@ -85,10 +85,17 @@ preview of the hover state, and a hover the sketch layer cannot paint reads as
85
85
  no hover at all once the real background is transparent.
86
86
 
87
87
  An inner part that carries its own surface (a header strip, a footer) takes its
88
- own class and its own five values. Where such a part draws no outline, bind the
88
+ own class and its own five values. The class is easy to forget, because the part
89
+ already has its own values and looks finished without it — a part carrying only
90
+ the values is left crisp, and reads as a hard-edged rectangle dropped inside a
91
+ drawn box. No checker sees it. Where such a part draws no outline, bind the
89
92
  hatch ink to the ink its **parent** is outlined in, so the component reads as one
90
93
  drawing rather than a shaded panel dropped into a box:
91
94
 
95
+ ```svelte
96
+ <span class="mywidget-header sketch-chip">{label}</span>
97
+ ```
98
+
92
99
  ```css
93
100
  .mywidget-header {
94
101
  --sketch-fill: var(--mywidget-header-surface);
@@ -176,8 +183,7 @@ inherits, so one declaration covers everything under it:
176
183
 
177
184
  Travel is stated in px against a glyph whose size the layer cannot know, so the
178
185
  dial that suits a card's worth of artwork tears a 16px icon apart. Reach for the
179
- soft bank before reaching for `none`. The shipped `SectionDivider` is the worked
180
- example.
186
+ soft bank before reaching for `none`.
181
187
 
182
188
  ## First-party components
183
189
 
@@ -64,6 +64,12 @@ to that list, so the two cannot drift apart.
64
64
  | `-padding` | Internal spacing |
65
65
  | `-gap` | Spacing between sibling elements |
66
66
 
67
+ `-width`, `-height` and `-size` are the fall-through: any dimension with no
68
+ more specific name behind it. They read the `--space-*` scale through the same
69
+ picker `-gap` uses, and they match last, so `-border-width`, `-divider-height`,
70
+ `-icon-size` and the rest claim their token first. Reach for the specific name
71
+ when one fits; a stroke is `-border-width` even where the CSS says `outline:`.
72
+
67
73
  ## Typography
68
74
 
69
75
  | Suffix | Meaning |