@fusion.dev/helix-docs 0.104.1 → 0.106.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 (48) hide show
  1. package/content/accessibility.mdx +92 -71
  2. package/content/angular.mdx +26 -24
  3. package/content/browser-support.mdx +61 -58
  4. package/content/building-on-the-headless-tier.mdx +100 -74
  5. package/content/charting.mdx +98 -79
  6. package/content/coding-agents.mdx +87 -69
  7. package/content/color-modes.mdx +40 -37
  8. package/content/color.mdx +67 -54
  9. package/content/composition.mdx +85 -83
  10. package/content/elevation-and-material.mdx +32 -29
  11. package/content/events.mdx +77 -65
  12. package/content/forms.mdx +69 -58
  13. package/content/iconography.mdx +75 -71
  14. package/content/installation.mdx +77 -68
  15. package/content/intent-reference.mdx +59 -54
  16. package/content/introduction.mdx +25 -23
  17. package/content/localization.mdx +122 -82
  18. package/content/maps.mdx +431 -0
  19. package/content/metrics.mdx +296 -299
  20. package/content/motion.mdx +44 -36
  21. package/content/other-frameworks.mdx +33 -31
  22. package/content/overlays.mdx +72 -54
  23. package/content/overriding-a-component.mdx +85 -74
  24. package/content/react.mdx +72 -64
  25. package/content/recipes.mdx +64 -47
  26. package/content/routing.mdx +100 -93
  27. package/content/sandbox.mdx +39 -39
  28. package/content/server-rendering.mdx +95 -84
  29. package/content/shape-and-borders.mdx +44 -37
  30. package/content/size-and-density.mdx +51 -46
  31. package/content/space-and-size.mdx +41 -34
  32. package/content/state-and-control.mdx +63 -51
  33. package/content/surfaces-and-separation.mdx +55 -53
  34. package/content/svelte.mdx +20 -19
  35. package/content/theme-builder.mdx +42 -31
  36. package/content/theming.mdx +58 -51
  37. package/content/tiers.mdx +69 -61
  38. package/content/tokens.mdx +72 -53
  39. package/content/typography.mdx +47 -47
  40. package/content/using-helix-with-an-agent.mdx +147 -115
  41. package/content/using-the-elements.mdx +55 -50
  42. package/content/versioning.mdx +42 -35
  43. package/content/visual-axes.mdx +168 -146
  44. package/content/vue.mdx +24 -24
  45. package/content/without-a-bundler.mdx +40 -23
  46. package/dist/pages.generated.js +49 -43
  47. package/dist/sources.generated.js +3548 -2682
  48. package/package.json +3 -3
@@ -1,48 +1,49 @@
1
1
  ---
2
- description: Three families, one scale, and a heading that is a real heading.
2
+ description: The font families, the type scale, and how Helix styles headings and prose.
3
3
  section: Foundations
4
4
  title: Typography
5
5
  ---
6
6
 
7
7
  # Typography
8
8
 
9
- ## The families
9
+ ## Font families
10
10
 
11
11
  [//]: # (tokens:font)
12
12
 
13
13
  | Token | Value | For |
14
14
  | --- | --- | --- |
15
- | `--helix-font-sans` | `ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif` | Everything, unless it is one of the two below. |
16
- | `--helix-font-mono` | `ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace` | Code, and anything that has to line up in a column. |
17
- | `--helix-font-display` | `ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif` | Headings meant to be looked at rather than read. Falls back to the sans face. |
15
+ | `--helix-font-sans` | `ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif` | All text except code and display headings. |
16
+ | `--helix-font-mono` | `ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace` | Code, and text that needs to line up in columns. |
17
+ | `--helix-font-display` | `ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif` | Large display headings. Falls back to the sans face. |
18
18
 
19
19
  [//]: # (/tokens)
20
20
 
21
- Three, not a scale of weights and widths, because a design system with more type
22
- families than a product has uses ends up with two that mean the same thing.
21
+ Helix has three font families and no scale of weights and widths. A design
22
+ system with more families than a product uses ends up with two that mean the
23
+ same thing.
23
24
 
24
- ## The scale
25
+ ## The type scale
25
26
 
26
- `--helix-text-xs` through `--helix-text-3xl`, plus `--helix-text-control`, which
27
- is the size a control's own text takes and follows the region's density rather
28
- than the type scale.
27
+ The type scale runs from `--helix-text-xs` to `--helix-text-3xl`.
28
+ `--helix-text-control` sets the size of a control's own text. It follows the
29
+ region's density and is separate from the type scale.
29
30
 
30
- **The scale is applied to real headings by the base layer, not by a component.**
31
+ **The base layer applies the scale to heading elements.**
31
32
 
32
33
  ```css
33
34
  :where([data-prose] h1) { font-size: var(--helix-text-3xl); }
34
35
  :where([data-prose] h2) { font-size: var(--helix-text-2xl); }
35
36
  ```
36
37
 
37
- Styling the element rather than a component means the markup and the appearance
38
- cannot disagree by default, and it reaches prose the design system never renders:
39
- anything out of a CMS, a markdown file, or a rich-text field lands inside
40
- `[data-prose]` and comes out right without being rewritten.
38
+ Because the styles target the elements, a heading's markup and its appearance
39
+ match by default. The styles also reach prose that Helix does not render.
40
+ Content from a CMS, a Markdown file, or a rich-text field is styled correctly
41
+ inside `[data-prose]`, with no changes to the content.
41
42
 
42
- **`Text` exists for the case where they genuinely have to come apart.** A
43
- section's second heading sometimes has to look smaller without lying about the
44
- outline, and a `<p>` sometimes has to read as a caption, so `as` chooses the
45
- element and `variant` chooses the ramp:
43
+ Use `Text` when a heading's element and its appearance need to differ. A
44
+ section's second heading may need to look smaller and keep its place in the
45
+ outline. A `<p>` may need to look like a caption. `as` chooses the element, and
46
+ `variant` chooses the ramp:
46
47
 
47
48
  ```html
48
49
  <helix-text
@@ -51,14 +52,14 @@ element and `variant` chooses the ramp:
51
52
  >Reads as body, outlines as a heading</helix-text>
52
53
  ```
53
54
 
54
- They default to matching, so `as="h2"` and nothing else is the common case, and
55
- the pairing is resolved in CSS rather than by writing an attribute onto the host,
56
- because that would be a hydration mismatch in every React consumer. Size, weight
57
- and leading are not properties: a ramp is a decision the system already made, and
58
- exposing the parts offers a few hundred combinations of which a handful are
59
- right. There is no separate `Heading` component, because that is `as`.
55
+ By default the two match, so most headings need only `as="h2"`. CSS resolves
56
+ the match. `Text` does not write an attribute onto the host element, because
57
+ that would cause a hydration mismatch in every React app. Size, weight, and
58
+ leading are not properties. Each ramp already sets them, and setting them one
59
+ by one would allow a few hundred combinations, of which only a handful are
60
+ right. There is no separate `Heading` component, because `as` does that job.
60
61
 
61
- ## Applying it to your own text
62
+ ## Style your own text
62
63
 
63
64
  ```html
64
65
  <article data-prose>
@@ -67,25 +68,25 @@ right. There is no separate `Heading` component, because that is `as`.
67
68
  </article>
68
69
  ```
69
70
 
70
- `[data-prose]` is the container. Inside it, headings, paragraphs, lists, links
71
- and code are all styled from the token layer.
71
+ Put `[data-prose]` on the element that contains your text. Inside it, the token
72
+ layer styles headings, paragraphs, lists, links, and code.
72
73
 
73
- For one-off text outside prose, `[data-text]` carries the ramp:
74
+ For one-off text outside prose, use `[data-text]` to set the ramp:
74
75
 
75
76
  ```html
76
77
  <span data-text="sm">Fine print</span>
77
78
  ```
78
79
 
79
- ## The vertical metrics
80
+ ## Line height, letter spacing, and weight
80
81
 
81
82
  [//]: # (tokens:leading)
82
83
 
83
84
  | Token | Value | For |
84
85
  | --- | --- | --- |
85
- | `--helix-leading-tight` | `1.2` | Body text set close, for a short measure. |
86
- | `--helix-leading-normal` | `1.5` | Body text, and the default a paragraph takes. |
87
- | `--helix-leading-loose` | `1.7` | Body text set open, for a long measure or a small size. |
88
- | `--helix-leading-heading` | `calc(0.99em + 0.5rem)` | Headings, which want less than a paragraph. Written as a length plus an em so it tightens as the type grows. |
86
+ | `--helix-leading-tight` | `1.2` | Body text set close, for short lines. |
87
+ | `--helix-leading-normal` | `1.5` | Body text. The default line height for a paragraph. |
88
+ | `--helix-leading-loose` | `1.7` | Body text set open, for long lines or small sizes. |
89
+ | `--helix-leading-heading` | `calc(0.99em + 0.5rem)` | Headings, which need less line height than a paragraph. It is a length plus an em, so it tightens as the type grows. |
89
90
 
90
91
  [//]: # (/tokens)
91
92
 
@@ -93,10 +94,10 @@ For one-off text outside prose, `[data-text]` carries the ramp:
93
94
 
94
95
  | Token | Value | For |
95
96
  | --- | --- | --- |
96
- | `--helix-tracking-tight` | `-0.01em` | Large type, which needs the letters closer than a paragraph does. |
97
- | `--helix-tracking-normal` | `0` | Body text, and the default. |
98
- | `--helix-tracking-wide` | `0.02em` | Small capitals and labels, which need the room. |
99
- | `--helix-tracking-wider` | `0.12em` | An uppercase eyebrow, which needs more room than a label. |
97
+ | `--helix-tracking-tight` | `-0.01em` | Large type, which needs tighter letter spacing than a paragraph. |
98
+ | `--helix-tracking-normal` | `0` | Body text. The default letter spacing. |
99
+ | `--helix-tracking-wide` | `0.02em` | Labels and small capitals, which need extra letter spacing. |
100
+ | `--helix-tracking-wider` | `0.12em` | Uppercase eyebrows, which need more letter spacing than a label. |
100
101
 
101
102
  [//]: # (/tokens)
102
103
 
@@ -104,14 +105,13 @@ For one-off text outside prose, `[data-text]` carries the ramp:
104
105
 
105
106
  | Token | Value | For |
106
107
  | --- | --- | --- |
107
- | `--helix-weight-regular` | `400` | Body text, and the weight most of a page is set in. |
108
- | `--helix-weight-medium` | `500` | A label, or a heading that should not shout. |
109
- | `--helix-weight-semibold` | `600` | A heading, and the heaviest this system goes. |
108
+ | `--helix-weight-regular` | `400` | Body text. Most of a page uses this weight. |
109
+ | `--helix-weight-medium` | `500` | Labels, and quieter headings. |
110
+ | `--helix-weight-semibold` | `600` | Headings. The heaviest weight in Helix. |
110
111
 
111
112
  [//]: # (/tokens)
112
113
 
113
- **Headings have their own leading token rather than a smaller number of the body
114
- one.** A heading set at the paragraph's line height reads as loose at every
115
- size, and the correction is not proportional: the larger the type, the less
116
- leading it wants. One token expresses that once instead of a magic multiplier at
117
- every heading level.
114
+ **Headings have their own leading token.** At a paragraph's line height, a
115
+ heading looks loose at every size. The fix is not proportional, because larger
116
+ type needs less leading. `--helix-leading-heading` expresses that once, so no
117
+ heading level needs its own multiplier.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: The four things that look like a library bug and are not, and where an agent should read from.
2
+ description: Four mistakes that look like Helix bugs, and the files an agent should read.
3
3
  parent: coding-agents
4
4
  section: Getting started
5
5
  title: Using Helix with an agent
@@ -8,16 +8,21 @@ title: Using Helix with an agent
8
8
 
9
9
  # Using Helix with an agent
10
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.
11
+ **We publish this guide as a web page so that every tool can read it.** It began
12
+ as `.claude/skills/using-helix/SKILL.md`, a file format that one vendor reads in
13
+ a repository. Every other vendor reads its own file:
16
14
 
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.
15
+ - Cursor reads `.cursor/rules`.
16
+ - Copilot reads `.github/copilot-instructions.md`.
17
+
18
+ If you install the published packages, you have none of these files. You have
19
+ this site and no checkout of our repository.
20
+
21
+ So the guide lives in one place, here, at a URL that
22
+ [llms.txt](https://fusion.dev/llms.txt) indexes and that each of those files can
23
+ link to. Paste the link into the file your tool reads. [The MCP
24
+ server](./coding-agents) works with any vendor and runs code, and Claude,
25
+ Cursor, Windsurf, and Zed all support it.
21
26
 
22
27
  ## Read the manifest before writing a tag
23
28
 
@@ -26,33 +31,39 @@ https://fusion.dev/helix/custom-elements.json
26
31
  @fusion.dev/helix/custom-elements.json
27
32
  ```
28
33
 
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.
34
+ The manifest lists:
35
+
36
+ - every tag
37
+ - every property, with its literal union
38
+ - every event, with its class
39
+ - every slot, part, and custom property
35
40
 
36
- One family as data, when the whole manifest is more than the question needs:
41
+ **If a tag is not in the manifest, it does not exist.** A property value outside
42
+ a declared union is invalid. Property types are literal unions for this reason.
43
+ `'ghost' | 'outlined' | 'solid' | 'subtle'` shows you the values, and a named
44
+ type such as `ButtonEmphasis` sends you somewhere else to find them.
45
+
46
+ When you need one family, fetch it as data:
37
47
 
38
48
  ```
39
49
  https://fusion.dev/helix/components/<family>.json
40
50
  https://fusion.dev/helix/components/<family>.md
41
51
  ```
42
52
 
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.
53
+ The `.md` file has the API tables and the examples. The HTML page leaves the
54
+ examples out on purpose. Start from `https://fusion.dev/llms.txt`. In one
55
+ request, it names every document and these two sources.
46
56
 
47
- ## The four things that go wrong
57
+ ## Four mistakes that look like Helix bugs
48
58
 
49
- These are in order of how often they look like a library bug and are not.
59
+ Each of these looks like a bug in Helix. They are listed from the most common to
60
+ the least common.
50
61
 
51
- ### 1. Refusing a change by not answering
62
+ ### 1. Blocking a change without `preventDefault()`
52
63
 
53
- **Writing `value`, `open`, `checked` or `active` is a seed and a correction.**
54
- The element starts there and goes on applying its own changes, exactly as
55
- `<input value="Ada">` does, so this works and needs no handler beside it:
64
+ **Setting `value`, `open`, `checked`, or `active` sets the starting state and
65
+ corrects the current state.** The element starts in that state and then applies
66
+ its own changes, the same as `<input value="Ada">`. This works with no handler:
56
67
 
57
68
  ```html
58
69
  <helix-input
@@ -61,10 +72,10 @@ The element starts there and goes on applying its own changes, exactly as
61
72
  ></helix-input>
62
73
  ```
63
74
 
64
- The trap is on the other side. React force-restores a native `<input>` whose
65
- state did not move and does not do this for a custom element, so a handler that
66
- declines to call `setState` leaves the element holding the new value. Refusal is
67
- `preventDefault()` on the matching `helix-before-*` event:
75
+ The mistake happens when you block a change. React restores a native `<input>`
76
+ whose state did not change. React does not do this for a custom element, so a
77
+ handler that does not call `setState` leaves the element with the new value. To
78
+ block a change, call `preventDefault()` on the matching `helix-before-*` event:
68
79
 
69
80
  ```tsx
70
81
  <helix-input
@@ -77,92 +88,109 @@ declines to call `setState` leaves the element holding the new value. Refusal is
77
88
  />
78
89
  ```
79
90
 
80
- `default-value` and its twins are what a reset returns to. See
81
- [State and control](./state-and-control).
91
+ A form reset returns the element to `default-value` and the matching attributes
92
+ for the other states. See [State and control](./state-and-control).
82
93
 
83
- ### 2. The event name is the event's own
94
+ ### 2. Using the exact event name
84
95
 
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', …)`.
96
+ In React, write `onhelix-before-open`. `onHelixOpen` and `onOpen` do not work.
97
+ React removes `on` from the prop name and listens for exactly the rest, so the
98
+ casing must match. Outside React, use
99
+ `addEventListener('helix-before-open', …)`.
88
100
 
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.
101
+ **Lifecycle events do not bubble. Coordination events do.**
102
+ `helix-before-open` and `helix-before-close` reach the element and stop there,
103
+ so a menu closing inside a dialog does not close the dialog. `helix-select`,
104
+ `helix-before-change`, and `helix-dismiss` cross shadow boundaries, so you can
105
+ listen for them on a container. If a listener on a wrapper receives nothing, the
106
+ event is a lifecycle event. Put the listener on the element.
94
107
 
95
- ### 3. `:disabled`, never `[disabled]`
108
+ ### 3. Styling a disabled control with `:disabled`
96
109
 
97
110
  ```css
98
111
  helix-input:disabled { … } /* right: matches a disabled fieldset too */
99
112
  helix-input[disabled] { … } /* wrong: a fieldset sets no attribute */
100
113
  ```
101
114
 
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.
115
+ The browser disables a control inside `<fieldset disabled>` without adding an
116
+ attribute to it. The `[disabled]` selector does not match it, so the control
117
+ looks enabled while it does not accept input.
104
118
 
105
- ### 4. Two stylesheets, then the elements
119
+ ### 4. Loading every style layer before the elements
106
120
 
107
121
  ```ts
108
122
  import '@fusion.dev/helix-styles/helix.css'
109
123
  import '@fusion.dev/helix'
110
124
  ```
111
125
 
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.
126
+ `helix.css` combines four files in the order they must load:
127
+
128
+ 1. `primitives.css`
129
+ 2. `tokens.css`
130
+ 3. `base.css`
131
+ 4. `elements.css`
132
+
133
+ Import them one by one only when you need to replace one of them.
114
134
 
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.
135
+ People most often leave out `elements.css`. Everything works without it until
136
+ you render the page on the server. An element that has not upgraded has no
137
+ styles of its own, so the markup appears unstyled and then jumps into place.
118
138
 
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 185
124
- semantic tokens are declared and every color resolves to `rgba(0, 0, 0, 0)`,
125
- since `tokens.css` refers to a primitive 297 times and to nothing else. The
126
- second is the one that looks like a working install, and it is the reason
127
- `helix.css` exists.
139
+ Each missing layer has its own symptom:
128
140
 
129
- ## Choosing a family
141
+ - An unstyled flash before hydration means `elements.css` is missing.
142
+ - Unstyled headings mean `base.css` is missing.
143
+ - No color anywhere means `primitives.css` or `tokens.css` is missing.
130
144
 
131
- Ask the manifest, not your memory. Each family's `helix.aliases` lists the other
132
- names it goes by, which is usually how a request arrives: `Segment` and `NavItem`
133
- are `Toggle`, `Key` is `Legend`, `Hint` is `Tooltip`.
145
+ To tell the last two apart, look at the stylesheet. The screen looks the same.
146
+ Without `tokens.css`, nothing declares `--helix-color-*`. Without
147
+ `primitives.css`, all 185 semantic tokens are declared and every color resolves
148
+ to `rgba(0, 0, 0, 0)`, because `tokens.css` refers to a primitive 297 times and
149
+ to nothing else. A missing `primitives.css` looks like a working install, and
150
+ `helix.css` exists to prevent it.
134
151
 
135
- Two distinctions worth knowing before searching:
152
+ ## Choosing a component family
136
153
 
137
- - **A group is a part when it reports a form value** and its own family when it
138
- does not. `Radio.Group` and `Toggle.Group` are parts; `Checkbox Group` and
139
- `Button Group` are families whose members submit themselves.
140
- - **A control has no `size`.** Height comes from `data-density` on the region,
141
- `sm` / `md` / `lg`, which crosses shadow boundaries through custom properties.
142
- If two controls in a row must differ in height, the layout is wrong rather than
143
- the API.
154
+ Look up the family in the manifest. Each family's `helix.aliases` lists its
155
+ other names, and a request often uses one of them:
144
156
 
145
- ## Styling from the outside
157
+ - `Segment` and `NavItem` are `Toggle`.
158
+ - `Key` is `Legend`.
159
+ - `Hint` is `Tooltip`.
146
160
 
147
- Three doors, in the order to try them.
161
+ Know two rules before you search:
162
+
163
+ - **A group that reports a form value is a part.** A group that does not is its
164
+ own family. `Radio.Group` and `Toggle.Group` are parts. `Checkbox Group` and
165
+ `Button Group` are families whose members each submit their own value.
166
+ - **A control has no `size`.** Its height comes from `data-density` on the
167
+ region, `sm` / `md` / `lg`, which crosses shadow boundaries through custom
168
+ properties. If two controls in a row need different heights, change the
169
+ layout.
170
+
171
+ ## Styling components from outside
172
+
173
+ You can style a component in three ways. Try them in this order:
148
174
 
149
175
  1. **A token.** `--helix-seed-radii: 2px` at `:root` reaches every component,
150
- because custom properties inherit. Most "can I restyle the button" is really
151
- "our brand has less rounding". The seed and not `--helix-radii-md`: the four
152
- radii are siblings derived from it, so `md` alone moves neither the buttons
153
- nor the cards.
154
- 2. **A per-component hook.** `--card-padding` on `helix-card`. Named after the
155
- component, documented on its page, defaults to a semantic token.
156
- 3. **A part.** `helix-select::part(trigger)`. The most powerful and the one that
157
- couples you to internal structure.
176
+ because custom properties inherit. Most requests to restyle the button are
177
+ really requests for less rounding across the brand. Set the seed. The four
178
+ radii, including `--helix-radii-md`, are computed from it, so changing `md`
179
+ alone changes neither the buttons nor the cards.
180
+ 2. **A per-component hook.** For example, `--card-padding` on `helix-card`. Each
181
+ hook is named after its component, documented on the component's page, and
182
+ defaults to a semantic token.
183
+ 3. **A part.** For example, `helix-select::part(trigger)`. A part gives you the
184
+ most control, and it ties your styles to the component's internal structure.
158
185
 
159
- **Never read a primitive.** `--_helix-neutral-500` and its kind belong to
160
- `@fusion.dev/helix-styles`; the underscore says so and the indirection is the point.
186
+ **Never read a primitive.** `--_helix-neutral-500` and the other primitives
187
+ belong to `@fusion.dev/helix-styles`. The underscore marks them as internal.
188
+ Read the semantic tokens that refer to them.
161
189
 
162
- ## No custom elements at all
190
+ ## Styling HTML without custom elements
163
191
 
164
- `@fusion.dev/helix-styles` is plain CSS with no JavaScript, and it carries recipes for
165
- markup Helix does not render:
192
+ `@fusion.dev/helix-styles` is plain CSS with no JavaScript. It includes recipes
193
+ for markup that Helix does not render:
166
194
 
167
195
  ```html
168
196
  <button
@@ -173,16 +201,21 @@ markup Helix does not render:
173
201
  <div data-density="sm">…</div>
174
202
  ```
175
203
 
176
- A recipe has appearance and no behavior: no state, no form participation, no
177
- focus trapping, no keyboard handling. If you are adding script to one, the
178
- component is what you wanted.
204
+ A recipe sets appearance only. It has none of these:
205
+
206
+ - state
207
+ - form participation
208
+ - focus trapping
209
+ - keyboard handling
210
+
211
+ If you are adding a script to a recipe, use the component.
179
212
 
180
- ## Pointing your tool at this page
213
+ ## Link your tool to this guide
181
214
 
182
- **A line, not a copy.** Every tool that reads project instructions reads a file
183
- in your repository, and they all read a different one. What goes in it is a
184
- pointer to this URL rather than its contents: a copy starts going stale the
185
- moment it is pasted, and this page changes with the library.
215
+ **Link to this page from your instructions file, and do not copy it.** Each tool
216
+ that reads project instructions reads a file in your repository, and each tool
217
+ reads a different one. A copy starts to go stale as soon as you paste it, and
218
+ this page changes with the library.
186
219
 
187
220
  ```md
188
221
  Helix: read https://fusion.dev/helix/docs/using-helix-with-an-agent
@@ -197,13 +230,12 @@ for the API.
197
230
  | GitHub Copilot | `.github/copilot-instructions.md` |
198
231
  | Anything else | Whatever it reads. The line is the same |
199
232
 
200
- **We do not write those files for you**, and that is deliberate rather than
201
- lazy: a file we generate into your repository is one you then
202
- maintain against our changes, and a tool that rewrites it on install is a
203
- dependency you did not ask for in a directory you own. The line above is yours to
204
- paste and yours to delete.
233
+ We do not write those files for you, and we chose this on purpose. You would
234
+ have to maintain a file we generated in your repository against our changes. A
235
+ tool that rewrote it on install would be a dependency you did not ask for, in a
236
+ directory you own. You paste the line above, and you can delete it.
205
237
 
206
- ## The one thing worth installing
238
+ ## Install the Helix MCP server
207
239
 
208
240
  ```json
209
241
  {
@@ -213,22 +245,22 @@ paste and yours to delete.
213
245
  }
214
246
  ```
215
247
 
216
- `helix mcp` is a subcommand of the CLI, speaking MCP over stdio, which Claude,
217
- Cursor, Windsurf and Zed all read. It answers the question no document can,
218
- which is what a given brand color derives to. It runs on your machine, talks to
219
- a client you chose, and has no network in it.
248
+ `helix mcp` is a subcommand of the CLI. It serves MCP over stdio, which Claude,
249
+ Cursor, Windsurf, and Zed all read. It computes the theme a given brand color
250
+ produces, which no document can list. It runs on your machine, talks to a client
251
+ you chose, and makes no network requests.
220
252
 
221
- ## What this page will not tell you
253
+ ## What this guide does not cover
222
254
 
223
- **Anything the manifest answers.** Property names, unions, part names, event
224
- names and hooks change with the library and are in a file you can fetch; a list
225
- of them here would be a second copy going stale. Fetch the family.
255
+ **Anything in the manifest.** Property names, unions, part names, event names,
256
+ and hooks change with the library, and they are in a file you can fetch. A list
257
+ of them here would be a second copy that goes stale. Fetch the family.
226
258
 
227
- **What a brand color derives to.** That is computation rather than a document,
228
- and `helix mcp` is where it lives: an MCP server over stdio with
229
- `helix_audit_theme` and `helix_derive_theme`, both taking the JSON a
230
- `helix.theme.json` holds. It runs on your machine and nobody hosts it.
259
+ **The theme a brand color produces.** Helix computes it, so use `helix mcp`, an
260
+ MCP server over stdio with `helix_audit_theme` and `helix_derive_theme`. Both
261
+ take the same JSON that a `helix.theme.json` file holds. It runs on your
262
+ machine, and we do not host it.
231
263
 
232
- **Whether a component is good for your case.** The examples are the honest
233
- answer to that and they are text: every family's `.md` carries them, and
234
- `https://fusion.dev/helix/sandbox?e=<family>.<slug>` opens one editable.
264
+ **Whether a component fits your case.** Read the examples to decide. Every
265
+ family's `.md` file includes them as text, and
266
+ `https://fusion.dev/helix/sandbox?e=<family>.<slug>` opens one for editing.