@fusion.dev/helix-docs 0.0.0-dev

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/content/accessibility.mdx +117 -0
  3. package/content/angular.mdx +60 -0
  4. package/content/browser-support.mdx +83 -0
  5. package/content/building-on-the-headless-tier.mdx +143 -0
  6. package/content/charting.mdx +93 -0
  7. package/content/coding-agents.mdx +139 -0
  8. package/content/color-modes.mdx +87 -0
  9. package/content/color.mdx +92 -0
  10. package/content/composition.mdx +171 -0
  11. package/content/elevation-and-material.mdx +93 -0
  12. package/content/events.mdx +125 -0
  13. package/content/forms.mdx +170 -0
  14. package/content/iconography.mdx +80 -0
  15. package/content/installation.mdx +125 -0
  16. package/content/intent-reference.mdx +107 -0
  17. package/content/introduction.mdx +69 -0
  18. package/content/localization.mdx +95 -0
  19. package/content/metrics.mdx +399 -0
  20. package/content/motion.mdx +91 -0
  21. package/content/other-frameworks.mdx +70 -0
  22. package/content/overlays.mdx +102 -0
  23. package/content/overriding-a-component.mdx +160 -0
  24. package/content/react.mdx +144 -0
  25. package/content/recipes.mdx +131 -0
  26. package/content/routing.mdx +208 -0
  27. package/content/sandbox.mdx +76 -0
  28. package/content/server-rendering.mdx +103 -0
  29. package/content/shape-and-borders.mdx +80 -0
  30. package/content/size-and-density.mdx +86 -0
  31. package/content/space-and-size.mdx +89 -0
  32. package/content/state-and-control.mdx +132 -0
  33. package/content/surfaces-and-separation.mdx +99 -0
  34. package/content/svelte.mdx +45 -0
  35. package/content/theme-builder.mdx +66 -0
  36. package/content/theming.mdx +111 -0
  37. package/content/tiers.mdx +159 -0
  38. package/content/tokens.mdx +88 -0
  39. package/content/typography.mdx +117 -0
  40. package/content/using-helix-with-an-agent.mdx +231 -0
  41. package/content/using-the-elements.mdx +125 -0
  42. package/content/versioning.mdx +72 -0
  43. package/content/visual-axes.mdx +75 -0
  44. package/content/vue.mdx +80 -0
  45. package/content/without-a-bundler.mdx +119 -0
  46. package/dist/catalog.d.ts +186 -0
  47. package/dist/catalog.js +296 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +6 -0
  50. package/dist/naming.d.ts +14 -0
  51. package/dist/naming.js +28 -0
  52. package/dist/pages.generated.d.ts +7 -0
  53. package/dist/pages.generated.js +284 -0
  54. package/dist/reference.d.ts +105 -0
  55. package/dist/reference.js +181 -0
  56. package/dist/registry.d.ts +89 -0
  57. package/dist/registry.js +70 -0
  58. package/dist/sections.d.ts +44 -0
  59. package/dist/sections.js +36 -0
  60. package/dist/sources.generated.d.ts +6 -0
  61. package/dist/sources.generated.js +5158 -0
  62. package/package.json +46 -0
@@ -0,0 +1,231 @@
1
+ ---
2
+ description: The four things that look like a library bug and are not, and where an agent should read from.
3
+ parent: coding-agents
4
+ section: Getting started
5
+ title: Using Helix with an agent
6
+ ---
7
+
8
+
9
+ # Using Helix with an agent
10
+
11
+ **A page rather than a skill file, and the format is the point.** This started as
12
+ `.claude/skills/using-helix/SKILL.md`, which is one vendor's format sitting in a
13
+ repository, and every other vendor has its own: Cursor reads `.cursor/rules`,
14
+ Copilot reads `.github/copilot-instructions.md`, and a consumer of the published
15
+ packages has none of them because they have this site and no checkout.
16
+
17
+ So the content lives once, here, at a URL that [llms.txt](https://fusion.dev/llms.txt) indexes and
18
+ that every one of those formats can point at. Paste the link into whichever file
19
+ your tool reads. What is vendor-neutral and executable is [the MCP
20
+ server](./coding-agents), which Claude, Cursor, Windsurf and Zed all speak.
21
+
22
+ ## Read the manifest before writing a tag
23
+
24
+ ```
25
+ https://fusion.dev/helix/custom-elements.json
26
+ @fusion.dev/helix/custom-elements.json
27
+ ```
28
+
29
+ Every tag, every property with its literal union, every event with its class,
30
+ every slot, part and custom property. **The vocabulary is the file**: a tag that
31
+ is not in it does not exist, and a property value outside a declared union is one
32
+ the component rejects. Unions are literal rather than named for exactly this
33
+ reason, so `'ghost' | 'outlined' | 'solid' | 'subtle'` answers the question where
34
+ `ButtonEmphasis` sends you somewhere else.
35
+
36
+ One family as data, when the whole manifest is more than the question needs:
37
+
38
+ ```
39
+ https://fusion.dev/helix/components/<family>.json
40
+ https://fusion.dev/helix/components/<family>.md
41
+ ```
42
+
43
+ The `.md` carries the API tables **and the examples**, which the HTML page
44
+ deliberately does not. Start from `https://fusion.dev/llms.txt`, which is one
45
+ request and names every document plus these two surfaces.
46
+
47
+ ## The four things that go wrong
48
+
49
+ These are in order of how often they look like a library bug and are not.
50
+
51
+ ### 1. A declared `value` hands you the control
52
+
53
+ ```text
54
+ <!-- Ignores every keystroke. Nothing is logged. Nothing looks wrong. -->
55
+ <helix-input label="Name" value="Ada"></helix-input>
56
+ ```
57
+
58
+ Do not paste it. Plain text rather than a live example, the same way
59
+ [State and control](./state-and-control) shows this one: a reader copies what is
60
+ in front of them, and these are the two pages that have to show the mistake in
61
+ order to name it.
62
+
63
+ **Writing `value`, `open`, `checked` or `active` is a claim of ownership.** From
64
+ then on the element announces changes and applies none of them, waiting to be
65
+ told. Which mode it is in is read once, at its first update.
66
+
67
+ Seed with the `default-` twin, or answer the event:
68
+
69
+ ```html
70
+ <helix-input
71
+ default-value="Ada"
72
+ label="Name"
73
+ ></helix-input>
74
+ ```
75
+
76
+ ```tsx
77
+ <helix-input label="Name" onChange={(e) => setName(e.currentTarget.value)} value={name} />
78
+ ```
79
+
80
+ In TypeScript this is a compile error rather than a surprise. Everywhere else the
81
+ element warns on its second unanswered request.
82
+
83
+ ### 2. The event name is the event's own
84
+
85
+ `onhelix-before-open`, not `onHelixOpen` and not `onOpen`. React slices `on` off the
86
+ prop and listens for exactly what is left, so the casing is not negotiable. From
87
+ anything that is not React, `addEventListener('helix-before-open', …)`.
88
+
89
+ **Lifecycle does not bubble and coordination does.** `helix-before-open` and
90
+ `helix-before-close` reach the element and stop, so a menu closing inside a dialog is
91
+ not the dialog closing; `helix-select`, `helix-before-change` and `helix-dismiss` cross
92
+ shadow boundaries and can be caught on a container. If a listener on a wrapper
93
+ hears nothing, it is a lifecycle event and belongs on the element.
94
+
95
+ ### 3. `:disabled`, never `[disabled]`
96
+
97
+ ```css
98
+ helix-input:disabled { … } /* right: matches a disabled fieldset too */
99
+ helix-input[disabled] { … } /* wrong: a fieldset sets no attribute */
100
+ ```
101
+
102
+ A control inside `<fieldset disabled>` is disabled by the platform and carries no
103
+ attribute, so the attribute selector paints it as live while it refuses input.
104
+
105
+ ### 4. Two stylesheets, then the elements
106
+
107
+ ```ts
108
+ import '@fusion.dev/helix/helix.css'
109
+ import '@fusion.dev/helix'
110
+ ```
111
+
112
+ `helix.css` is `primitives.css`, `tokens.css`, `base.css` and `elements.css` in
113
+ the order they have to be in. Write them out yourself only to substitute one.
114
+
115
+ `elements.css` is the one people leave out, and everything works without it
116
+ until the page is server-rendered: an element that has not upgraded has no
117
+ styles of its own, so the markup flashes unstyled and then snaps into place.
118
+
119
+ The tell for a missing layer is which. A flash before hydration is
120
+ `elements.css`; bare headings are `base.css`. **No color anywhere means the
121
+ ramp or the semantic layer, and the two are told apart by what is in the
122
+ stylesheet rather than by what is on screen**: without `tokens.css` nothing
123
+ declares `--helix-color-*` at all, while without `primitives.css` all 177 are
124
+ declared and every one resolves to `rgba(0, 0, 0, 0)`, since `tokens.css` refers
125
+ to a primitive 235 times and to nothing else. The second is the one that looks
126
+ like a working install, and it is the reason `all.css` exists.
127
+
128
+ ## Choosing a family
129
+
130
+ Ask the manifest, not your memory. Each family's `helix.aliases` lists the other
131
+ names it goes by, which is usually how a request arrives: `Segment` and `NavItem`
132
+ are `Toggle`, `Key` is `Legend`, `Hint` is `Tooltip`.
133
+
134
+ Two distinctions worth knowing before searching:
135
+
136
+ - **A group is a part when it reports a form value** and its own family when it
137
+ does not. `Radio.Group` and `Toggle.Group` are parts; `Checkbox Group` and
138
+ `Button Group` are families whose members submit themselves.
139
+ - **A control has no `size`.** Height comes from `data-density` on the region,
140
+ `sm` / `md` / `lg`, which crosses shadow boundaries through custom properties.
141
+ If two controls in a row must differ in height, the layout is wrong rather than
142
+ the API.
143
+
144
+ ## Styling from the outside
145
+
146
+ Three doors, in the order to try them.
147
+
148
+ 1. **A token.** `--helix-radii-md: 2px` at `:root` reaches every component,
149
+ because custom properties inherit. Most "can I restyle the button" is really
150
+ "our brand has less rounding".
151
+ 2. **A per-component hook.** `--card-padding` on `helix-card`. Named after the
152
+ component, documented on its page, defaults to a semantic token.
153
+ 3. **A part.** `helix-select::part(trigger)`. The most powerful and the one that
154
+ couples you to internal structure.
155
+
156
+ **Never read a primitive.** `--_helix-neutral-500` and its kind belong to
157
+ `@fusion.dev/helix-styles`; the underscore says so and the indirection is the point.
158
+
159
+ ## No custom elements at all
160
+
161
+ `@fusion.dev/helix-styles` is plain CSS with no JavaScript, and it carries recipes for
162
+ markup Helix does not render:
163
+
164
+ ```html
165
+ <button
166
+ data-button="solid"
167
+ data-button-tone="primary"
168
+ >Save</button>
169
+ <article data-prose><h2>From a CMS</h2></article>
170
+ <div data-density="sm">…</div>
171
+ ```
172
+
173
+ A recipe has appearance and no behavior: no state, no form participation, no
174
+ focus trapping, no keyboard handling. If you are adding script to one, the
175
+ component is what you wanted.
176
+
177
+ ## Pointing your tool at this page
178
+
179
+ **A line, not a copy.** Every tool that reads project instructions reads a file
180
+ in your repository, and they all read a different one. What goes in it is a
181
+ pointer to this URL rather than its contents: a copy starts going stale the
182
+ moment it is pasted, and this page changes with the library.
183
+
184
+ ```md
185
+ Helix: read https://fusion.dev/helix/docs/using-helix-with-an-agent
186
+ before writing components, and https://fusion.dev/helix/custom-elements.json
187
+ for the API.
188
+ ```
189
+
190
+ | Tool | Where it goes |
191
+ |---|---|
192
+ | Claude Code | `CLAUDE.md`, or a skill in `.claude/skills/` |
193
+ | Cursor | `.cursor/rules/` |
194
+ | GitHub Copilot | `.github/copilot-instructions.md` |
195
+ | Anything else | Whatever it reads. The line is the same |
196
+
197
+ **We do not write those files for you**, and that is the Sovereignty Constraint
198
+ rather than laziness: a file we generate into your repository is one you then
199
+ maintain against our changes, and a tool that rewrites it on install is a
200
+ dependency you did not ask for in a directory you own. The line above is yours to
201
+ paste and yours to delete.
202
+
203
+ ## The one thing worth installing
204
+
205
+ ```json
206
+ {
207
+ "mcpServers": {
208
+ "helix": { "command": "helix", "args": ["mcp"] }
209
+ }
210
+ }
211
+ ```
212
+
213
+ `helix mcp` is a subcommand of the CLI, speaking MCP over stdio, which Claude,
214
+ Cursor, Windsurf and Zed all read. It answers the question no document can,
215
+ which is what a given brand color derives to. It runs on your machine, talks to
216
+ a client you chose, and has no network in it.
217
+
218
+ ## What this page will not tell you
219
+
220
+ **Anything the manifest answers.** Property names, unions, part names, event
221
+ names and hooks change with the library and are in a file you can fetch; a list
222
+ of them here would be a second copy going stale. Fetch the family.
223
+
224
+ **What a brand color derives to.** That is computation rather than a document,
225
+ and `helix mcp` is where it lives: an MCP server over stdio with
226
+ `helix_audit_theme` and `helix_derive_theme`, both taking the JSON a
227
+ `helix.theme.json` holds. It runs on your machine and nobody hosts it.
228
+
229
+ **Whether a component is good for your case.** The examples are the honest
230
+ answer to that and they are text: every family's `.md` carries them, and
231
+ `https://fusion.dev/helix/sandbox?e=<family>.<slug>` opens one editable.
@@ -0,0 +1,125 @@
1
+ ---
2
+ description: Binding events, registering elements, and the traps in both.
3
+ pinned: true
4
+ section: Essentials
5
+ title: Using the elements
6
+ ---
7
+
8
+ # Using the elements
9
+
10
+ Write `<helix-card>` in your JSX. They are plain custom elements: no wrapper, no
11
+ patched React, and no React package: there is not one.
12
+
13
+ ```tsx
14
+ export default function Page() {
15
+ return (
16
+ <helix-card>
17
+ <h2 slot="header">Deployments</h2>
18
+ <helix-button emphasis="solid" tone="primary">Ship it</helix-button>
19
+ </helix-card>
20
+ )
21
+ }
22
+ ```
23
+
24
+ Everything below is a trap that has actually cost time, rather than general
25
+ advice.
26
+
27
+ ## Bind Helix events with `onhelix-*`
28
+
29
+ ```tsx
30
+ <helix-tabs onhelix-before-activate={(event) => setPane(event.detail.value)} />
31
+ ```
32
+
33
+ There is a real bug underneath this, and the elements work around it for you.
34
+ **React attaches no custom element listener while hydrating**, so on a
35
+ server-rendered page every one of these props is dead until React commits an
36
+ update for some other reason, at which point it silently starts working. Nothing
37
+ is logged. It is
38
+ [facebook/react#35446](https://github.com/facebook/react/issues/35446), open and
39
+ unfixed.
40
+
41
+ Every Helix element binds the handler itself, immediately before it announces
42
+ anything, using the function React is already holding for it. Measured in Chrome
43
+ against React 19.2.8: on a hydrated page the first event reaches the handler,
44
+ and it is never delivered twice once React binds its own listener.
45
+
46
+ Nothing is required of you, and nothing changes once React fixes it.
47
+
48
+ React's own events are unaffected where they bubble. `onClick` on a
49
+ `<helix-button>` goes through the synthetic system, which hydration does set up.
50
+
51
+ A few of ours are native events relayed from inside the component, and the ones
52
+ that do not bubble (`load` and `error` on Avatar, `ended`, `play` and `pause` on
53
+ Video) reach nothing React sets up. The elements bind those for you, so
54
+ `onError` on a `<helix-avatar>` works like the rest.
55
+
56
+ ## The prop is `on` plus the event name
57
+
58
+ React slices `on` off the prop and listens for exactly what is left, without
59
+ inspecting it.
60
+
61
+ - `onhelix-before-open` reaches `helix-before-open`. ✅
62
+ - `onhelix-sample-start` reaches `helix-sample-start`. ✅
63
+ - `onHelixOpen` binds an event called `HelixOpen`, which nobody dispatches. ❌
64
+
65
+ The number of hyphens does not matter, measured in Chrome against React 19.2.8.
66
+ An earlier version of this page said two were a hard stop; that was the
67
+ hydration bug above, read as a fact about names.
68
+
69
+ ## Register once, in a client component
70
+
71
+ A custom element registers as a side effect of importing its module, and that
72
+ import has to be in the client graph to ever run. A server component's imports
73
+ do not ship to the browser. So one client component holds every element import
74
+ and renders `null`:
75
+
76
+ ```tsx
77
+ 'use client'
78
+ import '@fusion.dev/helix/card'
79
+ import '@fusion.dev/helix/button'
80
+
81
+ export function HelixElements(): null {
82
+ return null
83
+ }
84
+ ```
85
+
86
+ Render it once in your layout. **Your pages stay server components.** An
87
+ unregistered tag is just an unknown element in the HTML that upgrades when the
88
+ definition lands. Asking you to mark every page `'use client'` to use a button
89
+ would not be a design system.
90
+
91
+ ## Import `elements.css` beside the base layer
92
+
93
+ ```ts
94
+ import '@fusion.dev/helix-styles/base.css'
95
+ import '@fusion.dev/helix/elements.css'
96
+ ```
97
+
98
+ It hides each tag with `:not(:defined)` until it upgrades, using `visibility` so
99
+ the page does not reflow. Without it a card arrives as a run of unstyled text
100
+ and then snaps into a card.
101
+
102
+ It is generated from the element sources and enumerates the tags, because CSS
103
+ cannot select "any custom element" and a bare `:not(:defined)` would also catch
104
+ your framework's dev overlay.
105
+
106
+ ## Import from subpaths
107
+
108
+ ```ts
109
+ import '@fusion.dev/helix/card' // ✅ one family
110
+ import '@fusion.dev/helix' // ❌ the barrel
111
+ ```
112
+
113
+ One family per module, so you are not shipping the library to use a button.
114
+
115
+ ## There is no declarative shadow DOM
116
+
117
+ Components render on the server through Lit SSR in our own tests, but the app
118
+ ships no DSD, and the shadow styles arrive with the definition rather than in
119
+ the markup. You do not need to configure anything for this; it is here so the
120
+ absence is not mistaken for something missing.
121
+
122
+ The short version of why: DSD cannot activate for script-inserted nodes, a
123
+ server component's output is replayed from the flight payload through DOM APIs
124
+ rather than re-run, and the patch that made it work hooked webpack, which
125
+ Turbopack ignores, silently.
@@ -0,0 +1,72 @@
1
+ ---
2
+ description: What a version number means here, and which packages move together.
3
+ order: 6
4
+ section: Getting started
5
+ title: Versioning
6
+ ---
7
+
8
+ # Versioning
9
+
10
+ **Every Helix package shares one version.** `@fusion.dev/helix@1.4.0` works with
11
+ `@fusion.dev/helix-headless@1.4.0` and with `@fusion.dev/helix-styles@1.4.0`, and that is a
12
+ guarantee rather than a coincidence: they are released together, always, from
13
+ one tag.
14
+
15
+ ```sh
16
+ bun add @fusion.dev/helix@1.4.0 @fusion.dev/helix-styles@1.4.0
17
+ ```
18
+
19
+ Pin them to the same number or pin none of them. A range that lets one drift
20
+ ahead of another is the one configuration this scheme does not defend against.
21
+
22
+ ## Why lockstep
23
+
24
+ Cortex moves independently. Full-repo lockstep would be wrong here because the
25
+ documentation packages carry the version of what they document: publishing a
26
+ Cortex fix would bump `@fusion.dev/helix-docs` to a version in which nothing about Helix
27
+ changed.
28
+
29
+ Per-domain lockstep keeps the property that matters, which is that two Helix
30
+ packages of the same version are the pair that was tested together.
31
+
32
+ ## The version lives in the tag
33
+
34
+ Every manifest in the repository holds `0.0.0-dev`. The real number is stamped
35
+ in CI, seconds before packing.
36
+
37
+ If you read the source: a manifest saying `0.0.0-dev` is not an unreleased
38
+ package, it is every package. Committing the number back
39
+ would need a bot whose push access outranks branch protection, to record
40
+ something `git tag` already holds.
41
+
42
+ Two tag shapes, and the second is the useful one:
43
+
44
+ | Tag | Means |
45
+ |---|---|
46
+ | `helix-v1.4.0` | A release that promoted |
47
+ | `helix-attempt-v1.4.0` | A number that was spent and did not ship |
48
+
49
+ Version computation reads both, so a failed release burns a number rather than
50
+ poisoning the next one. If you see 1.4.0 skipped, that is what happened.
51
+
52
+ ## Documentation is versioned with the code
53
+
54
+ `@fusion.dev/helix-docs@1.4.0` documents Helix 1.4.0. There is no version axis inside
55
+ the package and no version switcher in it: the package version *is* the version,
56
+ because every package in the domain moves together.
57
+
58
+ A site serving several versions installs several versions, which is the
59
+ mechanism npm already provides and the only one that cannot drift.
60
+
61
+ ## Stability
62
+
63
+ Components carry a `status` in the manifest and on their page: `experimental`,
64
+ `stable`, or `deprecated`. Most of the library is experimental today and says
65
+ so. That label is the honest one and it will not be softened to make the set
66
+ look more finished than it is.
67
+
68
+ ## Release notes
69
+
70
+ On GitHub, generated from the tags. There is no changelog file in this package,
71
+ because a second copy of the release notes is a second thing to keep in step and
72
+ the one that goes stale is the one nobody is reading that week.
@@ -0,0 +1,75 @@
1
+ ---
2
+ description: Four words with fixed vocabularies, one escape hatch, and the word that is refused.
3
+ section: Theming
4
+ title: Visual axes
5
+ ---
6
+
7
+ # Visual axes
8
+
9
+ A component's visual choices come from four registered axes with fixed
10
+ vocabularies. You cannot invent a value, and a component may take a subset.
11
+
12
+ | Axis | Values |
13
+ |---|---|
14
+ | `emphasis` | `ghost`, `outlined`, `solid`, `subtle` |
15
+ | `material` | `frosted`, `glass`, `transparent` |
16
+ | `separation` | `elevated`, `filled`, `outlined`, `plain` |
17
+ | `tone` | The status and intent words: `primary`, `accent`, `neutral`, `danger`, and so on |
18
+ | `width` | `auto`, `full`, `standard` |
19
+
20
+ ```html
21
+ <helix-button
22
+ emphasis="solid"
23
+ tone="primary"
24
+ >Save</helix-button>
25
+ <helix-card separation="outlined">…</helix-card>
26
+ <helix-megamenu material="glass">…</helix-megamenu>
27
+ ```
28
+
29
+ ## Why two axes
30
+
31
+ **The library had four axes and one word for all of them.** Counted across both
32
+ tiers, `appearance` held 21 declarations describing at least four different
33
+ questions: where a surface ends (`outlined` against `plain` on a Card), how much
34
+ ink a control claims (`ghost` against `solid` on a Button), which widget gets
35
+ rendered (`segmented` against `underlined` on Tabs), and in one case a plain
36
+ misfiled tone.
37
+
38
+ The tell is that `outlined` appeared in two of those groups meaning different
39
+ things. On a Card it says where the surface ends. On a Button it says how much
40
+ ink. Same word, two questions, and no way to ask one without answering the
41
+ other.
42
+
43
+ **`appearance` is refused outright now.** It is not a word this library uses,
44
+ and a component reintroducing it fails the build.
45
+
46
+ ## Two axes, not one
47
+
48
+ This is the test, and the library found it before the registry existed.
49
+ `megamenu-root.css` reads: a consumer can make the panel glass, which is not an
50
+ appearance, because `full` already occupies that axis with a layout. A panel can
51
+ be glass *and* outlined. Two axes.
52
+
53
+ If you are adding a property and cannot decide which axis it belongs to, that is
54
+ usually the answer: it is two properties.
55
+
56
+ ## `variant` is the escape hatch
57
+
58
+ ```html
59
+ <helix-tabs variant="segmented">…</helix-tabs>
60
+ <helix-time-picker variant="wheels">…</helix-time-picker>
61
+ ```
62
+
63
+ `variant` takes no registry, being component-specific by definition: `segmented`
64
+ against `underlined`, `list` against `wheels`, `dots` against `marks`. It is a
65
+ choice between two widgets, which is structural wherever it is made, so it lives
66
+ on either tier.
67
+
68
+ **What it may not be is a place to put something that is on an axis.** That is
69
+ the failure mode `appearance` demonstrated for 21 components.
70
+
71
+ ## `tone` is the one axis both tiers carry
72
+
73
+ A styled axis never appears on the headless tier, which has nothing to paint and
74
+ no box to size. `tone` is the exception and a principled one: it says what a
75
+ thing *means*, and meaning survives having no stylesheet.
@@ -0,0 +1,80 @@
1
+ ---
2
+ description: The one compiler setting Vue needs, and why props and events already work.
3
+ parent: installation
4
+ section: Getting started
5
+ title: Vue
6
+ ---
7
+
8
+ # Vue
9
+
10
+ ```ts
11
+ import '@fusion.dev/helix/vue'
12
+ ```
13
+
14
+ A types-only import, once. It augments Vue's `GlobalComponents` so every Helix
15
+ tag is typed in an SFC template, with its real props and its real events. The
16
+ generated file declares the augmentation under both `vue` and
17
+ `@vue/runtime-core`, because tooling disagrees about which one to read.
18
+
19
+ ## Tell the compiler the tags are deliberate
20
+
21
+ Without this, Vue resolves `<helix-button>` as a component it cannot find and
22
+ warns on every render.
23
+
24
+ ```ts
25
+ // vite.config.ts
26
+ export default defineConfig({
27
+ plugins: [
28
+ vue({
29
+ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('helix-') } },
30
+ }),
31
+ ],
32
+ })
33
+ ```
34
+
35
+ The prefix test is the whole rule. Every element in the library is `helix-`
36
+ something, in both tiers, and nothing else is.
37
+
38
+ ## Props and events need no ceremony
39
+
40
+ Vue sets a property when the element has one and falls back to an attribute
41
+ otherwise, which is exactly right for these: a string goes through either path
42
+ and an array or an object goes through the property.
43
+
44
+ ```vue
45
+ <helix-tabs :items="items" @helix-before-activate="onActivate" />
46
+ ```
47
+
48
+ `@helix-before-activate` binds with `addEventListener`, so the name is the event's own.
49
+ Vue is the framework this works most quietly in, which is why this page is
50
+ short: there is no bridge and no wrapper, only the compiler setting above.
51
+
52
+ **Force the property with `.prop` if you ever need to**, for instance where a
53
+ string would be ambiguous:
54
+
55
+ ```vue
56
+ <helix-input :value.prop="'42'" />
57
+ ```
58
+
59
+ Rarely necessary, and it exists so you do not go looking for it.
60
+
61
+ ## Seeding, mirroring and refusing
62
+
63
+ `value`, `open`, `checked` and `active` are seeds. The element applies its own
64
+ changes from there, so all three of these work. See [State and
65
+ control](./state-and-control).
66
+
67
+ ```vue
68
+ <!-- Seeded: the element owns it from there. -->
69
+ <helix-input default-value="Ada" label="Name" />
70
+
71
+ <!-- Mirrored: bind the notification and let the element apply. -->
72
+ <helix-input label="Name" @change="name = $event.target.value" />
73
+
74
+ <!-- Refused: you take the decision for this one change. -->
75
+ <helix-input
76
+ :value="name"
77
+ label="Name"
78
+ @helix-before-change="$event.preventDefault(); name = clean($event.detail.value)"
79
+ />
80
+ ```