@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.
- package/LICENSE +21 -0
- package/content/accessibility.mdx +117 -0
- package/content/angular.mdx +60 -0
- package/content/browser-support.mdx +83 -0
- package/content/building-on-the-headless-tier.mdx +143 -0
- package/content/charting.mdx +93 -0
- package/content/coding-agents.mdx +139 -0
- package/content/color-modes.mdx +87 -0
- package/content/color.mdx +92 -0
- package/content/composition.mdx +171 -0
- package/content/elevation-and-material.mdx +93 -0
- package/content/events.mdx +125 -0
- package/content/forms.mdx +170 -0
- package/content/iconography.mdx +80 -0
- package/content/installation.mdx +125 -0
- package/content/intent-reference.mdx +107 -0
- package/content/introduction.mdx +69 -0
- package/content/localization.mdx +95 -0
- package/content/metrics.mdx +399 -0
- package/content/motion.mdx +91 -0
- package/content/other-frameworks.mdx +70 -0
- package/content/overlays.mdx +102 -0
- package/content/overriding-a-component.mdx +160 -0
- package/content/react.mdx +144 -0
- package/content/recipes.mdx +131 -0
- package/content/routing.mdx +208 -0
- package/content/sandbox.mdx +76 -0
- package/content/server-rendering.mdx +103 -0
- package/content/shape-and-borders.mdx +80 -0
- package/content/size-and-density.mdx +86 -0
- package/content/space-and-size.mdx +89 -0
- package/content/state-and-control.mdx +132 -0
- package/content/surfaces-and-separation.mdx +99 -0
- package/content/svelte.mdx +45 -0
- package/content/theme-builder.mdx +66 -0
- package/content/theming.mdx +111 -0
- package/content/tiers.mdx +159 -0
- package/content/tokens.mdx +88 -0
- package/content/typography.mdx +117 -0
- package/content/using-helix-with-an-agent.mdx +231 -0
- package/content/using-the-elements.mdx +125 -0
- package/content/versioning.mdx +72 -0
- package/content/visual-axes.mdx +75 -0
- package/content/vue.mdx +80 -0
- package/content/without-a-bundler.mdx +119 -0
- package/dist/catalog.d.ts +186 -0
- package/dist/catalog.js +296 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/naming.d.ts +14 -0
- package/dist/naming.js +28 -0
- package/dist/pages.generated.d.ts +7 -0
- package/dist/pages.generated.js +284 -0
- package/dist/reference.d.ts +105 -0
- package/dist/reference.js +181 -0
- package/dist/registry.d.ts +89 -0
- package/dist/registry.js +70 -0
- package/dist/sections.d.ts +44 -0
- package/dist/sections.js +36 -0
- package/dist/sources.generated.d.ts +6 -0
- package/dist/sources.generated.js +5158 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fusion Development Group
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: What the library guarantees, what it cannot, and where the line is.
|
|
3
|
+
section: Foundations
|
|
4
|
+
title: Accessibility
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Accessibility
|
|
8
|
+
|
|
9
|
+
Some of accessibility is structural, and a component can own it. Some of it is
|
|
10
|
+
about your content, and no component can. Knowing which is which is the
|
|
11
|
+
difference between a page that is accessible and a page built out of accessible
|
|
12
|
+
parts.
|
|
13
|
+
|
|
14
|
+
## What the library owns
|
|
15
|
+
|
|
16
|
+
**Roles, states and keyboard interaction.** Every component implements the
|
|
17
|
+
pattern its role implies: arrow keys move within a `Tabs`, a `Listbox` and a
|
|
18
|
+
`Menu`, `Escape` dismisses an overlay, `Home` and `End` go to the ends, typing
|
|
19
|
+
jumps to a match. None of that is yours to write.
|
|
20
|
+
|
|
21
|
+
**Focus, including trapping and restoring it.** A `Dialog` traps focus while
|
|
22
|
+
open and returns it to whatever opened it. So do `Drawer` and the modal path of
|
|
23
|
+
`Popover`.
|
|
24
|
+
|
|
25
|
+
**Contrast, by construction rather than by review.** The theme engine measures
|
|
26
|
+
every foreground against the ground it is really painted on and solves for the
|
|
27
|
+
target rather than checking afterwards. Every pairing is then re-measured on a
|
|
28
|
+
laid-out page, including the great majority of rules that set an ink or a ground
|
|
29
|
+
but not both, so a pairing nobody thought of is still checked. See [Surfaces and
|
|
30
|
+
separation](./surfaces-and-separation).
|
|
31
|
+
|
|
32
|
+
**Reduced motion, per component.** Fifty-nine stylesheets answer
|
|
33
|
+
`prefers-reduced-motion` individually, because they have to: the base layer's
|
|
34
|
+
global rule is `*, *::before, *::after` in the document, and `*` does not cross
|
|
35
|
+
a shadow boundary, so it reaches your prose and none of the components.
|
|
36
|
+
A component that animates without answering fails the build.
|
|
37
|
+
|
|
38
|
+
**Disabled without disappearing.** `disabled` is mirrored to `aria-disabled`
|
|
39
|
+
rather than removing the control from the accessibility tree, so a screen reader
|
|
40
|
+
can still announce that the control exists and is currently off.
|
|
41
|
+
|
|
42
|
+
## What you own
|
|
43
|
+
|
|
44
|
+
**Names.** A component can render a label; it cannot know what to call your
|
|
45
|
+
thing. Every control takes `label`, and an icon-only control has nothing else to
|
|
46
|
+
go on:
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<helix-icon-button
|
|
50
|
+
label="Close"
|
|
51
|
+
name="x"
|
|
52
|
+
></helix-icon-button>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**`label` means a label and `aria-label` means a name.** They are not synonyms
|
|
56
|
+
here. `label` renders visible text and names the control through it;
|
|
57
|
+
`aria-label` names the control with nothing on screen. Reach for the second only
|
|
58
|
+
when the page already makes the purpose plain, which is rarer than it feels.
|
|
59
|
+
|
|
60
|
+
**Heading order.** Components render the heading levels you give them, not
|
|
61
|
+
levels they guess. A `Card` does not emit an `h2` on your behalf, because it
|
|
62
|
+
cannot know what is above it.
|
|
63
|
+
|
|
64
|
+
**Language and direction.** Set `lang` and `dir` on the document. Components use
|
|
65
|
+
logical properties throughout, so a right-to-left document lays out correctly,
|
|
66
|
+
but nothing infers the direction for you.
|
|
67
|
+
See [Localization and RTL](./localization).
|
|
68
|
+
|
|
69
|
+
**Live regions for your own updates.** The components announce their own state
|
|
70
|
+
changes. A message from your application needs somewhere to be announced:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<helix-announcement tone="warning">Your trial ends on Friday.</helix-announcement>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`Announcement` is always a polite live region, never assertive. `role="alert"`
|
|
77
|
+
interrupts whatever is being read, which is right for something that appears
|
|
78
|
+
mid-session and wrong for something that was in the served HTML: an assertive
|
|
79
|
+
announcement would cut across a screen-reader user on every page load, before
|
|
80
|
+
they had read anything. Politeness costs a moment and is never wrong.
|
|
81
|
+
|
|
82
|
+
Note the distinction from `Callout`, which is a passage that interrupts a
|
|
83
|
+
document and is read where it sits. Neither is a variant of the other.
|
|
84
|
+
|
|
85
|
+
## Two things the shadow boundary changes
|
|
86
|
+
|
|
87
|
+
**IDREFs do not cross it.** `aria-labelledby` and `aria-describedby` inside a
|
|
88
|
+
shadow root cannot resolve to an id in your document, and the reference is
|
|
89
|
+
dropped silently rather than erroring. This is why every control renders its own
|
|
90
|
+
label instead of being wrapped by one, and why wiring two components together by
|
|
91
|
+
id does not work. See [Forms and validation](./forms).
|
|
92
|
+
|
|
93
|
+
**`<label for>` cannot reach in either.** A `<label>` in your markup will not
|
|
94
|
+
name a Helix control. Use the control's own `label`.
|
|
95
|
+
|
|
96
|
+
Where a relationship genuinely has to cross the boundary, the components use
|
|
97
|
+
`ElementInternals` and element-reference properties rather than ids, which is
|
|
98
|
+
the only mechanism that works. If you need the same for your own composition,
|
|
99
|
+
that is the tool.
|
|
100
|
+
|
|
101
|
+
## Hiding things correctly
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<span data-hidden-visually>Sorting by</span>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The base layer's `[data-hidden-visually]` clips rather than using
|
|
108
|
+
`display: none` or `visibility: hidden`, both of which take the element out of
|
|
109
|
+
the accessibility tree entirely, which is the opposite of what visually-hidden
|
|
110
|
+
text is for.
|
|
111
|
+
|
|
112
|
+
## What is not covered
|
|
113
|
+
|
|
114
|
+
The library is tested in Chromium, by automated specs and by pixel baselines. It
|
|
115
|
+
is not run through a screen reader on every release, and the components'
|
|
116
|
+
patterns follow the ARIA authoring practices rather than being certified against
|
|
117
|
+
them. Where you find a real failure, it is a bug: report it.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The schema Angular needs before it will compile an unknown tag.
|
|
3
|
+
parent: installation
|
|
4
|
+
section: Getting started
|
|
5
|
+
title: Angular
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Angular
|
|
9
|
+
|
|
10
|
+
Angular refuses to compile a template containing a tag it does not recognize.
|
|
11
|
+
That is a good default and it is the one thing standing between you and working
|
|
12
|
+
Helix components.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'
|
|
16
|
+
|
|
17
|
+
@Component({
|
|
18
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
19
|
+
standalone: true,
|
|
20
|
+
template: `<helix-button>Save</helix-button>`,
|
|
21
|
+
})
|
|
22
|
+
export class SaveButton {}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Declare it on the components that use Helix rather than globally if you can.
|
|
26
|
+
`CUSTOM_ELEMENTS_SCHEMA` switches off unknown-element checking for the whole
|
|
27
|
+
template it is on, so scoping it keeps the check for your own typos.
|
|
28
|
+
|
|
29
|
+
## Binding
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<helix-input
|
|
33
|
+
(change)="name = $event.target.value"
|
|
34
|
+
[value]="name"
|
|
35
|
+
/>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`[prop]` sets a property, which is what a non-string value needs, and `(event)`
|
|
39
|
+
binds with `addEventListener`, so the event name is its own.
|
|
40
|
+
|
|
41
|
+
**Do not use `attr.` for a stateful property.** `[attr.value]="name"` writes
|
|
42
|
+
the attribute, which seeds the control and is then out of date the moment a
|
|
43
|
+
reader types; the property is the live channel.
|
|
44
|
+
|
|
45
|
+
## No typings ship for Angular
|
|
46
|
+
|
|
47
|
+
There is no `angular.d.ts` in the package. `CUSTOM_ELEMENTS_SCHEMA` turns off
|
|
48
|
+
the checking that typings would feed, so a generated file would describe types
|
|
49
|
+
Angular has already agreed not to enforce. The manifest is published as
|
|
50
|
+
`@fusion.dev/helix/custom-elements.json` if you want to generate something for your
|
|
51
|
+
own codebase from it.
|
|
52
|
+
|
|
53
|
+
The practical consequence is that a misspelled property fails silently here
|
|
54
|
+
where it is a compile error in React, Vue and Svelte. Worth a note in review.
|
|
55
|
+
|
|
56
|
+
## Taking a decision
|
|
57
|
+
|
|
58
|
+
`value`, `open`, `checked` and `active` are seeds: the element applies its own
|
|
59
|
+
changes from there. To take one decision, listen for the `helix-before-*` event
|
|
60
|
+
and call `preventDefault()`. See [State and control](./state-and-control).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The platform features this is built on, and what is actually tested.
|
|
3
|
+
order: 5
|
|
4
|
+
section: Getting started
|
|
5
|
+
title: Browser support
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Browser support
|
|
9
|
+
|
|
10
|
+
Helix is built on the platform rather than on a compatibility layer. There is no
|
|
11
|
+
polyfill story, no transpiled fallback, and no configuration that trades
|
|
12
|
+
features for reach: what the library needs is what your browser has to have.
|
|
13
|
+
|
|
14
|
+
## What is tested
|
|
15
|
+
|
|
16
|
+
**Chromium, through Playwright.** The DOM specs run in it, and 83 pixel
|
|
17
|
+
baselines are captured in it, on Linux, with the network blocked.
|
|
18
|
+
|
|
19
|
+
That is the honest extent of it. Everything below is a statement about which
|
|
20
|
+
platform features the library uses, not a claim that we run the suite in Firefox
|
|
21
|
+
and Safari. If you ship Helix to those and find something broken, that is a bug
|
|
22
|
+
report we want, and it is not one we would have caught.
|
|
23
|
+
|
|
24
|
+
## What the library uses
|
|
25
|
+
|
|
26
|
+
| Feature | Where |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Custom elements and shadow DOM | Everything |
|
|
29
|
+
| `ElementInternals` | Form participation, and ARIA that has to cross a shadow boundary |
|
|
30
|
+
| Popover API | Every overlay: menu, tooltip, select, combobox, dialog |
|
|
31
|
+
| `light-dark()` | 761 declarations, 135 of them in the token layer |
|
|
32
|
+
| `oklch()` | Every color in the system |
|
|
33
|
+
| `:has()` | 11 rules, mostly relational styling of slotted content |
|
|
34
|
+
| `@starting-style` | Entry transitions on overlays |
|
|
35
|
+
| `:state()` | Every boolean state the library publishes: `open`, `checked`, `selected`, `invalid`, `read`, `has-label`, `settled`, `empty` |
|
|
36
|
+
| `CSS.highlights` | Filter-match highlighting, in `Command` and `Combobox` |
|
|
37
|
+
|
|
38
|
+
Positioning is `@floating-ui/dom` rather than CSS anchor positioning, which is a
|
|
39
|
+
deliberate choice and the one place the library takes a dependency instead of
|
|
40
|
+
the platform: anchor positioning is not yet available everywhere the rest of
|
|
41
|
+
this list is.
|
|
42
|
+
|
|
43
|
+
## What decides your floor
|
|
44
|
+
|
|
45
|
+
**`light-dark()` and the Popover API.** Everything else on that list has been
|
|
46
|
+
available longer. If you need to know whether a browser can run Helix, those are
|
|
47
|
+
the two to check, because they are the newest things the library assumes and
|
|
48
|
+
they are load-bearing: the first is how every token expresses both color modes
|
|
49
|
+
in one declaration, and the second is how every overlay is drawn in the top
|
|
50
|
+
layer.
|
|
51
|
+
|
|
52
|
+
`:state()` is the third and is deliberately not with them, because it does
|
|
53
|
+
degrade: a component that cannot record a state keeps whatever its resting rule
|
|
54
|
+
draws, so a checkbox is drawn unchecked rather than drawn wrong or not at all.
|
|
55
|
+
It is feature-tested at the one place that records any of them.
|
|
56
|
+
|
|
57
|
+
Neither degrades usefully. A browser without `light-dark()` gets no color at
|
|
58
|
+
all rather than one mode; a browser without popover gets overlays that render in
|
|
59
|
+
flow. This is why there is no support table with a fallback column: there is no
|
|
60
|
+
partial mode to be in.
|
|
61
|
+
|
|
62
|
+
## `CSS.highlights` is the one exception
|
|
63
|
+
|
|
64
|
+
`Command` and `Combobox` highlight the matched substring in their filtered
|
|
65
|
+
results using the CSS Custom Highlight API. It is the least widely available
|
|
66
|
+
thing here, and it is the only one that degrades gracefully: without it the
|
|
67
|
+
results still filter, sort and select correctly, and the match is simply not
|
|
68
|
+
highlighted.
|
|
69
|
+
|
|
70
|
+
Nothing else in the library reads it.
|
|
71
|
+
|
|
72
|
+
## No IE, no polyfills, no build flag
|
|
73
|
+
|
|
74
|
+
There is no `browserslist` and no transpilation target, which is a decision
|
|
75
|
+
rather than an oversight. A design system whose components are
|
|
76
|
+
custom elements cannot be shipped to a browser without custom elements, and
|
|
77
|
+
everything else on the list above arrived in the same generation of browsers
|
|
78
|
+
that shipped those.
|
|
79
|
+
|
|
80
|
+
If you need to support something older, the honest answer is the base layer:
|
|
81
|
+
`@fusion.dev/helix-styles` is plain CSS with no JavaScript, and a page built from its
|
|
82
|
+
recipes works considerably further back than the components do.
|
|
83
|
+
See [Without a bundler](./without-a-bundler).
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Using the headless tier as the foundation for a design system of your own.
|
|
3
|
+
section: Essentials
|
|
4
|
+
title: Building on the headless tier
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Building on the headless tier
|
|
8
|
+
|
|
9
|
+
The headless tier is a set of components with no stylesheet: structure, ARIA,
|
|
10
|
+
keyboard behavior, state and form participation, and nothing about how any of it
|
|
11
|
+
looks. That makes it usable as a foundation rather than as a library you consume
|
|
12
|
+
whole. Your tags, your stylesheets, our accessibility.
|
|
13
|
+
|
|
14
|
+
This is a different thing from theming Helix. Theming keeps our components and
|
|
15
|
+
changes their tokens. This keeps our behavior and replaces the presentation
|
|
16
|
+
layer outright, under names of your own.
|
|
17
|
+
|
|
18
|
+
## One line does it
|
|
19
|
+
|
|
20
|
+
Every headless component renders its children by interpolating a tag rather than
|
|
21
|
+
naming one, and the prefix comes from a static on the class:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { HelixHeadlessCombobox } from '@fusion.dev/helix-headless/combobox'
|
|
25
|
+
|
|
26
|
+
export class AcmeCombobox extends HelixHeadlessCombobox {
|
|
27
|
+
static override tier = 'acme-'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
customElements.define('acme-combobox', AcmeCombobox)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That is the whole mechanism, and it is the same one the styled tier uses: it
|
|
34
|
+
writes `static override tier = 'helix-'` and nothing else. The static appears 116
|
|
35
|
+
times in the headless manifest, so it is discoverable rather than folklore.
|
|
36
|
+
|
|
37
|
+
## The render graph is the part that will catch you
|
|
38
|
+
|
|
39
|
+
**A component that renders another gives you two things to register, and only
|
|
40
|
+
one of them is obvious.** Twenty-two of the 88 headless families interpolate
|
|
41
|
+
another family's tag, across 89 call sites and seventeen distinct children.
|
|
42
|
+
|
|
43
|
+
Register `acme-combobox` without also registering `acme-listbox` and the panel
|
|
44
|
+
renders a tag nobody defined. That is not an error and not a warning: an
|
|
45
|
+
unregistered custom element is an inert, unstyled inline box, so what you see is
|
|
46
|
+
a combobox that appears not to open.
|
|
47
|
+
|
|
48
|
+
| Family | Renders |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `bar-chart`, `donut-chart`, `heatmap`, `line-chart` | `tooltip` |
|
|
51
|
+
| `code-block` | `copy-button` |
|
|
52
|
+
| `color-input` | `color-picker` |
|
|
53
|
+
| `color-picker` | `button`, `button-group`, `slider` |
|
|
54
|
+
| `combobox` | `listbox`, `tag` |
|
|
55
|
+
| `command` | `dialog`, `listbox` |
|
|
56
|
+
| `copy-button` | `tooltip` |
|
|
57
|
+
| `data-table` | `pagination` |
|
|
58
|
+
| `date-input` | `date-picker`, `tag` |
|
|
59
|
+
| `file-input` | `tag` |
|
|
60
|
+
| `notifications` | `format-date` |
|
|
61
|
+
| `pagination` | `option`, `select` |
|
|
62
|
+
| `select` | `listbox` |
|
|
63
|
+
| `steps`, `tour` | `button` |
|
|
64
|
+
| `time-input` | `tag`, `time-picker` |
|
|
65
|
+
| `time-picker` | `listbox`, `option` |
|
|
66
|
+
| `video`, `video-playlist` | `menu`, `menu-item` |
|
|
67
|
+
|
|
68
|
+
The safe rule is to subclass and register a family together with everything it
|
|
69
|
+
renders, transitively: `command` reaches `dialog` and `listbox`, and `listbox`
|
|
70
|
+
reaches `option`.
|
|
71
|
+
|
|
72
|
+
**This table is maintained by hand today and should not be.** The graph is
|
|
73
|
+
already computed inside this repository, by the check that stops us shipping the
|
|
74
|
+
same fault, and it is thrown away afterward rather than written to the manifest.
|
|
75
|
+
Until it is published, treat the table above as the version that can go stale.
|
|
76
|
+
|
|
77
|
+
## What you have to supply yourself
|
|
78
|
+
|
|
79
|
+
**A `:not(:defined)` guard.** `@fusion.dev/helix` ships an `elements.css` that
|
|
80
|
+
hides each of its tags until the definition lands, so a page does not show a run
|
|
81
|
+
of unstyled text and then snap. The headless package ships no such file, and
|
|
82
|
+
nothing can generate one for tags you have not named yet. Write the rule for
|
|
83
|
+
your own elements:
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
acme-combobox:not(:defined),
|
|
87
|
+
acme-listbox:not(:defined) {
|
|
88
|
+
visibility: hidden;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Visibility rather than `display`, so the page does not reflow when the
|
|
93
|
+
definitions arrive.
|
|
94
|
+
|
|
95
|
+
**Your own typings.** The generated `jsx.d.ts`, `vue.d.ts` and `svelte.d.ts`
|
|
96
|
+
carry our tag names. For yours, merge each element into the tag map:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
declare global {
|
|
100
|
+
interface HTMLElementTagNameMap {
|
|
101
|
+
'acme-combobox': AcmeCombobox
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Per element, by hand. There is no generated version of this for consumer names.
|
|
107
|
+
|
|
108
|
+
## What you get without asking
|
|
109
|
+
|
|
110
|
+
**Tree-shaking is honest.** `@fusion.dev/helix-headless` declares
|
|
111
|
+
`sideEffects: ["./dist/**/*.js"]`, which is true rather than convenient: the
|
|
112
|
+
modules register elements on import, so a bundler that believed otherwise would
|
|
113
|
+
drop a registration and leave you debugging a tag that does not upgrade.
|
|
114
|
+
|
|
115
|
+
**A styling surface that is documented.** Every part carries an `@csspart`, so
|
|
116
|
+
`::part()` is a published door rather than a shadow tree you have to guess at.
|
|
117
|
+
That is what makes replacing the presentation layer tractable: you are writing
|
|
118
|
+
against named parts, not against our internals.
|
|
119
|
+
|
|
120
|
+
**Composition stays ours.** Focus management, typeahead, the roving tab stop,
|
|
121
|
+
form participation and validity all live below the tier you are replacing, so
|
|
122
|
+
they keep working under your names.
|
|
123
|
+
|
|
124
|
+
## What is not promised yet
|
|
125
|
+
|
|
126
|
+
`tier` is an implementation detail that happens to be visible in the manifest.
|
|
127
|
+
Using it the way this page describes turns it into API with a stability contract
|
|
128
|
+
attached, and that contract is not written. Treat this as a supported pattern
|
|
129
|
+
that is not yet a versioned promise, and pin your Helix version.
|
|
130
|
+
|
|
131
|
+
## How this differs from white-labeling
|
|
132
|
+
|
|
133
|
+
Two different customers get confused for each other here. This page is for the
|
|
134
|
+
one building a design system on top of ours and content for our tags to sit
|
|
135
|
+
beside theirs. The other wants Helix shipped under their name with ours absent,
|
|
136
|
+
which is a different and more expensive mechanism: rewriting the tag literals at
|
|
137
|
+
build time, a second distribution to publish, and an artifact per customer.
|
|
138
|
+
Nothing on this page needs any of that.
|
|
139
|
+
|
|
140
|
+
The sovereignty question answers itself in this direction. You hold the
|
|
141
|
+
presentation layer, the tags and the build. If Fusion stopped tomorrow you would
|
|
142
|
+
keep a working design system and lose upstream fixes to the behavior underneath
|
|
143
|
+
it.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The data channel nobody expects, and why it is a real table.
|
|
3
|
+
section: Essentials
|
|
4
|
+
title: Charting
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Charting
|
|
8
|
+
|
|
9
|
+
Six chart families: `Bar Chart`, `Line Chart`, `Donut Chart`, `Sparkline`,
|
|
10
|
+
`Trend` and the shared `Legend`. Each has its own API page. Here is how the data
|
|
11
|
+
gets in, which is the same for all of them.
|
|
12
|
+
|
|
13
|
+
## The data is a table in the document
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<table id="revenue">
|
|
17
|
+
<caption>Revenue by quarter</caption>
|
|
18
|
+
<thead>
|
|
19
|
+
<tr><th>Quarter</th><th>Direct</th><th>Partner</th></tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
<tr><th>Q1</th><td>120</td><td>80</td></tr>
|
|
23
|
+
<tr><th>Q2</th><td>150</td><td>95</td></tr>
|
|
24
|
+
</tbody>
|
|
25
|
+
</table>
|
|
26
|
+
|
|
27
|
+
<helix-bar-chart for="revenue"></helix-bar-chart>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`for` points at a real `<table>` by id, and the chart reads its series from it.
|
|
31
|
+
That is unlike every other component in the library, and it is not a
|
|
32
|
+
convenience.
|
|
33
|
+
|
|
34
|
+
**A property cannot survive hydration.** A framework's server render emits
|
|
35
|
+
markup, and a property is not markup: an array assigned to `items` exists only
|
|
36
|
+
after JavaScript has run, so the server has nothing to send and the first paint
|
|
37
|
+
has nothing to draw. Text in the document is the one channel that both a server
|
|
38
|
+
and a client can see.
|
|
39
|
+
|
|
40
|
+
**And the table is not a workaround, it is the accessible version.** A chart is
|
|
41
|
+
a picture of numbers, and a screen reader cannot read a picture. The table is
|
|
42
|
+
the numbers, in the document, navigable, and the chart is the drawing of them.
|
|
43
|
+
Hide the table visually if it should not be seen, and the data is still there:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<table
|
|
47
|
+
data-hidden-visually
|
|
48
|
+
id="revenue"
|
|
49
|
+
>…</table>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The caption becomes the chart's accessible name unless you set `label`, which is
|
|
53
|
+
usually the right words and is already written.
|
|
54
|
+
|
|
55
|
+
## Series colours
|
|
56
|
+
|
|
57
|
+
Twenty-four `--helix-color-series-*` tokens, in order. A chart with three series
|
|
58
|
+
uses the first three. They are ordered to stay distinguishable in that order
|
|
59
|
+
rather than to look good as a full set of twenty-four.
|
|
60
|
+
|
|
61
|
+
The palette wraps, and where it wraps is one number in one place. It used to be
|
|
62
|
+
four: four families held the same `const PALETTE = 24` and the legend held a
|
|
63
|
+
fifth, so when the palette went from eight to twenty-four one of them was missed
|
|
64
|
+
and drew every ninth series in the first color while the legend beside it named
|
|
65
|
+
the ninth correctly. A number two files have to agree on is a number that will
|
|
66
|
+
stop agreeing.
|
|
67
|
+
|
|
68
|
+
**A chart's mark carries its own series color as a custom property**, because a
|
|
69
|
+
chart knows which series a bar belongs to and CSS cannot count it. That is what
|
|
70
|
+
lets you restyle a series from outside without knowing the drawing order.
|
|
71
|
+
|
|
72
|
+
## Legends
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<helix-legend for="revenue"></helix-legend>
|
|
76
|
+
<helix-bar-chart for="revenue"></helix-bar-chart>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The legend reads the same table, so the names and the colours cannot disagree
|
|
80
|
+
with the chart's. Pointing them at two different tables is the only way to make
|
|
81
|
+
them disagree, and it is not something to do by accident.
|
|
82
|
+
|
|
83
|
+
## Reduced motion
|
|
84
|
+
|
|
85
|
+
Charts animate on entry and on data change, and every one of them answers
|
|
86
|
+
`prefers-reduced-motion`. See [Motion](./motion).
|
|
87
|
+
|
|
88
|
+
## What these are not
|
|
89
|
+
|
|
90
|
+
**Not a charting library.** There is no axis configuration language, no plugin
|
|
91
|
+
system, and no chart type you build by composing scales. Six drawings that read
|
|
92
|
+
a table, styled by the token system, with an accessible representation by
|
|
93
|
+
construction. When you need a grammar of graphics, use one.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Every surface here is readable by a machine, and where each one is.
|
|
3
|
+
order: 4
|
|
4
|
+
section: Getting started
|
|
5
|
+
title: Coding agents
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Coding agents
|
|
9
|
+
|
|
10
|
+
An agent writing Helix code should not have to read a rendered website. Three
|
|
11
|
+
surfaces give it the library as text, all generated from the sources this site
|
|
12
|
+
renders from.
|
|
13
|
+
|
|
14
|
+
## The manifest
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
https://fusion.dev/helix/custom-elements.json
|
|
18
|
+
@fusion.dev/helix/custom-elements.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A URL and a package path, the same file either way. **The URL exists because this
|
|
22
|
+
page's own argument required it**: an agent that found the library through
|
|
23
|
+
`llms.txt` has this site and no checkout, and a specifier is reachable from a
|
|
24
|
+
`node_modules` and from nowhere else. It was only the specifier until 24 Aug 2026,
|
|
25
|
+
and the specifier did not resolve either: `exports` carried a `./*` pattern
|
|
26
|
+
pointing at `./dist/*/index.js`, so the path two pages recommended was one
|
|
27
|
+
`require.resolve` refused.
|
|
28
|
+
|
|
29
|
+
Every element, every property with its literal type union, every event with its
|
|
30
|
+
class, every slot, every part, every custom property. It is what the framework
|
|
31
|
+
typings, the reference documentation and the site's component pages are all
|
|
32
|
+
generated from, so it cannot disagree with them.
|
|
33
|
+
|
|
34
|
+
**A generated manifest is what makes hallucinating a component impossible to
|
|
35
|
+
sustain.** The vocabulary is the file: a tag that is not in it does not exist,
|
|
36
|
+
and a prop value outside a declared union is a value the component will reject.
|
|
37
|
+
|
|
38
|
+
Literal unions rather than named types are enforced at analysis time for exactly
|
|
39
|
+
this reason: `'ghost' | 'outlined' | 'solid' | 'subtle'` tells a reader what to
|
|
40
|
+
write, where `ButtonEmphasis` tells them to go and look somewhere else.
|
|
41
|
+
|
|
42
|
+
## Markdown for every family
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
/components/<family>.md
|
|
46
|
+
/components/<family>.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Static files. The `.md` carries the family's metadata, every element's
|
|
50
|
+
description and API tables, and **the examples**, which the HTML page
|
|
51
|
+
deliberately does not: that page answers what a component can do and the sandbox
|
|
52
|
+
beside it answers what it looks like, and a reference that started collecting
|
|
53
|
+
pictures would stop being the one place you can trust to be complete. An agent
|
|
54
|
+
has no sandbox to be sent to, and to it an example is text rather than a
|
|
55
|
+
picture.
|
|
56
|
+
|
|
57
|
+
The `.json` is the same family as structured data.
|
|
58
|
+
|
|
59
|
+
## What is enforced
|
|
60
|
+
|
|
61
|
+
These are the difference between code that looks right and code that passes:
|
|
62
|
+
|
|
63
|
+
| Rule | Check |
|
|
64
|
+
|---|---|
|
|
65
|
+
| A dispatched event must be declared | `check:events` |
|
|
66
|
+
| A documented part must be rendered | `check:parts` |
|
|
67
|
+
| A documented CSS hook must be read | `check:hooks` |
|
|
68
|
+
| A visual property draws from its axis | `check:axes` |
|
|
69
|
+
| A form control styles with `:disabled` | `check:disabled` |
|
|
70
|
+
| A component that animates answers reduced motion | `check:motion` |
|
|
71
|
+
| No component reads a primitive token | `check:tokens` |
|
|
72
|
+
|
|
73
|
+
If you are generating Helix code, the first two are the ones that produce
|
|
74
|
+
working-looking output that is wrong.
|
|
75
|
+
|
|
76
|
+
## The index
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
/llms.txt
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Every page on the site, grouped, each one linked at its `.md` and annotated with
|
|
83
|
+
a sentence. Generated from the same route registry the sitemap comes from, so a
|
|
84
|
+
page cannot exist without appearing here and cannot appear here without
|
|
85
|
+
existing.
|
|
86
|
+
|
|
87
|
+
Start there rather than crawling: one request, and it names every page on the
|
|
88
|
+
site plus the two machine-readable surfaces above. No count here, because a
|
|
89
|
+
number typed into prose is a number that goes stale, which the read of these pages
|
|
90
|
+
on 24 Aug 2026 found five of.
|
|
91
|
+
|
|
92
|
+
## From a component's page
|
|
93
|
+
|
|
94
|
+
Every family page carries a menu beside its title with **View Markdown**, **Copy
|
|
95
|
+
Markdown**, and links that hand the document's URL to Claude or to Cursor. The
|
|
96
|
+
URL rather than the contents, because the URL stays current and a pasted copy
|
|
97
|
+
starts going stale the moment it is pasted.
|
|
98
|
+
|
|
99
|
+
## The one thing a document cannot answer
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
helix mcp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The three surfaces above are closed sets: every tag, every token, every example,
|
|
106
|
+
all published and all fetchable. What none of them holds is what *your* brand
|
|
107
|
+
color derives to, because the input is a color and the domain is infinite, so the
|
|
108
|
+
answer has to be computed when it is asked.
|
|
109
|
+
|
|
110
|
+
`helix mcp` serves that over stdio as an MCP server, with two tools:
|
|
111
|
+
`helix_audit_theme`, which measures every foreground against the ground it is
|
|
112
|
+
really painted on and reports each pair with its ratio and target, and
|
|
113
|
+
`helix_derive_theme`, which returns the CSS. Both take the same JSON a
|
|
114
|
+
`helix.theme.json` holds.
|
|
115
|
+
|
|
116
|
+
**It refuses to hand back a palette that fails its own targets**, the way
|
|
117
|
+
`helix theme build` does. Pasting one into a stylesheet is worse than reading a
|
|
118
|
+
warning above it, and an agent does not read warnings.
|
|
119
|
+
|
|
120
|
+
**You run it and nobody hosts it.** A server we operated would be a dependency
|
|
121
|
+
you cannot exit, which the Sovereignty Constraint refuses; this is a subcommand
|
|
122
|
+
of a binary you installed, talking to a client you chose, with no network in it.
|
|
123
|
+
|
|
124
|
+
## What is not here
|
|
125
|
+
|
|
126
|
+
**`llms-full.txt`, or any other whole-library download.** Concatenating every
|
|
127
|
+
page into one file is out of date the moment anything changes, where the links
|
|
128
|
+
in `llms.txt` are individually cacheable and individually current.
|
|
129
|
+
|
|
130
|
+
This page used to offer one anyway, a repository path to a generated document
|
|
131
|
+
holding every family at once, which contradicted the paragraph above it and was
|
|
132
|
+
not a thing a consumer could open: an agent reading the published package has
|
|
133
|
+
this site and no checkout. What it was for is covered better by the two surfaces
|
|
134
|
+
already here. `llms.txt` is the index, one request, and each family's `.md` is
|
|
135
|
+
the document. Fetch the families you are using.
|
|
136
|
+
|
|
137
|
+
**A search index.** The site has one for humans; there is nothing an agent can
|
|
138
|
+
query yet, and `llms.txt` plus the documents it names is a smaller thing to read
|
|
139
|
+
than most search APIs are to learn.
|