@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
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: What Helix costs a page, measured rather than claimed.
|
|
3
|
+
order: 7
|
|
4
|
+
section: Getting started
|
|
5
|
+
title: Metrics
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Metrics
|
|
9
|
+
|
|
10
|
+
*Generated by `bun run metrics` from the built packages. Do not edit: every
|
|
11
|
+
number here is measured, and an edited one is a number nothing produced.*
|
|
12
|
+
|
|
13
|
+
Bundle size, and what a component costs in time. The two are kept apart on
|
|
14
|
+
purpose: bytes are falsifiable across systems and belong in the comparison,
|
|
15
|
+
and a render timing is a fact about a page, a machine and a day, so it never
|
|
16
|
+
joins one.
|
|
17
|
+
|
|
18
|
+
## How these are measured
|
|
19
|
+
|
|
20
|
+
The method is on the page because a size table without one is a claim rather
|
|
21
|
+
than a measurement.
|
|
22
|
+
|
|
23
|
+
- **The published artifact, not the source.** Each row bundles `dist`, which is what npm ships, through Bun 1.1.43, minified, ESM, browser target, no source maps.
|
|
24
|
+
- **Everything is bundled in, Lit included.** A page with no other Lit library pays for Lit, so it is counted. Its own row is in the package table below, so it can be subtracted.
|
|
25
|
+
- **A package row re-exports the package; a component row imports it for effect.** That is how each is used: an element is imported to register itself, and a package is measured by its whole public API. Importing a `sideEffects: false` package for effect measures nothing, because a bundler is right to delete it.
|
|
26
|
+
- **Gzip is level 9 and brotli is quality 11**, which is what a CDN serving static assets does.
|
|
27
|
+
- **The bundler version is part of the number.** It is pinned in `packageManager` and in CI, so the same source measures the same in both places.
|
|
28
|
+
|
|
29
|
+
Two columns for every component, because there is no single honest number for
|
|
30
|
+
a component in a library with a shared runtime:
|
|
31
|
+
|
|
32
|
+
- **Alone** is a page that uses this component and nothing else. It includes the runtime the component cannot work without, so it counts that runtime again for every component after the first.
|
|
33
|
+
- **Added** is what this component costs a page that already has the runtime. It excludes what is shared, so it flatters the first component you install.
|
|
34
|
+
|
|
35
|
+
Neither is the real number. The pair brackets it.
|
|
36
|
+
|
|
37
|
+
## The floor
|
|
38
|
+
|
|
39
|
+
Lit and `@fusion.dev/helix-core`, which is what every element needs and no element
|
|
40
|
+
owns. **Added** is measured against this.
|
|
41
|
+
|
|
42
|
+
**It is larger than most single components below, and that is not a mistake.**
|
|
43
|
+
The floor re-exports the whole of `@fusion.dev/helix-core` so that it is the same size
|
|
44
|
+
under every family. A family that reaches only part of core shakes the rest
|
|
45
|
+
out and comes in smaller than the floor, which is the tree-shaking working:
|
|
46
|
+
nobody ever downloads this floor on its own.
|
|
47
|
+
|
|
48
|
+
| | Raw | Gzip | Brotli |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| Lit and `@fusion.dev/helix-core` | 132,955 | 44,397 | 38,019 |
|
|
51
|
+
|
|
52
|
+
## Whole packages
|
|
53
|
+
|
|
54
|
+
Everything a package exports, retained. This is the ceiling rather than a
|
|
55
|
+
typical page: `@fusion.dev/helix` and `@fusion.dev/helix-headless` are side-effectful, so
|
|
56
|
+
a barrel import of either really does cost the whole row, and nothing in this
|
|
57
|
+
repository does that. The per-component tables are what a page pays.
|
|
58
|
+
|
|
59
|
+
| Package | Raw | Gzip | Brotli | |
|
|
60
|
+
|---|---|---|---|---|
|
|
61
|
+
| `@fusion.dev/helix-core` | 132,509 | 43,947 | 37,675 | Element base class, mixins, decorators, floating positioning. |
|
|
62
|
+
| `@fusion.dev/helix-headless` | 516,161 | 131,048 | 102,771 | Every headless family, which is what the barrel means. |
|
|
63
|
+
| `@fusion.dev/helix-icons` | 46,295 | 14,855 | 12,542 | The whole glyph set. |
|
|
64
|
+
| `@fusion.dev/helix-theme` | 3,288 | 1,570 | 1,353 | The derivation engine. Build time for most consumers, runtime for a theme switcher. |
|
|
65
|
+
| `@fusion.dev/helix` | 964,531 | 218,272 | 160,502 | Every styled family. |
|
|
66
|
+
| `lit` | 15,394 | 6,118 | 5,460 | Not ours. Here so it can be subtracted from every row above it. |
|
|
67
|
+
|
|
68
|
+
## The stylesheets
|
|
69
|
+
|
|
70
|
+
Linked rather than bundled, so these are the files exactly as a browser
|
|
71
|
+
receives them. `@fusion.dev/helix-styles` ships its stylesheets with the comments
|
|
72
|
+
removed and keeps the reasoning in the repository, which is why these numbers
|
|
73
|
+
are a quarter of what the sources weigh. It has no JavaScript at all, so this
|
|
74
|
+
is the whole cost of adopting the base layer on its own.
|
|
75
|
+
|
|
76
|
+
| File | Raw | Gzip | Brotli | |
|
|
77
|
+
|---|---|---|---|---|
|
|
78
|
+
| `base.css` | 23,052 | 3,527 | 3,024 | The base layer. Recipes for markup Helix does not render, usable with no JavaScript at all. |
|
|
79
|
+
| `elements.css` | 8,336 | 1,905 | 1,614 | The components' own layer, generated from every family's stylesheet. |
|
|
80
|
+
| `all.css` | 179 | 142 | 110 | The three above in one import, in the order they have to be in. |
|
|
81
|
+
| `primitives.css` | 6,274 | 1,233 | 1,055 | The primitive layer. Private, shipped because `tokens.css` resolves into it. |
|
|
82
|
+
| `tokens.css` | 19,989 | 2,469 | 2,073 | The 185 semantic tokens. Nothing paints without this. |
|
|
83
|
+
|
|
84
|
+
## What the numbers say
|
|
85
|
+
|
|
86
|
+
**Most of a first component is the runtime under it.** The median styled family is 13,976 bytes gzipped alone and adds 2,475 to a page that already has Helix on it, so 82 percent of the first one is shared and 18 percent is the component. Ten typical components are one plus nine additions rather than ten of the first number, and the pair of columns below is what lets that be worked out rather than guessed.
|
|
87
|
+
|
|
88
|
+
Across the tier: the cheapest is `code` at 9,057 gzipped alone, the dearest `data-table` at 49,471, over 93 components. Every family at once is 218,272.
|
|
89
|
+
|
|
90
|
+
**No family imports a barrel, and `check:barrels` is what keeps it that way.** `@fusion.dev/helix-headless` is `sideEffects` over every module in `dist`, which is true of a file that calls `customElements.define` and which means a bundler may drop none of it: one `import { HelixHeadlessButton } from '@fusion.dev/helix-headless'` costs 131,048 bytes gzipped whatever else the page does, which is 9 times the median family measured above. The styled tier imports the family it extends instead, all 93 of them, and the check refuses the shape rather than the package name, so the rule follows `sideEffects` wherever it goes.
|
|
91
|
+
|
|
92
|
+
The headless tier never had the problem, and it is the control: `@fusion.dev/helix-core` is `sideEffects: false`, and a headless family is a median of 16,084 bytes gzipped alone.
|
|
93
|
+
|
|
94
|
+
## Against other systems
|
|
95
|
+
|
|
96
|
+
Four other libraries of custom elements, pinned and measured on 2026-08-24 by `bun run metrics:peers`, which installs them into `.local` and bundles them through the harness that produced everything above. **Helix's own rows are measured on every run of `bun run metrics`**, so the column that could go stale against this repository is the one that cannot.
|
|
97
|
+
|
|
98
|
+
**Every system here is built on Lit**, so the runtime under the comparison is the same library in every column and what is left is the components. A React component library is not in the table, because comparing a custom element with a React component measures the framework.
|
|
99
|
+
|
|
100
|
+
**There is no whole-library row**, deliberately. It would compare how many components each vendor decided to ship, and how each one counts a component, which is the number that looks most like a measurement and means least. Five components every one of these has is the comparison.
|
|
101
|
+
|
|
102
|
+
| Component | System | Raw | Gzip | Brotli |
|
|
103
|
+
|---|---|---|---|---|
|
|
104
|
+
| `button` | **Helix** | 37,328 | 11,515 | 10,158 |
|
|
105
|
+
| | Carbon | 133,058 | 20,959 | 17,685 |
|
|
106
|
+
| | Material Web | 47,060 | 14,173 | 12,591 |
|
|
107
|
+
| | Shoelace | 62,628 | 18,287 | 15,805 |
|
|
108
|
+
| | Spectrum | 98,792 | 22,045 | 18,674 |
|
|
109
|
+
| `checkbox` | **Helix** | 40,872 | 12,578 | 11,068 |
|
|
110
|
+
| | Carbon | 60,255 | 15,609 | 13,550 |
|
|
111
|
+
| | Material Web | 43,785 | 13,556 | 12,015 |
|
|
112
|
+
| | Shoelace | 47,614 | 15,177 | 13,301 |
|
|
113
|
+
| | Spectrum | 62,492 | 15,437 | 13,481 |
|
|
114
|
+
| `dialog` | **Helix** | 34,636 | 11,191 | 9,886 |
|
|
115
|
+
| | Carbon | 510,866 | 67,390 | 39,189 |
|
|
116
|
+
| | Material Web | 34,238 | 11,510 | 10,229 |
|
|
117
|
+
| | Shoelace | 52,754 | 17,698 | 15,554 |
|
|
118
|
+
| | Spectrum | 155,708 | 31,899 | 26,262 |
|
|
119
|
+
| `radio` | **Helix** | 43,716 | 12,868 | 11,346 |
|
|
120
|
+
| | Carbon | 63,055 | 16,887 | 14,605 |
|
|
121
|
+
| | Material Web | 41,044 | 13,234 | 11,757 |
|
|
122
|
+
| | Shoelace | 34,865 | 12,027 | 10,559 |
|
|
123
|
+
| | Spectrum | 38,685 | 10,660 | 9,370 |
|
|
124
|
+
| `switch` | **Helix** | 38,547 | 12,144 | 10,699 |
|
|
125
|
+
| | Carbon | 69,423 | 17,127 | 14,764 |
|
|
126
|
+
| | Material Web | 50,155 | 14,577 | 12,935 |
|
|
127
|
+
| | Shoelace | 37,833 | 11,814 | 10,568 |
|
|
128
|
+
| | Spectrum | 62,322 | 15,059 | 13,321 |
|
|
129
|
+
| `tabs` | **Helix** | 40,442 | 12,374 | 10,893 |
|
|
130
|
+
| | Carbon | 578,766 | 73,537 | 43,235 |
|
|
131
|
+
| | Material Web | 38,883 | 12,820 | 11,368 |
|
|
132
|
+
| | Shoelace | 54,573 | 17,140 | 15,068 |
|
|
133
|
+
| | Spectrum | 64,875 | 18,298 | 16,283 |
|
|
134
|
+
| `tooltip` | **Helix** | 59,436 | 20,247 | 17,886 |
|
|
135
|
+
| | Carbon | 189,382 | 30,354 | 22,273 |
|
|
136
|
+
| | Material Web | | not shipped | |
|
|
137
|
+
| | Shoelace | 57,412 | 20,220 | 17,933 |
|
|
138
|
+
| | Spectrum | 136,599 | 40,552 | 35,286 |
|
|
139
|
+
|
|
140
|
+
**Where Helix loses**, which is the half of a comparison table worth reading: `radio`, `switch`, `tooltip`. The largest gap is radio against Spectrum, by 2,208 bytes gzipped. 4 of 28 peer measurements come in under ours.
|
|
141
|
+
|
|
142
|
+
- **Carbon** `@carbon/web-components@2.61.0`. Its dialog is Modal, its radio is Radio Button, and its switch is Toggle.
|
|
143
|
+
- **Material Web** `@material/web@2.5.0`. Ships no tooltip. Its button is measured as the filled one, there being no single button element.
|
|
144
|
+
- **Shoelace** `@shoelace-style/shoelace@2.20.1`. Its tabs element is Tab Group.
|
|
145
|
+
- **Spectrum** `@spectrum-web-components/button@1.12.2`. One package per component, so there is no barrel to import by accident.
|
|
146
|
+
|
|
147
|
+
Every specifier measured is in [`peers.json`](https://github.com/project-fusion/fusion/blob/main/helix/docs/peers.json), so a disagreement with these numbers is a thing that can be run rather than argued.
|
|
148
|
+
|
|
149
|
+
## What it costs in time
|
|
150
|
+
|
|
151
|
+
Measured on 2026-08-26 by `bun run metrics:timing`, which drives a real browser against the built packages. The median of 9 runs each, on one machine.
|
|
152
|
+
|
|
153
|
+
**These never join the comparison above, and that is not a hedge.** Bytes are a fact about a package and can be held against anybody else's. A timing is a fact about a page, a machine and a day, so a column of somebody else's milliseconds would be comparing two laptops rather than two libraries.
|
|
154
|
+
|
|
155
|
+
**Nothing checks these.** A timing moves several percent between runs on one machine and much further between machines, so a gate on it would fail for reasons nobody caused, which is the fastest way to teach a team that red means nothing. `verify` does not run it. It is refreshed by hand, the same as the peer table, and the date above is the answer to how current it is.
|
|
156
|
+
|
|
157
|
+
| Component | Time to upgrade | First render | Hydration |
|
|
158
|
+
| --- | --- | --- | --- |
|
|
159
|
+
| `button` | 5.80ms | 0.10ms | 6.30ms |
|
|
160
|
+
| `checkbox` | 7.00ms | 0.10ms | 6.50ms |
|
|
161
|
+
| `dialog` | 6.20ms | 0.10ms | 6.60ms |
|
|
162
|
+
| `tabs` | 6.60ms | 0.10ms | 6.90ms |
|
|
163
|
+
| `tooltip` | 6.90ms | 0.10ms | 7.00ms |
|
|
164
|
+
|
|
165
|
+
**Time to upgrade** is the gap between markup being in the document and its shadow root existing, which is the flash of unstyled content that server rendering exists to remove. It is measured the way it happens: the markup is parsed first and the definition arrives after, which is what a script at the foot of a page does. It is dominated by fetching and evaluating the module, so it tracks the byte tables above rather than saying something separate from them.
|
|
166
|
+
|
|
167
|
+
**First render** is what one fresh element costs once the definition is already there, from construction to its first update completing. It is mostly Lit's and is close to identical across families, which is the point: a family far off it is doing something in its first update that the others are not.
|
|
168
|
+
|
|
169
|
+
**Hydration** is the gap between a server-rendered tree sitting in the document and that same tree being live. It is the number server rendering exists to justify and the easiest of the three to fake, so it is measured against a document the server actually rendered and delivered: assigning that markup with `innerHTML` does not parse a declarative shadow root, so there would be nothing to adopt and every reading would be a rebuild wearing the right label.
|
|
170
|
+
|
|
171
|
+
**Every figure above was checked for adoption rather than assumed to have it**, and the check is that adoption adds nothing: the count of parts in the shadow root before the definition arrives has to equal the count after. Marking a node and looking for it later passes either way, because Lit does not clear a shadow root it did not create, so a failed hydration leaves the mark in place and appends a second copy of the tree beneath it. Measured on Button with the support module removed, the parts went from one to two while the timing moved by half a millisecond, so without the adoption test the wrong number is indistinguishable from the right one. A row reading `(rebuilt)` is that failure, and there are none.
|
|
172
|
+
|
|
173
|
+
## Per component
|
|
174
|
+
|
|
175
|
+
Alphabetical, so this is a lookup table. Bytes, not kilobytes, because a
|
|
176
|
+
rounded number in a generated table is a number somebody has to un-round
|
|
177
|
+
before they can check it.
|
|
178
|
+
|
|
179
|
+
### Headless tier
|
|
180
|
+
|
|
181
|
+
Structure, accessibility, state and form participation. No presentation, and no stylesheet to load beside it.
|
|
182
|
+
|
|
183
|
+
| Family | Alone raw | Alone gzip | Alone brotli | Added raw | Added gzip | Added brotli |
|
|
184
|
+
|---|---|---|---|---|---|---|
|
|
185
|
+
| `accordion` | 76,223 | 25,761 | 21,340 | 3,227 | 1,222 | 655 |
|
|
186
|
+
| `announcement` | 74,254 | 25,126 | 20,904 | 1,374 | 684 | 205 |
|
|
187
|
+
| `app-header` | 27,483 | 9,331 | 8,226 | 1,306 | 611 | 203 |
|
|
188
|
+
| `avatar` | 28,859 | 9,794 | 8,643 | 2,644 | 1,070 | 582 |
|
|
189
|
+
| `badge` | 26,919 | 9,246 | 8,148 | 1,120 | 570 | 195 |
|
|
190
|
+
| `bar-chart` | 70,700 | 24,232 | 21,400 | 12,138 | 4,122 | 2,902 |
|
|
191
|
+
| `blockquote` | 26,839 | 9,232 | 8,140 | 917 | 523 | 98 |
|
|
192
|
+
| `breadcrumbs` | 73,875 | 24,790 | 20,711 | 1,955 | 727 | 354 |
|
|
193
|
+
| `button` | 31,158 | 10,389 | 9,175 | 3,167 | 1,125 | 613 |
|
|
194
|
+
| `button-group` | 26,832 | 9,170 | 8,068 | 661 | 406 | 108 |
|
|
195
|
+
| `callout` | 73,005 | 24,653 | 20,548 | 912 | 524 | 175 |
|
|
196
|
+
| `card` | 26,866 | 9,186 | 8,102 | 694 | 420 | 51 |
|
|
197
|
+
| `carousel` | 79,754 | 26,720 | 22,285 | 7,686 | 2,448 | 1,616 |
|
|
198
|
+
| `chat` | 83,713 | 27,957 | 23,221 | 9,449 | 2,829 | 1,918 |
|
|
199
|
+
| `checkbox` | 34,492 | 11,126 | 9,843 | 2,252 | 1,011 | 371 |
|
|
200
|
+
| `checkbox-group` | 37,392 | 11,749 | 10,393 | 4,655 | 1,633 | 878 |
|
|
201
|
+
| `code` | 25,451 | 8,754 | 7,715 | 451 | 292 | 9 |
|
|
202
|
+
| `code-block` | 111,326 | 37,528 | 31,778 | 12,439 | 3,555 | 2,534 |
|
|
203
|
+
| `color-input` | 129,288 | 42,299 | 35,792 | 26,102 | 7,166 | 5,692 |
|
|
204
|
+
| `color-picker` | 99,094 | 32,311 | 26,815 | 19,492 | 5,690 | 4,497 |
|
|
205
|
+
| `combobox` | 125,346 | 40,786 | 34,413 | 20,361 | 5,532 | 4,171 |
|
|
206
|
+
| `command` | 99,183 | 31,859 | 26,261 | 17,134 | 4,872 | 3,598 |
|
|
207
|
+
| `copy-button` | 110,006 | 37,256 | 31,540 | 11,120 | 3,302 | 2,246 |
|
|
208
|
+
| `data-table` | 141,668 | 45,322 | 38,167 | 37,185 | 9,848 | 7,800 |
|
|
209
|
+
| `date-input` | 122,127 | 40,161 | 33,994 | 19,388 | 5,390 | 4,091 |
|
|
210
|
+
| `date-picker` | 84,476 | 28,217 | 23,393 | 9,805 | 3,185 | 2,227 |
|
|
211
|
+
| `dialog` | 30,301 | 10,183 | 8,978 | 2,534 | 1,081 | 560 |
|
|
212
|
+
| `divider` | 27,036 | 9,308 | 8,172 | 1,115 | 596 | 170 |
|
|
213
|
+
| `donut-chart` | 66,610 | 22,813 | 20,207 | 9,398 | 3,290 | 2,302 |
|
|
214
|
+
| `drawer` | 29,268 | 9,876 | 8,699 | 1,792 | 798 | 355 |
|
|
215
|
+
| `drilldown` | 79,507 | 26,788 | 22,064 | 5,059 | 1,765 | 1,078 |
|
|
216
|
+
| `empty` | 26,988 | 9,226 | 8,147 | 816 | 473 | 74 |
|
|
217
|
+
| `file-input` | 84,086 | 28,154 | 23,166 | 6,219 | 2,205 | 1,428 |
|
|
218
|
+
| `format-date` | 30,257 | 10,284 | 9,103 | 2,805 | 972 | 520 |
|
|
219
|
+
| `heatmap` | 68,075 | 23,096 | 20,397 | 10,692 | 3,537 | 2,391 |
|
|
220
|
+
| `icon` | 72,411 | 24,410 | 20,339 | 850 | 487 | 75 |
|
|
221
|
+
| `icon-button` | 31,557 | 10,419 | 9,198 | 3,566 | 1,153 | 661 |
|
|
222
|
+
| `image` | 28,816 | 9,694 | 8,539 | 2,969 | 1,059 | 606 |
|
|
223
|
+
| `input` | 83,600 | 27,860 | 22,929 | 4,944 | 1,773 | 1,074 |
|
|
224
|
+
| `kbd` | 28,236 | 9,743 | 8,582 | 2,427 | 1,131 | 568 |
|
|
225
|
+
| `legend` | 29,187 | 10,066 | 8,845 | 1,097 | 600 | 176 |
|
|
226
|
+
| `line-chart` | 68,726 | 23,631 | 20,885 | 10,165 | 3,525 | 2,540 |
|
|
227
|
+
| `list` | 27,887 | 9,356 | 8,240 | 1,727 | 588 | 278 |
|
|
228
|
+
| `listbox` | 91,239 | 30,089 | 24,864 | 10,677 | 3,397 | 2,343 |
|
|
229
|
+
| `loader` | 27,072 | 9,293 | 8,182 | 900 | 503 | 64 |
|
|
230
|
+
| `megamenu` | 55,180 | 19,332 | 17,079 | 4,934 | 1,741 | 1,043 |
|
|
231
|
+
| `menu` | 108,430 | 36,763 | 31,227 | 8,105 | 2,390 | 1,596 |
|
|
232
|
+
| `meter` | 29,964 | 10,078 | 8,894 | 3,800 | 1,271 | 786 |
|
|
233
|
+
| `navigation` | 28,831 | 9,815 | 8,680 | 1,582 | 720 | 218 |
|
|
234
|
+
| `notifications` | 83,706 | 28,017 | 23,156 | 8,640 | 2,592 | 1,723 |
|
|
235
|
+
| `number-input` | 85,670 | 28,393 | 23,470 | 7,100 | 2,322 | 1,489 |
|
|
236
|
+
| `option` | 28,849 | 9,632 | 8,502 | 2,680 | 896 | 466 |
|
|
237
|
+
| `outline` | 31,837 | 10,814 | 9,540 | 4,539 | 1,772 | 1,039 |
|
|
238
|
+
| `pagination` | 130,381 | 41,800 | 35,277 | 26,097 | 6,594 | 5,115 |
|
|
239
|
+
| `pin-input` | 37,381 | 11,959 | 10,576 | 5,135 | 1,862 | 1,159 |
|
|
240
|
+
| `popover` | 52,511 | 18,611 | 16,413 | 2,019 | 873 | 397 |
|
|
241
|
+
| `progress` | 28,113 | 9,563 | 8,430 | 1,949 | 788 | 421 |
|
|
242
|
+
| `qr-code` | 38,587 | 13,556 | 11,956 | 12,807 | 4,961 | 4,012 |
|
|
243
|
+
| `quote` | 26,355 | 9,041 | 7,964 | 552 | 402 | 103 |
|
|
244
|
+
| `radio` | 35,977 | 11,448 | 10,083 | 3,138 | 1,162 | 529 |
|
|
245
|
+
| `rating` | 38,033 | 12,237 | 10,822 | 5,617 | 2,031 | 1,258 |
|
|
246
|
+
| `scroll-area` | 28,463 | 9,741 | 8,560 | 2,610 | 1,108 | 492 |
|
|
247
|
+
| `select` | 119,544 | 39,510 | 33,314 | 15,389 | 4,432 | 3,180 |
|
|
248
|
+
| `skeleton` | 25,544 | 8,786 | 7,725 | 544 | 354 | 6 |
|
|
249
|
+
| `slider` | 37,742 | 12,019 | 10,633 | 5,250 | 1,817 | 1,153 |
|
|
250
|
+
| `sparkline` | 31,645 | 11,086 | 9,794 | 2,530 | 1,085 | 618 |
|
|
251
|
+
| `speed-dial` | 100,991 | 34,766 | 29,474 | 3,821 | 1,299 | 714 |
|
|
252
|
+
| `splitter` | 36,329 | 11,666 | 10,300 | 10,165 | 2,926 | 2,149 |
|
|
253
|
+
| `stat` | 27,320 | 9,222 | 8,136 | 1,150 | 459 | 85 |
|
|
254
|
+
| `steps` | 81,596 | 27,126 | 22,419 | 6,303 | 1,864 | 1,145 |
|
|
255
|
+
| `switch` | 34,160 | 11,058 | 9,766 | 1,798 | 860 | 336 |
|
|
256
|
+
| `table` | 31,609 | 10,196 | 9,008 | 4,118 | 1,226 | 758 |
|
|
257
|
+
| `tabs` | 33,935 | 11,037 | 9,724 | 4,469 | 1,303 | 719 |
|
|
258
|
+
| `tag` | 73,289 | 24,733 | 20,562 | 897 | 492 | 128 |
|
|
259
|
+
| `textarea` | 36,847 | 11,696 | 10,315 | 4,115 | 1,416 | 757 |
|
|
260
|
+
| `time-input` | 134,184 | 43,285 | 36,521 | 28,643 | 7,663 | 6,078 |
|
|
261
|
+
| `time-picker` | 99,260 | 32,521 | 26,869 | 18,154 | 5,510 | 4,100 |
|
|
262
|
+
| `timeline` | 73,821 | 24,751 | 20,746 | 1,896 | 686 | 324 |
|
|
263
|
+
| `toast` | 77,019 | 25,851 | 21,418 | 4,007 | 1,278 | 790 |
|
|
264
|
+
| `toggle` | 38,209 | 12,108 | 10,665 | 3,928 | 1,330 | 703 |
|
|
265
|
+
| `toolbar` | 28,734 | 9,827 | 8,655 | 2,932 | 1,258 | 610 |
|
|
266
|
+
| `tooltip` | 55,871 | 19,441 | 17,199 | 5,867 | 2,058 | 1,213 |
|
|
267
|
+
| `tour` | 61,086 | 20,604 | 18,240 | 10,277 | 2,947 | 2,027 |
|
|
268
|
+
| `tree` | 83,288 | 27,803 | 22,913 | 7,069 | 2,379 | 1,471 |
|
|
269
|
+
| `trend` | 72,614 | 24,511 | 20,430 | 1,056 | 561 | 140 |
|
|
270
|
+
| `video` | 131,880 | 42,504 | 36,213 | 31,304 | 7,765 | 6,491 |
|
|
271
|
+
| `video-playlist` | 115,879 | 38,435 | 32,636 | 14,151 | 3,705 | 2,679 |
|
|
272
|
+
| `virtual-list` | 29,714 | 10,244 | 9,021 | 2,089 | 964 | 480 |
|
|
273
|
+
|
|
274
|
+
### Styled tier
|
|
275
|
+
|
|
276
|
+
The headless tier plus presentation. This is what `bun add @fusion.dev/helix` gives you.
|
|
277
|
+
|
|
278
|
+
| Family | Alone raw | Alone gzip | Alone brotli | Added raw | Added gzip | Added brotli |
|
|
279
|
+
|---|---|---|---|---|---|---|
|
|
280
|
+
| `accordion` | 79,518 | 26,569 | 21,911 | 6,510 | 2,049 | 1,327 |
|
|
281
|
+
| `announcement` | 78,022 | 26,038 | 21,534 | 5,126 | 1,535 | 995 |
|
|
282
|
+
| `app-header` | 30,814 | 10,122 | 8,910 | 4,620 | 1,375 | 845 |
|
|
283
|
+
| `aura` | 93,124 | 32,615 | 28,199 | 66,161 | 23,230 | 20,023 |
|
|
284
|
+
| `avatar` | 32,433 | 10,538 | 9,282 | 6,205 | 1,798 | 1,187 |
|
|
285
|
+
| `badge` | 29,550 | 9,942 | 8,737 | 3,734 | 1,211 | 784 |
|
|
286
|
+
| `bar-chart` | 86,453 | 26,999 | 23,818 | 27,871 | 6,944 | 5,289 |
|
|
287
|
+
| `blockquote` | 28,015 | 9,529 | 8,383 | 2,077 | 793 | 367 |
|
|
288
|
+
| `breadcrumbs` | 77,346 | 25,597 | 21,315 | 5,414 | 1,491 | 1,010 |
|
|
289
|
+
| `button` | 37,328 | 11,515 | 10,158 | 9,319 | 2,274 | 1,635 |
|
|
290
|
+
| `button-group` | 28,508 | 9,543 | 8,424 | 2,320 | 783 | 404 |
|
|
291
|
+
| `callout` | 75,023 | 25,222 | 20,989 | 2,914 | 1,037 | 632 |
|
|
292
|
+
| `card` | 30,062 | 9,999 | 8,796 | 3,878 | 1,197 | 857 |
|
|
293
|
+
| `carousel` | 88,795 | 28,265 | 23,685 | 16,716 | 3,981 | 2,976 |
|
|
294
|
+
| `chat` | 95,392 | 30,099 | 25,048 | 21,117 | 4,948 | 3,809 |
|
|
295
|
+
| `checkbox` | 40,872 | 12,578 | 11,068 | 8,616 | 2,460 | 1,714 |
|
|
296
|
+
| `checkbox-group` | 39,639 | 12,321 | 10,884 | 6,888 | 2,215 | 1,375 |
|
|
297
|
+
| `code` | 26,476 | 9,057 | 7,994 | 1,459 | 598 | 175 |
|
|
298
|
+
| `code-block` | 124,510 | 39,736 | 33,611 | 25,604 | 5,672 | 4,385 |
|
|
299
|
+
| `color-input` | 161,123 | 47,164 | 40,047 | 57,917 | 11,996 | 10,000 |
|
|
300
|
+
| `color-picker` | 125,641 | 36,555 | 30,553 | 46,033 | 9,895 | 8,289 |
|
|
301
|
+
| `combobox` | 139,026 | 43,268 | 36,586 | 34,020 | 7,937 | 6,234 |
|
|
302
|
+
| `command` | 109,436 | 33,866 | 28,080 | 27,372 | 6,828 | 5,298 |
|
|
303
|
+
| `copy-button` | 120,632 | 39,134 | 33,084 | 21,728 | 5,089 | 3,840 |
|
|
304
|
+
| `data-table` | 169,562 | 49,471 | 41,812 | 65,060 | 14,048 | 11,315 |
|
|
305
|
+
| `date-input` | 137,056 | 43,024 | 36,502 | 34,295 | 8,187 | 6,693 |
|
|
306
|
+
| `date-picker` | 90,171 | 29,433 | 24,478 | 15,482 | 4,357 | 3,290 |
|
|
307
|
+
| `dialog` | 34,636 | 11,191 | 9,886 | 6,854 | 2,117 | 1,412 |
|
|
308
|
+
| `divider` | 29,181 | 9,821 | 8,631 | 3,243 | 1,074 | 656 |
|
|
309
|
+
| `donut-chart` | 78,627 | 24,829 | 21,927 | 21,398 | 5,370 | 3,962 |
|
|
310
|
+
| `drawer` | 33,215 | 10,783 | 9,504 | 5,721 | 1,692 | 1,135 |
|
|
311
|
+
| `drilldown` | 83,865 | 27,759 | 22,977 | 9,404 | 2,737 | 1,944 |
|
|
312
|
+
| `empty` | 28,295 | 9,601 | 8,463 | 2,107 | 826 | 412 |
|
|
313
|
+
| `file-input` | 94,830 | 30,332 | 25,049 | 16,956 | 4,366 | 3,243 |
|
|
314
|
+
| `flex` | 27,799 | 9,391 | 8,289 | 1,986 | 698 | 351 |
|
|
315
|
+
| `format-date` | 30,905 | 10,420 | 9,233 | 3,435 | 1,135 | 531 |
|
|
316
|
+
| `grid` | 28,326 | 9,563 | 8,437 | 2,511 | 871 | 525 |
|
|
317
|
+
| `heatmap` | 71,868 | 23,944 | 21,216 | 14,467 | 4,404 | 3,164 |
|
|
318
|
+
| `icon` | 73,106 | 24,587 | 20,429 | 1,528 | 627 | 270 |
|
|
319
|
+
| `icon-button` | 37,656 | 11,455 | 10,088 | 9,651 | 2,208 | 1,604 |
|
|
320
|
+
| `image` | 30,646 | 10,202 | 8,977 | 4,781 | 1,555 | 1,038 |
|
|
321
|
+
| `input` | 89,818 | 29,243 | 24,183 | 11,157 | 3,138 | 2,169 |
|
|
322
|
+
| `kbd` | 29,648 | 10,205 | 8,982 | 3,824 | 1,584 | 1,038 |
|
|
323
|
+
| `legend` | 34,022 | 11,144 | 9,798 | 5,924 | 1,707 | 1,104 |
|
|
324
|
+
| `line-chart` | 81,226 | 25,771 | 22,754 | 22,646 | 5,721 | 4,207 |
|
|
325
|
+
| `list` | 31,940 | 10,289 | 9,061 | 5,770 | 1,526 | 1,095 |
|
|
326
|
+
| `listbox` | 95,298 | 31,052 | 25,548 | 14,723 | 4,350 | 3,093 |
|
|
327
|
+
| `loader` | 30,078 | 10,033 | 8,831 | 3,891 | 1,192 | 862 |
|
|
328
|
+
| `megamenu` | 59,602 | 20,377 | 18,025 | 9,341 | 2,802 | 2,020 |
|
|
329
|
+
| `menu` | 114,709 | 38,183 | 32,425 | 14,366 | 3,748 | 2,813 |
|
|
330
|
+
| `meter` | 35,602 | 11,267 | 9,972 | 9,420 | 2,475 | 1,903 |
|
|
331
|
+
| `navigation` | 30,359 | 10,243 | 9,007 | 3,094 | 1,127 | 575 |
|
|
332
|
+
| `notifications` | 91,354 | 29,582 | 24,616 | 16,275 | 4,146 | 3,062 |
|
|
333
|
+
| `number-input` | 90,634 | 29,544 | 24,415 | 12,050 | 3,439 | 2,486 |
|
|
334
|
+
| `option` | 32,116 | 10,542 | 9,321 | 5,935 | 1,785 | 1,141 |
|
|
335
|
+
| `outline` | 35,824 | 11,618 | 10,255 | 8,509 | 2,589 | 1,722 |
|
|
336
|
+
| `pagination` | 148,273 | 44,851 | 37,991 | 43,971 | 9,539 | 7,685 |
|
|
337
|
+
| `pin-input` | 40,603 | 12,787 | 11,279 | 8,340 | 2,707 | 1,832 |
|
|
338
|
+
| `popover` | 55,129 | 19,245 | 17,022 | 4,619 | 1,500 | 983 |
|
|
339
|
+
| `progress` | 33,246 | 10,757 | 9,521 | 7,064 | 1,972 | 1,448 |
|
|
340
|
+
| `qr-code` | 40,238 | 13,976 | 12,338 | 14,441 | 5,391 | 4,349 |
|
|
341
|
+
| `quote` | 27,030 | 9,207 | 8,088 | 1,211 | 570 | 159 |
|
|
342
|
+
| `radio` | 43,716 | 12,868 | 11,346 | 10,869 | 2,616 | 1,892 |
|
|
343
|
+
| `rating` | 41,008 | 13,168 | 11,593 | 8,574 | 2,951 | 2,075 |
|
|
344
|
+
| `sandbox` | 61,201 | 16,813 | 14,897 | 34,739 | 8,029 | 6,423 |
|
|
345
|
+
| `scroll-area` | 31,437 | 10,374 | 9,095 | 5,567 | 1,762 | 1,019 |
|
|
346
|
+
| `select` | 131,872 | 41,805 | 35,381 | 27,696 | 6,674 | 5,111 |
|
|
347
|
+
| `skeleton` | 28,062 | 9,544 | 8,416 | 2,242 | 860 | 426 |
|
|
348
|
+
| `slider` | 47,682 | 13,817 | 12,221 | 15,173 | 3,610 | 2,730 |
|
|
349
|
+
| `sparkline` | 35,412 | 12,013 | 10,609 | 6,285 | 2,018 | 1,464 |
|
|
350
|
+
| `speed-dial` | 108,524 | 36,260 | 30,765 | 11,332 | 2,750 | 2,014 |
|
|
351
|
+
| `splitter` | 42,741 | 12,722 | 11,288 | 16,559 | 3,995 | 3,131 |
|
|
352
|
+
| `stat` | 29,954 | 9,789 | 8,646 | 3,774 | 1,039 | 614 |
|
|
353
|
+
| `steps` | 95,132 | 29,536 | 24,464 | 19,827 | 4,267 | 3,406 |
|
|
354
|
+
| `switch` | 38,547 | 12,144 | 10,699 | 6,170 | 1,934 | 1,308 |
|
|
355
|
+
| `table` | 34,938 | 10,790 | 9,513 | 7,436 | 1,842 | 1,199 |
|
|
356
|
+
| `tabs` | 40,442 | 12,374 | 10,893 | 10,965 | 2,669 | 1,847 |
|
|
357
|
+
| `tag` | 76,483 | 25,615 | 21,389 | 4,076 | 1,285 | 897 |
|
|
358
|
+
| `text` | 29,734 | 9,670 | 8,508 | 3,922 | 994 | 512 |
|
|
359
|
+
| `textarea` | 40,278 | 12,528 | 11,028 | 7,529 | 2,288 | 1,518 |
|
|
360
|
+
| `time-input` | 154,142 | 46,671 | 39,499 | 48,582 | 10,985 | 8,883 |
|
|
361
|
+
| `time-picker` | 110,619 | 34,662 | 28,765 | 29,508 | 7,635 | 5,964 |
|
|
362
|
+
| `timeline` | 77,087 | 25,507 | 21,266 | 5,151 | 1,394 | 978 |
|
|
363
|
+
| `toast` | 84,299 | 27,460 | 22,823 | 11,274 | 2,899 | 2,145 |
|
|
364
|
+
| `toggle` | 43,388 | 13,293 | 11,734 | 9,099 | 2,562 | 1,840 |
|
|
365
|
+
| `toolbar` | 29,632 | 10,054 | 8,841 | 3,813 | 1,495 | 838 |
|
|
366
|
+
| `tooltip` | 59,436 | 20,247 | 17,886 | 9,414 | 2,863 | 1,933 |
|
|
367
|
+
| `tour` | 71,124 | 22,307 | 19,738 | 20,296 | 4,649 | 3,493 |
|
|
368
|
+
| `tree` | 88,512 | 29,043 | 24,005 | 12,281 | 3,611 | 2,628 |
|
|
369
|
+
| `trend` | 75,648 | 25,206 | 21,009 | 4,083 | 1,215 | 769 |
|
|
370
|
+
| `video` | 149,490 | 45,696 | 38,857 | 48,893 | 10,889 | 9,107 |
|
|
371
|
+
| `video-playlist` | 128,650 | 40,737 | 34,547 | 26,903 | 5,922 | 4,697 |
|
|
372
|
+
| `virtual-list` | 30,546 | 10,470 | 9,192 | 2,904 | 1,181 | 649 |
|
|
373
|
+
|
|
374
|
+
## What lands in `node_modules`
|
|
375
|
+
|
|
376
|
+
A different question from what reaches a browser, and the one an install
|
|
377
|
+
budget is written against. Unpacked bytes of exactly what each package
|
|
378
|
+
publishes, which is its `files` field. **Source maps are not in these
|
|
379
|
+
numbers**: they are stripped from the tarball at pack time, so the last
|
|
380
|
+
column is what was removed rather than part of what remains.
|
|
381
|
+
|
|
382
|
+
| Package | Unpacked | Files | Source maps stripped | Runtime dependencies |
|
|
383
|
+
|---|---|---|---|---|
|
|
384
|
+
| `@fusion.dev/helix-core` | 910,682 | 113 | 160,137 | `@floating-ui/dom`, `@fusion.dev/helix-icons` |
|
|
385
|
+
| `@fusion.dev/helix-headless` | 10,110,995 | 422 | 737,978 | `@fusion.dev/helix-core`, `@fusion.dev/helix-icons`, `uqr` |
|
|
386
|
+
| `@fusion.dev/helix-icons` | 65,119 | 11 | 20,405 | none |
|
|
387
|
+
| `@fusion.dev/helix-styles` | 83,183 | 5 | 0 | none |
|
|
388
|
+
| `@fusion.dev/helix-theme` | 465,772 | 8 | 4,793 | none |
|
|
389
|
+
| `@fusion.dev/helix` | 10,029,875 | 717 | 287,558 | `@fusion.dev/helix-core`, `@fusion.dev/helix-headless`, `@fusion.dev/helix-styles` |
|
|
390
|
+
|
|
391
|
+
## Not measured yet
|
|
392
|
+
|
|
393
|
+
Named rather than left out, because a metrics page is read for what it does
|
|
394
|
+
not say as much as for what it does.
|
|
395
|
+
|
|
396
|
+
- **No whole-library comparison, and no fifth component.** The comparison above is five components against four systems. Widening it means naming the equivalent element in each system by hand, which is where a comparison table starts arguing, so each addition is a deliberate one rather than a sweep.
|
|
397
|
+
- **The peer numbers are pinned, so they age.** They are dated in the section above and refreshed by running `bun run metrics:peers`. Nothing fails when they are old, because nothing in this repository changes when somebody else releases, and a check that fired on the calendar would be noise.
|
|
398
|
+
- **No load or rendering timings.** First paint, time to upgrade, and the cost of hydrating a server-rendered tree. These need a browser harness rather than a bundler, and they never join a cross-system table.
|
|
399
|
+
- **No per-component stylesheet bytes.** There are none to report: a family's CSS is compiled into its JavaScript, so it is already inside every number above. `elements.css` is the light-DOM layer and is in the stylesheet table.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Four durations, five curves, and the rule every animating component has to answer.
|
|
3
|
+
section: Foundations
|
|
4
|
+
title: Motion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Motion
|
|
8
|
+
|
|
9
|
+
## The tokens
|
|
10
|
+
|
|
11
|
+
[//]: # (tokens:duration)
|
|
12
|
+
|
|
13
|
+
| Token | Value |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `--helix-duration-instant` | `0ms` |
|
|
16
|
+
| `--helix-duration-fast` | `120ms` |
|
|
17
|
+
| `--helix-duration-normal` | `200ms` |
|
|
18
|
+
| `--helix-duration-slow` | `320ms` |
|
|
19
|
+
|
|
20
|
+
[//]: # (/tokens)
|
|
21
|
+
|
|
22
|
+
[//]: # (tokens:easing)
|
|
23
|
+
|
|
24
|
+
| Token | Value |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `--helix-easing-standard` | `cubic-bezier(0.2, 0, 0, 1)` |
|
|
27
|
+
| `--helix-easing-emphasized` | `cubic-bezier(0.3, 0, 0, 1.2)` |
|
|
28
|
+
| `--helix-easing-decelerate` | `cubic-bezier(0, 0, 0, 1)` |
|
|
29
|
+
| `--helix-easing-accelerate` | `cubic-bezier(0.3, 0, 1, 1)` |
|
|
30
|
+
| `--helix-easing-spring` | `linear(0, 0.006, 0.025, 0.101, 0.539, 0.826, 0.976, 1.037, 1.043, 1.026, 1.006, 0.997, 1)` |
|
|
31
|
+
|
|
32
|
+
[//]: # (/tokens)
|
|
33
|
+
|
|
34
|
+
Four durations and five curves. `standard` for most things, `decelerate` for
|
|
35
|
+
something arriving, `accelerate` for something leaving, `emphasized` for a
|
|
36
|
+
movement the reader is meant to follow, and `spring` for the few places
|
|
37
|
+
overshoot reads as responsive rather than as a bug.
|
|
38
|
+
|
|
39
|
+
The theme engine takes a `motion` intent input, so a product can be uniformly
|
|
40
|
+
quicker or slower without every component being retuned.
|
|
41
|
+
|
|
42
|
+
## Reduced motion is per component
|
|
43
|
+
|
|
44
|
+
The base layer has a global rule capping animation and transition duration for a
|
|
45
|
+
reader who asked not to be moved. **It does not reach a single component.**
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
*, *::before, *::after { … }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`*` does not cross a shadow boundary. So that rule reaches your prose, your
|
|
52
|
+
layout and the base layer's own recipes, and none of the ninety-odd components
|
|
53
|
+
that draw inside a shadow root. Each one answers `prefers-reduced-motion` in its
|
|
54
|
+
own stylesheet, fifty-nine of them do because fifty-nine of them animate, and a
|
|
55
|
+
component that animates without one fails the build.
|
|
56
|
+
|
|
57
|
+
**The same is true of yours.** If you write a component with a shadow root, the
|
|
58
|
+
global rule will not cover it either. That is not a Helix behavior, it is how
|
|
59
|
+
`*` works, and it is the single easiest accessibility regression to ship without
|
|
60
|
+
noticing.
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
@media (prefers-reduced-motion: reduce) {
|
|
64
|
+
:host {
|
|
65
|
+
animation: none;
|
|
66
|
+
transition: none;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## What reduced motion means here
|
|
72
|
+
|
|
73
|
+
Not "no movement". A transition that communicates a relationship, like a panel
|
|
74
|
+
sliding out of the control that opened it, is doing work; a decorative float is
|
|
75
|
+
not. The library's own policy is to remove the second and shorten the first to
|
|
76
|
+
the point where it is instant but not jarring.
|
|
77
|
+
|
|
78
|
+
The one place this is absolute is anything that moves continuously without
|
|
79
|
+
input: the aura shader, marquees, and looping loaders all stop rather than speed
|
|
80
|
+
up.
|
|
81
|
+
|
|
82
|
+
## Two things that are not motion tokens
|
|
83
|
+
|
|
84
|
+
**`@starting-style`** is how overlays animate in, and it needs no duration token
|
|
85
|
+
of its own: it is a starting value, and the transition that runs from it uses
|
|
86
|
+
the tokens above.
|
|
87
|
+
|
|
88
|
+
**View transitions** are not used anywhere in the library. A component cannot
|
|
89
|
+
own a view transition, because the transition is between two states of the
|
|
90
|
+
document rather than of the component, so if you want them that is yours to
|
|
91
|
+
drive.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The three rules every framework reduces to, and what they look like in each.
|
|
3
|
+
parent: installation
|
|
4
|
+
section: Getting started
|
|
5
|
+
title: Other frameworks
|
|
6
|
+
trailing: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Other frameworks
|
|
10
|
+
|
|
11
|
+
Solid, Qwik, Lit, Ember, Alpine, htmx, and anything else that puts elements in a
|
|
12
|
+
document. All of them need the same three rules, and nothing beyond them.
|
|
13
|
+
|
|
14
|
+
## The three rules
|
|
15
|
+
|
|
16
|
+
**Strings go through attributes. Everything else goes through properties.** An
|
|
17
|
+
attribute is a string, so an array, an object or a function has to be assigned
|
|
18
|
+
to the element rather than written in the markup. Most frameworks do this for
|
|
19
|
+
you when the element has a matching property.
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
element.items = tabs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Events bind with `addEventListener`, and the name is the event's own.** Helix
|
|
26
|
+
events are `helix-before-open`, `helix-before-change`, `helix-select` and so on:
|
|
27
|
+
lowercase,
|
|
28
|
+
hyphenated, no camel-case variant. Whatever your framework's `on*` syntax is, it
|
|
29
|
+
should end up passing that string through.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
element.addEventListener('helix-before-change', (event) => console.log(event.detail))
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**A declared `value`, `open`, `checked` or `active` is a claim of ownership.**
|
|
36
|
+
The element announces the change and applies none of it, and waits to be told.
|
|
37
|
+
Use `default-value`, `default-open` and their siblings for a control that owns
|
|
38
|
+
its own state. This is the rule most likely to look like a bug, and
|
|
39
|
+
[State and control](./state-and-control) is the page about it.
|
|
40
|
+
|
|
41
|
+
## What each one needs
|
|
42
|
+
|
|
43
|
+
| Framework | Notes |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Solid | Sets properties on custom elements by default. Use `prop:` to force one and `attr:` to force the other. `on:helix-change` binds the literal name |
|
|
46
|
+
| Qwik | Works as JSX. Bind with `document:on…` or a ref, since a plain `on*` prop is normalized |
|
|
47
|
+
| Lit | `.prop=`, `?bool=` and `@helix-before-change=` are already the right three channels |
|
|
48
|
+
| Ember | Angle-bracket invocation passes attributes only, so set properties from a modifier |
|
|
49
|
+
| Alpine, htmx | Markup and attributes only, which reaches every component. Refusing a change needs script |
|
|
50
|
+
|
|
51
|
+
Nothing here is tested by us, which is the honest reason it is one page. If your
|
|
52
|
+
framework needs something the three rules above do not cover, that is worth
|
|
53
|
+
telling us: it is either a gap in this page or a gap in the library.
|
|
54
|
+
|
|
55
|
+
## Typings
|
|
56
|
+
|
|
57
|
+
`@fusion.dev/helix/jsx` is the base for anything JSX-shaped, and Svelte's own
|
|
58
|
+
typings are built from it. If your framework needs a different shape, the
|
|
59
|
+
manifest ships as `@fusion.dev/helix/custom-elements.json` and describes every tag,
|
|
60
|
+
prop, event and part, which is what all of ours are generated from.
|
|
61
|
+
|
|
62
|
+
## Registration
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import '@fusion.dev/helix'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Once, anywhere that runs in the browser. See [Installation](./installation) for
|
|
69
|
+
the stylesheets, which are the same in every framework and are the thing most
|
|
70
|
+
often left out.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: The top layer, what dismisses what, and where focus goes.
|
|
3
|
+
section: Essentials
|
|
4
|
+
title: Overlays and layering
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Overlays and layering
|
|
8
|
+
|
|
9
|
+
`Dialog`, `Drawer`, `Popover`, `Menu`, `Select`, `Combobox`, `Tooltip`,
|
|
10
|
+
`Megamenu`, `Speed Dial`, `Command` and `Tour` are all overlays, and they all
|
|
11
|
+
work the same way underneath. Knowing that way saves you from most of the
|
|
12
|
+
problems overlays cause.
|
|
13
|
+
|
|
14
|
+
## They render in the top layer
|
|
15
|
+
|
|
16
|
+
Every one uses the popover API, so the browser draws it in the top layer: above
|
|
17
|
+
everything, regardless of `z-index`, `overflow: hidden`, `transform` or a
|
|
18
|
+
stacking context somewhere up the tree.
|
|
19
|
+
|
|
20
|
+
**This is the fix for the oldest bug in the category.** A menu inside a
|
|
21
|
+
scrolling panel does not get clipped. A tooltip inside a `transform`ed card does
|
|
22
|
+
not get trapped in its stacking context. There is no portal to configure and no
|
|
23
|
+
`appendTo` to pass, because the platform is doing it.
|
|
24
|
+
|
|
25
|
+
The consequence for your CSS: **do not try to raise an overlay with `z-index`.**
|
|
26
|
+
It has no effect on a top-layer element, and reaching for it means something
|
|
27
|
+
else is wrong.
|
|
28
|
+
|
|
29
|
+
## Positioning is JavaScript
|
|
30
|
+
|
|
31
|
+
Placement uses `@floating-ui/dom` rather than CSS anchor positioning: flip when
|
|
32
|
+
there is no room below, shift to stay on screen, size to the available space.
|
|
33
|
+
Anchor positioning would be the platform answer and is not yet available
|
|
34
|
+
everywhere the rest of the library's requirements are. See [Browser
|
|
35
|
+
support](./browser-support).
|
|
36
|
+
|
|
37
|
+
That means an overlay's position is recomputed while it is open, on scroll and
|
|
38
|
+
resize, and stops when it closes.
|
|
39
|
+
|
|
40
|
+
## Dismissal
|
|
41
|
+
|
|
42
|
+
Light dismiss is the platform's: click outside, or press `Escape`. What Helix
|
|
43
|
+
adds is making that correct across shadow boundaries.
|
|
44
|
+
|
|
45
|
+
**`document.activeElement` stops at the outermost host.** Focus inside a shadow
|
|
46
|
+
tree is reported as the element containing that tree, so a naive "is focus still
|
|
47
|
+
inside me" check never matches and closes the overlay the moment anything inside
|
|
48
|
+
it is focused. Every dismissal path here walks through the roots instead.
|
|
49
|
+
|
|
50
|
+
**Nesting works because lifecycle events do not bubble.** `helix-before-open` and
|
|
51
|
+
`helix-before-close` are neither bubbling nor composed, so a menu closing inside a
|
|
52
|
+
dialog is not the dialog closing. `Escape` closes the innermost overlay and
|
|
53
|
+
leaves the rest.
|
|
54
|
+
|
|
55
|
+
## Focus
|
|
56
|
+
|
|
57
|
+
A modal overlay traps `Tab` inside itself while open and returns focus to
|
|
58
|
+
whatever opened it. `Dialog` and `Drawer` do this; `Popover` does it in its
|
|
59
|
+
modal mode and not otherwise.
|
|
60
|
+
|
|
61
|
+
**A tooltip never takes focus**, which is the one to remember: it is
|
|
62
|
+
described-by rather than interactive, so it cannot contain a button. If you want
|
|
63
|
+
something with a button in it, that is a `Popover`.
|
|
64
|
+
|
|
65
|
+
## Refusing to close
|
|
66
|
+
|
|
67
|
+
The close is announced before it happens:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
dialog.addEventListener('helix-before-close', (event) => {
|
|
71
|
+
if (unsaved) {
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That covers `Escape`, the backdrop click and the close button together, because
|
|
78
|
+
all three go through the same request. See [State and
|
|
79
|
+
control](./state-and-control).
|
|
80
|
+
|
|
81
|
+
## Entry and exit animation
|
|
82
|
+
|
|
83
|
+
Overlays animate in with `@starting-style` and out with a transition on the
|
|
84
|
+
`overlay` and `display` properties, which is what allows an exit animation on an
|
|
85
|
+
element the browser is about to remove from the top layer. Every one answers
|
|
86
|
+
`prefers-reduced-motion`.
|
|
87
|
+
|
|
88
|
+
## Which one to reach for
|
|
89
|
+
|
|
90
|
+
| | |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `Dialog` | A task that blocks the page |
|
|
93
|
+
| `Drawer` | The same, from an edge, usually larger |
|
|
94
|
+
| `Popover` | A surface anchored to a control, possibly interactive |
|
|
95
|
+
| `Tooltip` | A description of a control. Never interactive |
|
|
96
|
+
| `Menu` | A list of commands |
|
|
97
|
+
| `Select`, `Combobox` | A value chosen from a list, in a form |
|
|
98
|
+
| `Command` | A palette over the whole application |
|
|
99
|
+
| `Megamenu` | Navigation, anchored to a header |
|
|
100
|
+
|
|
101
|
+
A `Dialog` used for a tooltip's job traps focus for no reason. A `Tooltip` used
|
|
102
|
+
for a popover's job silently drops the button you put in it.
|