@fusion.dev/helix-docs 0.206.0 → 0.207.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +22 -2
  2. package/content/angular.mdx +37 -4
  3. package/content/browser-support.mdx +3 -3
  4. package/content/building-on-the-headless-tier.mdx +84 -21
  5. package/content/charting.mdx +50 -0
  6. package/content/coding-agents.mdx +2 -1
  7. package/content/color-modes.mdx +2 -2
  8. package/content/color.mdx +35 -5
  9. package/content/diagnostics.mdx +255 -0
  10. package/content/graphs.mdx +6 -0
  11. package/content/iconography.mdx +31 -13
  12. package/content/installation.mdx +34 -10
  13. package/content/intent-reference.mdx +27 -3
  14. package/content/introduction.mdx +3 -2
  15. package/content/localization.mdx +86 -4
  16. package/content/maps.mdx +68 -14
  17. package/content/metrics.mdx +252 -249
  18. package/content/other-frameworks.mdx +52 -12
  19. package/content/overriding-a-component.mdx +11 -10
  20. package/content/react.mdx +15 -1
  21. package/content/recipes.mdx +18 -2
  22. package/content/sandbox.mdx +75 -25
  23. package/content/server-rendering.mdx +2 -2
  24. package/content/state-and-control.mdx +7 -1
  25. package/content/surfaces-and-separation.mdx +2 -1
  26. package/content/svelte.mdx +54 -8
  27. package/content/theme-builder.mdx +15 -3
  28. package/content/theming.mdx +5 -1
  29. package/content/tiers.mdx +15 -14
  30. package/content/using-helix-with-an-agent.mdx +12 -5
  31. package/content/using-the-elements.mdx +24 -23
  32. package/content/versioning.mdx +15 -8
  33. package/content/visual-axes.mdx +11 -4
  34. package/content/vue.mdx +28 -1
  35. package/content/without-a-bundler.mdx +9 -7
  36. package/dist/index.d.ts +1 -3
  37. package/dist/index.js +0 -1
  38. package/dist/pages.generated.js +7 -1
  39. package/dist/reference.d.ts +70 -6
  40. package/dist/reference.js +13 -2
  41. package/dist/sources.generated.js +1346 -459
  42. package/package.json +13 -12
@@ -199,7 +199,8 @@ typos, in your other components.
199
199
 
200
200
  \`\`\`html
201
201
  <helix-input
202
- (change)="name = $event.target.value"
202
+ #field
203
+ (change)="name = field.value"
203
204
  [value]="name"
204
205
  />
205
206
  \`\`\`
@@ -207,6 +208,10 @@ typos, in your other components.
207
208
  \`[prop]\` sets a property, which a value that is not a string needs. \`(event)\`
208
209
  binds with \`addEventListener\`, so use the event's own name.
209
210
 
211
+ \`#field\` gives the template a reference to the element, typed as its class once
212
+ you import its family. Under \`strictTemplates\`, \`$event.target\` is an
213
+ \`EventTarget | null\`, which has no \`value\`.
214
+
210
215
  **Do not use \`attr.\` for a stateful property.** \`[attr.value]="name"\` writes the
211
216
  attribute. The attribute seeds the control and is out of date as soon as someone
212
217
  types in it. The property holds the current value.
@@ -218,9 +223,17 @@ checking that typings would feed, so Angular would not enforce a generated file.
218
223
  We publish the manifest as \`@fusion.dev/helix/custom-elements.json\`, so you can
219
224
  generate your own files from it.
220
225
 
221
- As a result, a misspelled property fails without an error in Angular. In React,
222
- Vue, and Svelte, the same mistake is a compile error. Check property names in
223
- code review.
226
+ As a result, a misspelled property fails without an error in Angular. Check
227
+ property names in code review.
228
+
229
+ The other frameworks check a misspelled name to different degrees:
230
+
231
+ - In React, TypeScript reports a misspelled prop unless its name has a hyphen.
232
+ See [React](./react).
233
+ - In Vue, \`vue-tsc\` reports one only when \`checkUnknownProps\` is on. See
234
+ [Vue](./vue).
235
+ - In Svelte, \`svelte-check\` reports an attribute that the Helix tag does not
236
+ declare.
224
237
 
225
238
  ## Refuse a change
226
239
 
@@ -228,6 +241,26 @@ code review.
228
241
  value you set and then applies its own changes. To refuse a change, listen for
229
242
  the \`helix-before-*\` event and call \`preventDefault()\`. See
230
243
  [State and control](./state-and-control).
244
+
245
+ To write your own value into a field you type into, set your signal and then
246
+ call \`detectChanges()\` on the component's \`ChangeDetectorRef\`:
247
+
248
+ \`\`\`ts
249
+ private readonly view = inject(ChangeDetectorRef)
250
+
251
+ protected takeHandle(event: Event): void {
252
+ event.preventDefault()
253
+ const { value } = (event as CustomEvent<{ value: string }>).detail
254
+ this.handle.set(clean(value))
255
+ this.view.detectChanges()
256
+ }
257
+ \`\`\`
258
+
259
+ Without zone.js, Angular renders a signal change in a later task. A key that
260
+ arrives before that task lands in the text from before your value, and the keys
261
+ typed in between are lost. A barcode scanner, a password manager, and a test
262
+ send keys that fast. \`detectChanges()\` renders the change before your handler
263
+ returns.
231
264
  `,
232
265
  'browser-support': `---
233
266
  description: The browser features Helix uses, and the browser we test in.
@@ -244,7 +277,7 @@ support in older browsers. Your browser needs every feature Helix uses.
244
277
 
245
278
  ## What we test
246
279
 
247
- We test in Chromium, through Playwright. The DOM specs run in it, and 83 pixel
280
+ We test in Chromium, through Playwright. The DOM specs run in it, and the pixel
248
281
  baselines are captured in it, on Linux, with the network blocked.
249
282
 
250
283
  We do not run the tests in Firefox or Safari. The rest of this page lists the
@@ -258,9 +291,9 @@ something broken, please report it. Our tests would not have caught it.
258
291
  | Custom elements and shadow DOM | Everything |
259
292
  | \`ElementInternals\` | Form participation, and ARIA that has to cross a shadow boundary |
260
293
  | Popover API | Every overlay: menu, tooltip, select, combobox, dialog |
261
- | \`light-dark()\` | 156 declarations in the shipped stylesheets, 71 of them in the token layer |
294
+ | \`light-dark()\` | Every color token, and each component color that differs between the modes |
262
295
  | \`oklch()\` | Every color in the system |
263
- | \`:has()\` | 11 rules, mostly relational styling of slotted content |
296
+ | \`:has()\` | Relational styling, mostly of slotted content |
264
297
  | \`@starting-style\` | Entry transitions on overlays |
265
298
  | \`:state()\` | Every boolean state the library publishes: \`open\`, \`checked\`, \`selected\`, \`invalid\`, \`read\`, \`has-label\`, \`settled\`, \`empty\`, \`controls\` |
266
299
  | \`CSS.highlights\` | Filter-match highlighting, in \`Command\` and \`Combobox\` |
@@ -371,9 +404,10 @@ headless manifest carries the static, so you can find it there.
371
404
  **When a component renders another component, register both.**
372
405
 
373
406
  If you register \`acme-combobox\` without \`acme-listbox\`, the panel renders a tag
374
- that is not defined. The browser shows no error or warning. An unregistered
375
- custom element is an inert, unstyled inline box, so the combobox appears not to
376
- open.
407
+ that is not defined. An unregistered custom element is an inert, unstyled inline
408
+ box, so the panel shows every option with no list around them, and typing
409
+ filters nothing. In development, the combobox says so once in the console and
410
+ names the tag to define.
377
411
 
378
412
  [//]: # (renders)
379
413
 
@@ -413,7 +447,8 @@ everything those families render in turn. For example, \`command\` reaches
413
447
  **The elements you write inside a component are yours to register as well.**
414
448
  A select renders none of the options you write in it, so \`acme-select\` needs
415
449
  your \`acme-option\` registered beside it. In development, a component holding a
416
- child of its tier that nobody registered says so once, with the import to add.
450
+ child of its tier that nobody registered says so once and names the tag to
451
+ define.
417
452
  A listbox given \`items\` and a filter input given a \`source\` draw options of
418
453
  your tier from data, and those need \`acme-option\` registered too.
419
454
 
@@ -426,7 +461,7 @@ generated table is always current.
426
461
 
427
462
  ### A \`:not(:defined)\` rule
428
463
 
429
- \`@fusion.dev/helix\` ships an \`elements.css\` that hides each of its tags until
464
+ \`@fusion.dev/helix-styles\` ships an \`elements.css\` that hides each of its tags until
430
465
  the tag's definition loads. Your page then does not show unstyled text that
431
466
  changes into components. The headless package ships no such file, and no tool
432
467
  can generate one for tags you have not named yet. Write the rule for your own
@@ -439,26 +474,85 @@ acme-listbox:not(:defined) {
439
474
  }
440
475
  \`\`\`
441
476
 
442
- Use \`visibility\` so the page does not reflow when the definitions arrive.
477
+ The rule hides each element until its definition loads, and the layout
478
+ settles when the elements upgrade.
479
+
480
+ ### A rule that hides the \`live\` parts
481
+
482
+ A \`live\` part is a live region. It holds words only a screen reader should
483
+ read, such as how many options a filter left, and the headless tier draws it
484
+ as ordinary text. Hide each one visually with a clip, which keeps it in the
485
+ accessibility tree:
486
+
487
+ \`\`\`css
488
+ acme-combobox::part(live),
489
+ acme-combobox::part(listbox-live) {
490
+ block-size: 1px;
491
+ clip-path: inset(50%);
492
+ inline-size: 1px;
493
+ overflow: hidden;
494
+ position: absolute;
495
+ white-space: nowrap;
496
+ }
497
+ \`\`\`
498
+
499
+ \`display: none\` and \`visibility: hidden\` take a region out of the
500
+ accessibility tree as well, and a screen reader then hears nothing. A
501
+ component forwards the \`live\` part of a component it renders under a name of
502
+ its own, such as \`listbox-live\` on Combobox. The styled tier hides every one
503
+ of them.
443
504
 
444
505
  ### Typings for your tags
445
506
 
446
- Your typings come from a manifest of your package. \`helix manifest build\`, in
447
- \`@fusion.dev/helix-cli\`, writes one that includes every member your subclasses
448
- inherit from ours. Name each Helix package you extend and where its manifest is:
507
+ Your typings come from a custom elements manifest of your package, written by
508
+ \`@custom-elements-manifest/analyzer\`:
449
509
 
450
510
  \`\`\`sh
451
- helix manifest build . --tier styled \\
452
- --dependency @fusion.dev/helix=node_modules/@fusion.dev/helix/custom-elements.json \\
453
- --dependency @fusion.dev/helix-headless=node_modules/@fusion.dev/helix-headless/custom-elements.json \\
454
- --dependency @fusion.dev/helix-core=node_modules/@fusion.dev/helix-core/custom-elements.json \\
455
- > custom-elements.json
511
+ npx @custom-elements-manifest/analyzer analyze --litelement --dependencies \\
512
+ --globs "src/**/*.ts"
456
513
  \`\`\`
457
514
 
458
- Then run a typings generator over it. Our own typings start from
515
+ Each flag does one job, and the manifest is incomplete without either:
516
+
517
+ - \`--dependencies\` reads the \`custom-elements.json\` each Helix package ships
518
+ and names in its \`package.json\`, so \`acme-combobox\` gets the members,
519
+ attributes, events, slots and parts it inherits from ours.
520
+ - \`--litelement\` reads Lit's decorators, so the properties you declare with
521
+ \`@property\` are attributes too.
522
+
523
+ Then run a typings generator over the manifest. Our own typings start from
459
524
  \`@wc-toolkit/jsx-types\` and \`@wc-toolkit/vuejs-types\`. The typings for
460
- \`acme-combobox\` then include the hundred or so members it inherits, as well as
461
- the two you wrote.
525
+ \`acme-combobox\` then include the members it inherits, as well as the ones you
526
+ wrote.
527
+
528
+ The generated file names Helix's event classes, such as
529
+ \`HelixBeforeChangeEvent\`, without importing them. Add one line at its top that
530
+ imports the names TypeScript reports as missing:
531
+
532
+ \`\`\`ts
533
+ import type { HelixBeforeChangeEvent, HelixFilterEvent } from '@fusion.dev/helix-core'
534
+ \`\`\`
535
+
536
+ Without that line, a project that sets \`skipLibCheck\` gets no error, and each
537
+ of those event handlers is typed \`any\`.
538
+
539
+ For a few tags in React, you can skip the generator. Each headless family
540
+ publishes its props in \`@fusion.dev/helix-headless/jsx\`, and your tag takes
541
+ them:
542
+
543
+ \`\`\`ts
544
+ import type { HelixHeadlessComboboxProps } from '@fusion.dev/helix-headless/jsx'
545
+
546
+ declare module 'react' {
547
+ namespace JSX {
548
+ interface IntrinsicElements {
549
+ 'acme-combobox': HelixHeadlessComboboxProps
550
+ }
551
+ }
552
+ }
553
+ \`\`\`
554
+
555
+ A subclass that adds properties joins its own to them with \`&\`.
462
556
 
463
557
  \`@fusion.dev/helix-cem\`, the step that runs those generators for our packages
464
558
  and corrects what they write, is not published. It is build tooling for this
@@ -526,10 +620,12 @@ findings as data. The rules check what your components document and write:
526
620
 
527
621
  ### Tree-shaking
528
622
 
529
- \`@fusion.dev/helix-headless\` declares \`sideEffects: ["./dist/**/*.js"]\`. The
530
- declaration is accurate, because the modules register elements when you import
531
- them. A bundler that treated them as free of side effects would drop a
532
- registration, and a tag would never upgrade.
623
+ \`@fusion.dev/helix-headless\` declares \`"sideEffects": true\`, because its
624
+ modules register elements when you import them. A bundler that treated them as
625
+ free of side effects would drop a registration, and a tag would never upgrade.
626
+ Import each family from its own entry, such as
627
+ \`@fusion.dev/helix-headless/combobox\`, so your bundle carries only the families
628
+ you use.
533
629
 
534
630
  ### Documented parts
535
631
 
@@ -886,6 +982,56 @@ bar there, and a heatmap draws the cell with no tone. The readings after the gap
886
982
  stay under their own labels, and the tooltip for that row still shows the
887
983
  row's cells.
888
984
 
985
+ A cell holds a number when its whole text reads as one, such as \`1200\` or
986
+ \`-3.5\`. A formatted number, such as \`1,200\` or \`$1.2k\`, is a gap. To chart a
987
+ formatted cell, write its number in \`data-value\`:
988
+
989
+ \`\`\`html
990
+ <tr><th>Q1</th><td data-value="1200">$1.2k</td><td data-value="800">$800</td></tr>
991
+ \`\`\`
992
+
993
+ The chart reads \`data-value\` when a cell has one, and the cell's text otherwise.
994
+ The table and the tooltip still show \`$1.2k\`.
995
+
996
+ ## Charts with many readings
997
+
998
+ A chart builds what it can show, and that is bounded by its size. The readings
999
+ themselves are read again each time the table or the data changes.
1000
+
1001
+ - **Names on the axis.** \`Line Chart\`, \`Area Chart\`, and \`Bar Chart\` draw every
1002
+ reading's name where every name fits. Where they do not, the chart draws a
1003
+ regular share of them, and always the first and the last. Where every row
1004
+ header holds a \`<time>\`, the names drawn start a period, such as the first
1005
+ reading of each month or each year. Where the names are numbers in order,
1006
+ such as years, they are round numbers. Other names are drawn every so many
1007
+ readings, counted from the first. The chart works out the share again when it
1008
+ is resized. The tooltip and the keys still say each reading's own name.
1009
+ - **Gridlines.** \`gridlines="category"\` rules the readings whose names are
1010
+ drawn.
1011
+ - **Lines.** Where a line has more readings than the plot has pixels across, it
1012
+ is drawn through the points those pixels can show: in each column of pixels,
1013
+ the first reading, the lowest, the highest, and the last.
1014
+ - **Markers.** \`markers\` marks every reading, with one path for each series.
1015
+ - **Columns.** \`Bar Chart\` draws each column as an element while every column
1016
+ and the space between two categories are at least a pixel wide. Past that, it
1017
+ draws each series' columns as one path, each column of pixels as tall as the
1018
+ tallest column in it, and draws the category the tooltip describes again over
1019
+ them, at least two pixels thick.
1020
+ - **Points.** \`Scatter Chart\` draws each series' points as paths. Points that
1021
+ overlap are drawn in separate groups, up to four deep, so each keeps its edge
1022
+ and a cluster reads darker. The point the keys rest on is drawn again over
1023
+ them.
1024
+ - **The table drawn from \`data\`.** Past a few hundred readings, the hidden table
1025
+ holds one page of them at a time. Its markup gives a screen reader the number
1026
+ of rows in the whole table and the place of each row, and the words after it
1027
+ say which readings the page holds. Two buttons after it show the previous and
1028
+ the next page. You press them with a screen reader as you read. They are left
1029
+ out of the tab order, so the plot stays the chart's one tab stop. The keys on
1030
+ the plot move through every reading.
1031
+ - **A table on the page.** A chart reads the whole table that \`for\` names, and
1032
+ the table is part of your page, however many rows it has. Where the readings
1033
+ are too many to put in the document, draw the chart from \`data\`.
1034
+
889
1035
  ## Series colors
890
1036
 
891
1037
  Helix has twenty-four series color tokens, in order, from
@@ -1129,7 +1275,8 @@ agent uses most:
1129
1275
  \`helix scan\`, \`helix diagnostics --once\`, and \`helix theme audit\` check what an
1130
1276
  agent wrote, and the MCP server runs the same checks.
1131
1277
  [Using Helix with an agent](./using-helix-with-an-agent#check-the-code-you-wrote)
1132
- lists what each finds.
1278
+ lists what each finds, and [Diagnostics](./diagnostics) sets the checks up in
1279
+ your app and in CI.
1133
1280
 
1134
1281
  ## The index
1135
1282
 
@@ -1260,11 +1407,20 @@ Choose a surface by where the element sits relative to the page. This is the
1260
1407
  choice people most often get wrong. See [Surfaces and
1261
1408
  separation](./surfaces-and-separation).
1262
1409
 
1410
+ A field is filled with \`well\`: 5% of \`foreground\` laid over whatever the field
1411
+ sits on, so its color depends on the surface under the field.
1412
+
1263
1413
  ### Inks
1264
1414
 
1265
1415
  Inks are the colors that go on grounds. The base ink is \`foreground\`.
1266
1416
 
1267
- - \`foreground-muted\` and \`foreground-subtle\` are for text that recedes.
1417
+ - \`foreground-muted\` is for text that recedes and is still read, such as a
1418
+ placeholder, a hint, or a caption. The engine solves it to the contrast
1419
+ target.
1420
+ - \`foreground-subtle\` recedes further. It is for marks that are not text, such
1421
+ as a separator, a chevron, or a grip, and for the text of a disabled control.
1422
+ It is a fixed step of the gray ramp, and the engine does not solve it, so its
1423
+ contrast is not guaranteed. Use \`foreground-muted\` for text that is read.
1268
1424
  - Text on a filled color takes the ink named for that fill:
1269
1425
  - \`foreground-on-primary\` on \`primary-surface\`
1270
1426
  - \`foreground-on-accent\` on \`accent-surface\`
@@ -1278,8 +1434,16 @@ Intent colors show what something means.
1278
1434
  \`-subtle\` variants. With no brand color set, both are ink: near-black in
1279
1435
  light mode and near-white in dark mode.
1280
1436
  - \`info\`, \`success\`, \`warning\`, and \`danger\` are for status. Each has a
1281
- \`-surface\` and a \`-surface-hover\` variant for the tinted box that holds the
1282
- message.
1437
+ \`-surface\` variant, the filled ground that carries \`foreground-on-status\`,
1438
+ such as a danger button or a badge in a status tone, and a \`-surface-hover\`
1439
+ variant for that fill under the pointer.
1440
+
1441
+ ### Rise and fall
1442
+
1443
+ \`rise\` and \`fall\` color a value by which way it moved, such as a price. They
1444
+ are \`success\` and \`danger\` unless you set them. Mainland China, Taiwan, and
1445
+ Japan draw a rise in red, so a page for one of those markets swaps the two. See
1446
+ [Rise and fall colors](./localization#rise-and-fall-colors).
1283
1447
 
1284
1448
  ### Series
1285
1449
 
@@ -1324,8 +1488,21 @@ OKLCH lets the theme engine *solve* for a palette from your brand color. See
1324
1488
 
1325
1489
  The engine measures each ink against the ground it will be painted on. A brand
1326
1490
  color that already meets the target there is used as it is. For any other color,
1327
- the engine searches for the lightness that meets the target. Every pair it
1328
- produces meets the target.
1491
+ the engine searches for the lightness that meets the target. A pair that cannot
1492
+ meet its target, such as one with a color you pinned, is reported in
1493
+ \`violations\`.
1494
+
1495
+ \`foreground\` and \`foreground-muted\` are also written in fields, so the engine
1496
+ solves them against the well over the page, a card, a raised surface, a dialog,
1497
+ and a sunken surface. The report names each of these grounds as the well over
1498
+ its surface, such as \`color-well over color-surface\`.
1499
+
1500
+ \`foreground-subtle\` is the one ink the engine does not solve. The range of
1501
+ readable grays has room for one quiet ink, and \`foreground-muted\` is that ink, so
1502
+ \`foreground-subtle\` stays a fixed step of the stylesheet's gray ramp and its
1503
+ contrast is not guaranteed. A component paints its quiet text in
1504
+ \`foreground-muted\`, and keeps \`foreground-subtle\` for marks that are not text and
1505
+ for the text of a disabled control.
1329
1506
 
1330
1507
  The engine can solve only the pairs it knows about, so we check two things:
1331
1508
 
@@ -1376,8 +1553,8 @@ class, or a second copy of any value.
1376
1553
  Each token is written once, with its light and dark values in \`light-dark()\`.
1377
1554
  The \`color-scheme\` property alone decides which value applies. \`color-scheme\`
1378
1555
  inherits, so one property sets the mode for an element and everything inside
1379
- it. The shipped stylesheets use \`light-dark()\` in 156 declarations, 71 of them
1380
- in the token layer.
1556
+ it. A component that sets a color of its own for each mode uses \`light-dark()\`
1557
+ the same way.
1381
1558
 
1382
1559
  ## Set the mode
1383
1560
 
@@ -1630,6 +1807,262 @@ For the same reason, we wrote \`DrilldownController\`. Since it was first writte
1630
1807
  was specific to menus. \`Menu\` and \`Drilldown\` now share it.
1631
1808
 
1632
1809
  If you are about to write a second copy of some behavior, make it shared.
1810
+ `,
1811
+ diagnostics: `---
1812
+ description: Find the Helix mistakes the browser accepts without an error, on your running pages and in your code, with a setup for Vite, React, Next.js, and CI.
1813
+ section: Tooling
1814
+ title: Diagnostics
1815
+ ---
1816
+
1817
+ # Diagnostics
1818
+
1819
+ Helix diagnostics finds the mistakes in your markup and styles that the browser
1820
+ accepts without an error. A \`::part()\` that names a part nothing publishes
1821
+ matches nothing, an attribute value outside the set a component takes falls
1822
+ back to the default, and \`disabled="false"\` turns \`disabled\` on. Each one is
1823
+ valid HTML or CSS, so nothing else reports it.
1824
+
1825
+ Two tools check your project, in two places:
1826
+
1827
+ - **\`initDiagnostics()\`**, from \`@fusion.dev/helix-diagnostics\`, checks each
1828
+ page as it runs in development. It prints each finding in DevTools, and a
1829
+ badge in the corner of the page lists them.
1830
+ - **\`helix scan\`**, from \`@fusion.dev/helix-cli\`, reads every stylesheet,
1831
+ template, and module in your project, and prints each finding with its file
1832
+ and line.
1833
+
1834
+ Both judge your code against the lint data of the Helix version you installed.
1835
+ A fault that both find has the same kind and the same fingerprint in each, so a
1836
+ report can join a page's findings to the scan's.
1837
+
1838
+ ## Start diagnostics in your app
1839
+
1840
+ \`\`\`sh
1841
+ npm install --save-dev @fusion.dev/helix-diagnostics
1842
+ \`\`\`
1843
+
1844
+ Call \`initDiagnostics()\` once, before your app mounts. Put the call in a module
1845
+ of its own, so a production build can leave it out:
1846
+
1847
+ \`\`\`ts
1848
+ // src/diagnostics.ts
1849
+ import { initDiagnostics } from '@fusion.dev/helix-diagnostics'
1850
+ import lint from '@fusion.dev/helix/lint.json'
1851
+
1852
+ initDiagnostics({ environment: 'development', tiers: [{ lint }] })
1853
+ \`\`\`
1854
+
1855
+ \`tiers\` passes the lint data from your own \`node_modules\`, so diagnostics loads
1856
+ nothing from the network. Without it, diagnostics loads the lint data for the
1857
+ version on the page from a public npm CDN.
1858
+
1859
+ An add-on package publishes the lint data for its own elements, and you pass it
1860
+ in \`tiers\` the same way. \`@fusion.dev/helix-sandbox\` publishes it for
1861
+ \`helix-sandbox-editor\`:
1862
+
1863
+ \`\`\`ts
1864
+ import lint from '@fusion.dev/helix/lint.json'
1865
+ import sandbox from '@fusion.dev/helix-sandbox/lint.json'
1866
+
1867
+ initDiagnostics({ environment: 'development', tiers: [{ lint }, { lint: sandbox }] })
1868
+ \`\`\`
1869
+
1870
+ The CDN serves the two tiers' lint data alone, so an add-on's elements are
1871
+ checked only where its lint data is in \`tiers\`. Without it, each of them is an
1872
+ unknown tag.
1873
+
1874
+ Load the module in development only. In Vite, with React or any other
1875
+ framework, import it from your entry:
1876
+
1877
+ \`\`\`tsx
1878
+ // src/main.tsx
1879
+ if (import.meta.env.DEV) {
1880
+ void import('./diagnostics.js')
1881
+ }
1882
+ \`\`\`
1883
+
1884
+ \`vite build\` replaces \`import.meta.env.DEV\` with \`false\` and drops the import
1885
+ with it. In Next.js, import it from \`instrumentation-client.ts\`, which runs in
1886
+ the browser before your app hydrates:
1887
+
1888
+ \`\`\`ts
1889
+ // instrumentation-client.ts
1890
+ if (process.env.NODE_ENV === 'development') {
1891
+ void import('./app/diagnostics')
1892
+ }
1893
+ \`\`\`
1894
+
1895
+ Every Helix template starts diagnostics this way. Angular, Svelte, Vue, Solid,
1896
+ and a page with no bundler call it the same way, and the package's README says
1897
+ where each one's entry is.
1898
+
1899
+ From then on, diagnostics runs after the page loads, after a navigation, and
1900
+ after the page changes. It prints each finding once per page, and the badge
1901
+ shows only while the page has findings. Hover over a finding in the badge's
1902
+ list to outline the element it is about.
1903
+
1904
+ ## Print the findings in your terminal
1905
+
1906
+ A page cannot write to your terminal, so the Helix command line receives each
1907
+ run beside your dev server. Install it, and turn the relay on where you call
1908
+ \`initDiagnostics()\`:
1909
+
1910
+ \`\`\`sh
1911
+ npm install --save-dev @fusion.dev/helix-cli
1912
+ \`\`\`
1913
+
1914
+ \`\`\`ts
1915
+ initDiagnostics({
1916
+ environment: 'development',
1917
+ sinks: { relay: true },
1918
+ tiers: [{ lint }],
1919
+ })
1920
+ \`\`\`
1921
+
1922
+ Then run the relay in your project's directory, beside the dev server:
1923
+
1924
+ \`\`\`sh
1925
+ npx helix diagnostics
1926
+ \`\`\`
1927
+
1928
+ Each Helix template has the script for it, so \`npm run diagnostics\` starts it
1929
+ there. The command prints each finding the first time a page has it. It listens
1930
+ on \`127.0.0.1:7417\`, takes runs only from pages on a local host, and serves the
1931
+ \`helix-diagnostics.json\` in the directory it runs in to the page.
1932
+
1933
+ ## Scan your code
1934
+
1935
+ \`\`\`sh
1936
+ npx helix scan
1937
+ \`\`\`
1938
+
1939
+ \`helix scan\` judges your code against the lint data of every Helix package you
1940
+ install that publishes \`lint.json\`, the two tiers and an add-on such as
1941
+ \`@fusion.dev/helix-sandbox\`. It reads every file \`.gitignore\` leaves in, apart
1942
+ from tests and the folders a build writes. It reads CSS, Sass, and Less; HTML, Vue, Svelte,
1943
+ and Astro templates; MDX; and JSX, Lit's \`html\` and \`css\` templates, and
1944
+ Angular's inline templates in JavaScript and TypeScript. Each finding has its
1945
+ file and line:
1946
+
1947
+ \`\`\`
1948
+ helix scan: 1 error, 0 warnings, 0 info, and 0 unsettled, in 42 files
1949
+ error nested-interactive <helix-button> <helix-icon-button>
1950
+ \`helix-icon-button\` is inside \`helix-button\`, which draws its content inside a native button. …
1951
+ in src/toolbar.tsx:12
1952
+ \`\`\`
1953
+
1954
+ - **It reads every route.** A page sees only the routes someone opened.
1955
+ - **What only a page can settle is listed as \`unsettled\`**, such as a hook set
1956
+ on a tag that does not have it, which works where an element inside the tag
1957
+ has it. An unsettled finding never fails the scan.
1958
+ - **It checks an attribute where your code writes it as one.**
1959
+ \`disabled="false"\` is checked. \`?disabled=\${false}\` in Lit, \`:disabled="false"\`
1960
+ in Vue, and \`disabled={false}\` in JSX set the value at run time, and the page
1961
+ judges what they write.
1962
+ - **\`--file <path>\` reports one file's findings**, and still reads the whole
1963
+ project, so an import in another file counts. \`--stdin <language>\` checks a
1964
+ snippet.
1965
+
1966
+ It exits 1 on a finding at \`error\`, and 3 where it cannot run, such as where no
1967
+ installed Helix package has lint data.
1968
+
1969
+ ## Run the checks in CI
1970
+
1971
+ \`--out\` writes a run to a CI file, and each command keeps the runs the others
1972
+ wrote there, so one file holds your code's run and your pages' runs:
1973
+
1974
+ \`\`\`sh
1975
+ npx helix scan --out reports/helix-diagnostics.json
1976
+ npx helix diagnostics --once http://localhost:4173/ --out reports/helix-diagnostics.json
1977
+ \`\`\`
1978
+
1979
+ \`helix diagnostics --once <url>\` opens a page headless, runs diagnostics on it
1980
+ once the page holds still, prints each finding, and exits. Repeat \`--once\` for
1981
+ each page. It needs Playwright installed beside \`@fusion.dev/helix-cli\`, and
1982
+ adds nothing to your app.
1983
+
1984
+ Where your own end-to-end tests open the pages, gather their runs with
1985
+ \`@fusion.dev/helix-diagnostics/ci\`:
1986
+
1987
+ \`\`\`ts
1988
+ import { ciSink } from '@fusion.dev/helix-diagnostics/ci'
1989
+
1990
+ // \`runs\` are the reports diagnostics returned in each page your job opened.
1991
+ const { code, summary } = ciSink(runs, { file: 'reports/helix-diagnostics.json' })
1992
+ console.log(summary)
1993
+ process.exitCode = code
1994
+ \`\`\`
1995
+
1996
+ A fault found on many pages is one line, with the pages it was on. A finding at
1997
+ \`error\` fails the job. \`failOn: 'warn'\` fails it on warnings too, and
1998
+ \`failOn: 'never'\` reports without failing. \`ciSink\` writes the whole file, so
1999
+ run \`helix scan --out\` after it to add your code's run.
2000
+
2001
+ ## What it finds
2002
+
2003
+ | Kind | Severity | Found by | Means |
2004
+ | --- | --- | --- | --- |
2005
+ | \`dead-hook\` | error | both | A hook is set where nothing answers it, or a name close to a hook is set that nothing reads |
2006
+ | \`dead-part\` | error | both | A \`::part()\` names a part the element does not publish, or follows another \`::part()\` |
2007
+ | \`disabled-attribute\` | error | scan | A rule styles a form control's \`[disabled]\`, which a control a \`<fieldset disabled>\` disables does not carry. Write \`:disabled\` |
2008
+ | \`duplicate-registration\` | error | page | Two copies of Helix are on the page |
2009
+ | \`false-boolean\` | error | both | A boolean attribute is written \`="false"\`, which turns it on, as \`disabled="false"\` does. An attribute that \`"false"\` turns off, such as Drawer's \`modal\`, is not one |
2010
+ | \`invalid-value\` | error | both | An attribute with a closed set of values carries one outside it, so the element falls back to its default |
2011
+ | \`manifest-skew\` | error | page | The lint data is from another version than the page runs, so no other finding is reported |
2012
+ | \`native-element\` | warn | scan | A native element is written where an installed Helix family stands in for it, such as \`<button>\` for \`helix-button\` |
2013
+ | \`nested-interactive\` | error | both | An interactive element is inside a button or a link, which HTML forbids, such as \`helix-icon-button\` inside \`helix-button\` or \`helix-button\` inside an \`<a>\` |
2014
+ | \`primitive-token\` | error | scan | A rule reads a \`--_helix-\` primitive, which is internal to \`@fusion.dev/helix-styles\` |
2015
+ | \`unforwarded-part\` | warn | both | The headless tag publishes a part the styled tag does not forward, which is a gap in Helix |
2016
+ | \`unimported-tag\` | warn | scan | An element is written, and nothing in the project imports the entry that registers it |
2017
+ | \`unknown-attribute\` | error | both | A Helix element carries an attribute it does not take |
2018
+ | \`unknown-event\` | error | scan | A React \`on\` prop names an event the element does not dispatch |
2019
+ | \`unknown-slot\` | error | scan | A child names a slot its Helix element does not have, so it is not drawn |
2020
+ | \`unknown-tag\` | error | both | A rule or an element names a tag no installed package lists |
2021
+ | \`unknown-token\` | error | scan | A rule reads or sets a \`--helix-\` name the token set does not have |
2022
+ | \`unreflected-attribute\` | warn | both | A rule selects a Helix element by an attribute it never writes back, so it matches only markup a server wrote |
2023
+
2024
+ **A button inside a button is judged where the markup settles it.** Button,
2025
+ Icon Button, Copy Button, Toggle, Motion Toggle, Radio, and Tab draw their
2026
+ content inside a native button, and Skip Link draws its content inside a link.
2027
+ A slot for actions, such as a card's \`footer-actions\`, is not inside anything
2028
+ pressable. A framework's component, such as a router's \`<Link>\`, places its
2029
+ children itself, so \`helix scan\` stops at it, and the page sees the \`<a>\` it
2030
+ renders.
2031
+
2032
+ ## Configure the checks
2033
+
2034
+ Both tools read \`helix-diagnostics.json\` from your project. The page cannot read
2035
+ files, so import it and pass it as \`file\`, or let the relay serve it:
2036
+
2037
+ \`\`\`json
2038
+ {
2039
+ "rules": {
2040
+ "native-element": "off",
2041
+ "unknown-tag": { "severity": "off", "tags": ["helix-mine"] }
2042
+ }
2043
+ }
2044
+ \`\`\`
2045
+
2046
+ A kind takes \`error\`, \`warn\`, \`info\`, or \`off\`, for every tag or for the tags
2047
+ you name. A setting that names nothing is refused, with the closest one that
2048
+ exists. Every setting is in the README of \`@fusion.dev/helix-diagnostics\`.
2049
+
2050
+ ## Scope
2051
+
2052
+ The checks cover what the lint data can settle. They do not check:
2053
+
2054
+ - the value a token or a hook is set to, such as \`--helix-radii-md: red\`.
2055
+ \`helix scan\` checks each \`--helix-\` name you write;
2056
+ - a selector that descends into a component, such as \`helix-card .title\` or
2057
+ \`helix-button span\`. It matches only the elements you put inside the
2058
+ component;
2059
+ - a \`::part()\` on a selector with no Helix tag in it, such as
2060
+ \`.cta::part(label)\`, since the class names no element whose parts can be
2061
+ read;
2062
+ - an attribute value an expression sets, such as \`tone={tone}\` in JSX, in
2063
+ \`helix scan\`. The page judges the attribute it writes;
2064
+ - the contrast of a color you write yourself. \`helix theme audit\` checks the
2065
+ contrast of your theme's tokens.
1633
2066
  `,
1634
2067
  'elevation-and-material': `---
1635
2068
  description: The layer, shadow, and material tokens for stacking, lifting, and translucent surfaces.
@@ -2567,6 +3000,12 @@ helix-graph:state(ready) > div {
2567
3000
  }
2568
3001
  \`\`\`
2569
3002
 
3003
+ Use the fade where the layout settles in a moment, such as a graph whose notes
3004
+ all have places. Leave a cosmos.gl graph shown. Its layout runs for several
3005
+ seconds by design, and the view follows the notes as they spread, so the reader
3006
+ watches the graph settle. Faded in on the state, its drawing is hidden for that
3007
+ whole time.
3008
+
2570
3009
  ## Draw the graph before it loads
2571
3010
 
2572
3011
  \`graphSnapshot(nodes, links, options)\` draws the graph on your server as an
@@ -2871,24 +3310,42 @@ bundler can remove that record.
2871
3310
  an order of precedence, the glyph on the page would come from whichever
2872
3311
  directory ranked higher, and nothing would look wrong.
2873
3312
 
2874
- ## Add a glyph to the Helix set
3313
+ ## Draw your own glyphs by name
2875
3314
 
2876
- Add the name to \`helix/icons/icons.txt\` and run \`turbo run icons\`. Phosphor
2877
- names resolve from \`@phosphor-icons/core\` at build time, so adding a glyph takes
2878
- one line. You do not copy a path into a TypeScript object by hand.
3315
+ \`registerIcons\` makes your glyphs resolve by \`name\`, so plain HTML can draw
3316
+ them:
2879
3317
 
2880
- For a mark Phosphor does not draw, add an SVG with the same name to
2881
- \`helix/icons/raw\`. The build reads it from there when Phosphor has no match.
2882
- Draw it with the same geometry, a 256 box at the bold weight. A glyph drawn at a
2883
- different weight stands out next to the others.
3318
+ \`\`\`ts
3319
+ import { registerIcons } from '@fusion.dev/helix/icon'
3320
+ import { peak } from './icons'
3321
+
3322
+ registerIcons([peak])
3323
+ \`\`\`
3324
+
3325
+ \`\`\`html
3326
+ <helix-icon name="peak"></helix-icon>
3327
+ \`\`\`
3328
+
3329
+ Each glyph registers under its own \`name\`. To register a whole set, build it
3330
+ with \`--index\` and pass the \`icons\` record the command writes to
3331
+ \`registerIcons\`.
3332
+
3333
+ \`registerIconLibrary\` resolves names under a prefix through a function of your
3334
+ own, such as one that points into a sprite sheet or fetches from a server. After
3335
+ \`registerIconLibrary('brand', resolve)\`, \`name="brand:peak"\` draws the glyph that
3336
+ \`resolve('peak')\` returns.
3337
+
3338
+ To sit beside the Helix set, draw a glyph with the same geometry, a 256 box at
3339
+ the bold weight. A glyph drawn at a different weight stands out next to the
3340
+ others.
2884
3341
 
2885
3342
  ## Product marks
2886
3343
 
2887
- Helix and Cortex have product marks. They are in the icon package, and they are
2888
- not part of the icon set. A mark identifies a product. An icon stands for an
2889
- action. **A mark is one rounded body with material removed.** It is never a
2890
- pictogram of a metaphor, because it stands beside the parent brand's mark, a
2891
- geometric monogram. Do not use the icon set to draw a logo.
3344
+ The set includes the Fusion, Helix, and Cortex marks, and the React and HTML5
3345
+ logos, so \`<helix-icon name="helix">\` draws the Helix mark. A mark identifies a
3346
+ product. An icon stands for an action. **The Helix and Cortex marks are each one
3347
+ rounded body with material removed.** Neither is a pictogram of a metaphor,
3348
+ because each stands beside the Fusion mark, a geometric monogram.
2892
3349
  `,
2893
3350
  installation: `---
2894
3351
  description: What to install, the order of the stylesheets, and how to check that the elements registered.
@@ -2908,6 +3365,24 @@ use its semantic tokens and draw nothing without them. If the elements are
2908
3365
  installed and the tokens are missing, your page shows unstyled boxes. There is
2909
3366
  no fallback theme.
2910
3367
 
3368
+ ## Start a new project
3369
+
3370
+ If you do not have a project yet, the scaffolder writes one with Helix
3371
+ installed, the stylesheets imported, and every element registered:
3372
+
3373
+ \`\`\`sh
3374
+ npm create @fusion.dev/helix-app@latest my-app -- --template react-vite
3375
+ \`\`\`
3376
+
3377
+ Leave out \`--template\`, and the command lists the templates and writes nothing.
3378
+ Each template starts with the same three pages:
3379
+
3380
+ - a landing page
3381
+ - a sorted, paged data table
3382
+ - a form that a \`<form>\` reads with \`FormData\`
3383
+
3384
+ The rest of this page adds Helix to a project you already have.
3385
+
2911
3386
  ## Import the stylesheets
2912
3387
 
2913
3388
  \`\`\`ts
@@ -2941,18 +3416,19 @@ import the two files you want and skip \`helix.css\`.
2941
3416
  \`tokens.css\` refers to the primitives throughout, so without
2942
3417
  \`primitives.css\` every semantic token resolves to nothing. We measured a page without it:
2943
3418
  \`--helix-color-background\` computes to \`rgba(0, 0, 0, 0)\`, and so does every
2944
- other color. [Theming](theming.mdx) also depends on this file, because without
3419
+ other color. [Theming](./theming) also depends on this file, because without
2945
3420
  it, setting \`--helix-seed-brand\` changes nothing.
2946
3421
  - **\`tokens.css\`** declares the semantic layer, the custom properties grouped
2947
3422
  by category, plus \`color-scheme: light dark\` on \`:root\`. The files below read
2948
3423
  from it, and it reads from \`primitives.css\`.
2949
3424
  - **\`base.css\`** holds the element reset, the type scale on bare \`h1\` through
2950
3425
  \`h6\`, and the recipes for markup that Helix does not render. It reads tokens.
2951
- - **\`elements.css\`** styles elements before they upgrade. It is the file most
2952
- often left out, because a page works without it until the page is
2953
- server-rendered. An element that has not upgraded has no shadow root and no
2954
- styles of its own, so the markup flashes unstyled and then jumps into place.
2955
- \`elements.css\` makes that interval look like the finished page.
3426
+ - **\`elements.css\`** hides each Helix element until its definition loads. It is
3427
+ the file most often left out, because a page works without it until the page
3428
+ is server-rendered. An element that has not upgraded has no shadow root and no
3429
+ styles of its own, so without the file the markup flashes unstyled. With it,
3430
+ the element stays hidden until it upgrades, and the layout settles when it
3431
+ does.
2956
3432
 
2957
3433
  Import the files from a stylesheet you control, and keep them out of individual
2958
3434
  components. The order then stays visible in one place.
@@ -2983,13 +3459,14 @@ Render one element and check whether it upgraded:
2983
3459
 
2984
3460
  \`\`\`ts
2985
3461
  await customElements.whenDefined('helix-button')
2986
- console.log(document.querySelector('helix-button')?.shadowRoot !== null)
3462
+ console.log(document.querySelector('helix-button')?.shadowRoot instanceof ShadowRoot)
2987
3463
  \`\`\`
2988
3464
 
2989
3465
  \`true\` means the definition loaded and the element built its shadow root.
2990
- \`false\`, or a promise that never settles, means the registration import did not
2991
- run. Usually the import is outside a \`'use client'\` boundary, or you imported
2992
- the subpath of a component you did not end up using.
3466
+ \`false\` means the page has no \`helix-button\` to check. A promise that never
3467
+ settles means the registration import did not run. Usually the import is outside
3468
+ a \`'use client'\` boundary, or you imported the subpath of a component you did
3469
+ not end up using.
2993
3470
 
2994
3471
  If the element upgraded and the page still looks wrong, check the stylesheets
2995
3472
  in the order above. The symptom tells you which layer is missing:
@@ -2999,6 +3476,10 @@ in the order above. The symptom tells you which layer is missing:
2999
3476
  - Components that look right once JavaScript runs, but flash unstyled before
3000
3477
  it, mean \`elements.css\` is missing.
3001
3478
 
3479
+ To check the rest of your markup and styles against the Helix you installed,
3480
+ start [Diagnostics](./diagnostics) in development, and run \`helix scan\` on your
3481
+ code. Each template starts diagnostics for you.
3482
+
3002
3483
  ## Framework setup
3003
3484
 
3004
3485
  The elements are custom elements, and every framework can render them.
@@ -3054,7 +3535,7 @@ empty intent, \`{}\`, is a valid theme.
3054
3535
  | \`accent\` | The second emphasis color. Defaults to the brand color, or to ink with no brand. \`{ from: 'brand', rotate: 180 }\` gives the complementary color, and needs a brand |
3055
3536
  | \`background\` | The page's color, \`{ light, dark }\`, for a page that is not pure white or pure black. The surfaces keep their contrast with it, and the grays take its tint. A page no text color can reach the contrast target on is refused |
3056
3537
  | \`neutral\` | The gray ramp, which can be tinted toward the brand. With \`background\`, it takes the page's tint |
3057
- | \`status\` | \`info\`, \`success\`, \`warning\`, and \`danger\`. A color pins one. An object adjusts its hue while it keeps its meaning |
3538
+ | \`status\` | \`info\`, \`success\`, \`warning\`, and \`danger\`. A color pins one. \`{ hue }\` moves its hue within the band that keeps its meaning |
3058
3539
  | \`chroma\` | How saturated the palette is, independent of hue |
3059
3540
  | \`scheme\` | \`'light'\` or \`'dark'\`, for a theme that has only one mode |
3060
3541
  | \`tokens\` | Literal overrides by token name, for values no other field sets |
@@ -3069,6 +3550,17 @@ lightness moves only as far as the target needs.
3069
3550
  \`danger\` rotated with the brand, your users would have to learn a new warning
3070
3551
  color for each product.
3071
3552
 
3553
+ **A status hue stays in its band.** Each band, in OKLCH degrees, covers the
3554
+ colors that still read as that status. A hue outside its band is refused, and
3555
+ the error names the band.
3556
+
3557
+ | Status | Hue band | Colors |
3558
+ |---|---|---|
3559
+ | \`danger\` | 15 to 40 | The reds, from crimson to red-orange |
3560
+ | \`warning\` | 45 to 105 | Orange and amber to yellow |
3561
+ | \`success\` | 125 to 180 | The greens, from yellow-green to teal-green |
3562
+ | \`info\` | 200 to 280 | Cyan and blue to indigo |
3563
+
3072
3564
  \`chroma\` sets how saturated the theme feels. Monotone, muted, restrained, and
3073
3565
  vivid all describe chroma, and none of them describes a hue. \`chroma\` has two
3074
3566
  settings:
@@ -3082,12 +3574,20 @@ the gray nearest the page that meets the contrast target, which is a mid gray.
3082
3574
  For black and white, leave out \`brand\`. Neither applies to a color you pinned. A
3083
3575
  pinned color is used exactly as you set it.
3084
3576
 
3577
+ \`tokens\` sets each value after the engine has solved the rest, so no other color
3578
+ is solved against it. Each pair that names the token measures it, and a value
3579
+ that misses its target is reported in \`violations\`. A name the engine does not
3580
+ emit is added as a token of your own. No default pair names it, so only a pair
3581
+ you list in \`pairings\` measures it. A value the engine cannot read is skipped: a
3582
+ token the engine emits keeps its derived value, and a name it does not emit is
3583
+ left out.
3584
+
3085
3585
  ## Contrast
3086
3586
 
3087
3587
  | Field | What it controls |
3088
3588
  |---|---|
3089
3589
  | \`contrastTarget\` | Target contrast for body text. 4.5 is WCAG AA, and 7 is AAA |
3090
- | \`pairings\` | Extra foreground and background pairs to solve, in addition to the pairs in the engine's surface model |
3590
+ | \`pairings\` | The foreground and background pairs to measure, by token name. A background of \`color-well over color-surface\` is a field's well over that surface. The list replaces the pairs from the engine's surface model. The engine measures each pair and solves none of them, and a pair that names a token the engine does not emit is reported as \`unverifiable\` |
3091
3591
 
3092
3592
  The default is 4.5, the WCAG 2.1 AA figure. The European Accessibility Act
3093
3593
  refers to WCAG 2.1 AA, so 4.5 is the figure with legal weight.
@@ -3141,11 +3641,16 @@ you the trade-off that a theme with heavy material makes.
3141
3641
 
3142
3642
  | Field | What it controls |
3143
3643
  |---|---|
3144
- | \`namespace\` | The token prefix. Defaults to \`helix\` |
3644
+ | \`namespace\` | The token prefix. Defaults to \`helix\`. \`acme\` emits \`--acme-color-primary\` |
3145
3645
 
3146
3646
  Use \`namespace\` when your product ships its own token layer built on the
3147
3647
  engine. The rest of the library still reads \`--helix-*\`, so a new namespace
3148
3648
  changes only your own tokens.
3649
+
3650
+ **A namespace is ASCII letters, digits, hyphens, and underscores, with no
3651
+ hyphen at either end.** The engine writes the hyphens around it. Any other
3652
+ namespace is refused with the value it received, such as an empty one, \`--acme\`,
3653
+ or \`acme tokens\`.
3149
3654
  `,
3150
3655
  introduction: `---
3151
3656
  description: What Helix is, and the packages it includes.
@@ -3167,8 +3672,9 @@ The components are custom elements, so you can use them in any framework or
3167
3672
  without one.
3168
3673
 
3169
3674
  **Helix computes your theme.** You enter a few inputs, such as your brand color,
3170
- and Helix computes every token from them. Every text and background pair meets
3171
- its contrast target. See [Theming](./theming).
3675
+ and Helix computes every token from them. Helix measures the contrast of each
3676
+ text and background pair it solves, and reports any pair that misses its target.
3677
+ See [Theming](./theming).
3172
3678
 
3173
3679
  **AI agents can read it.** A custom elements manifest describes every component,
3174
3680
  with typed options, documented parts, and generated React prop names, so an
@@ -3223,7 +3729,7 @@ custom events. [React](./react) covers both.
3223
3729
  See [Tiers](./tiers) for how the packages relate and which one to use.
3224
3730
  `,
3225
3731
  localization: `---
3226
- description: Set the locale of date, time, and number controls, translate the built-in words, and lay out pages right to left.
3732
+ description: Set the locale of date, time, and number controls, translate the built-in words, lay out pages right to left, and color a rise and a fall for your market.
3227
3733
  section: Essentials
3228
3734
  title: Localization and RTL
3229
3735
  ---
@@ -3387,7 +3893,7 @@ Register the words for a language once, and every element in that language
3387
3893
  speaks them:
3388
3894
 
3389
3895
  \`\`\`js
3390
- import { registerWords } from '@fusion.dev/helix'
3896
+ import { registerWords } from '@fusion.dev/helix/words'
3391
3897
 
3392
3898
  registerWords('pl', {
3393
3899
  'helix-combobox': {
@@ -3403,8 +3909,12 @@ registerWords('pl', {
3403
3909
  \`\`\`
3404
3910
 
3405
3911
  Each family's words are keyed by its tag, such as \`helix-combobox\`, and then
3406
- by the property name. The headless and styled tiers share one key. To type the
3407
- keys, import the generated types once:
3912
+ by the property name. The headless and styled tiers share one key.
3913
+ \`@fusion.dev/helix/words\` holds \`registerWords\` without the elements, so a page
3914
+ can register its words before it loads any, and it types each key and property.
3915
+ A page that uses only the headless tier imports
3916
+ \`@fusion.dev/helix-headless/words\`. With \`registerWords\` imported from another
3917
+ entry, import the types once:
3408
3918
 
3409
3919
  \`\`\`ts
3410
3920
  import type {} from '@fusion.dev/helix/words'
@@ -3554,6 +4064,81 @@ Aria's \`@react-stately/color\` translates, so you can reuse that table.
3554
4064
  Helix ships English words and no translations. Pass the words from your own
3555
4065
  translation system, either to \`registerWords\` or to the properties.
3556
4066
 
4067
+ ## Rise and fall colors
4068
+
4069
+ Markets draw a price movement in different colors:
4070
+
4071
+ - Mainland China, Taiwan, and Japan draw a rise in red and a fall in green.
4072
+ - Hong Kong, like most other markets, draws a rise in green and a fall in red.
4073
+
4074
+ Colors that show a status, such as an error or a warning, are the same in all
4075
+ of these markets. Only a color that shows which way a value moved changes.
4076
+
4077
+ Two tokens hold these colors:
4078
+
4079
+ | Token | Default |
4080
+ |---|---|
4081
+ | \`--helix-color-rise\` | \`--helix-color-success\`, which is green |
4082
+ | \`--helix-color-fall\` | \`--helix-color-danger\`, which is red |
4083
+
4084
+ \`Trend\` uses them when you set \`color-by="movement"\`. Without it, the color of a
4085
+ trend follows \`good\`, which says whether the change is an improvement.
4086
+
4087
+ For a page that shows one of the markets that draw a rise in red, swap the two
4088
+ tokens at the root of the page, and set \`color-by\` on each trend:
4089
+
4090
+ \`\`\`css
4091
+ :root,
4092
+ [data-helix-theme] {
4093
+ --helix-color-fall: var(--helix-color-success);
4094
+ --helix-color-rise: var(--helix-color-danger);
4095
+ }
4096
+ \`\`\`
4097
+
4098
+ \`\`\`html
4099
+ <helix-trend
4100
+ color-by="movement"
4101
+ format-options='{"style":"percent","maximumFractionDigits":2}'
4102
+ value="0.0124"
4103
+ >
4104
+ <span slot="label">Shanghai Composite</span>
4105
+ </helix-trend>
4106
+ \`\`\`
4107
+
4108
+ An element with \`data-helix-theme\` declares every token again for the part of
4109
+ the page it themes, so the rule names \`[data-helix-theme]\` beside \`:root\`. To
4110
+ color one part of a page, such as a panel of Shanghai prices on a page about
4111
+ several markets, set the two tokens on that panel.
4112
+
4113
+ The two tokens point at the status colors, so after the swap each one still
4114
+ measures at least 4.5:1 on the page and on every surface, in both modes. A
4115
+ theme that changes \`success\` and \`danger\` changes them too.
4116
+
4117
+ **Helix does not set these from \`lang\`.** The language of a page does not name
4118
+ its market. A page in Chinese for Hong Kong draws a rise in green, a \`zh-Hant\`
4119
+ page with no region could be for Taiwan or for Hong Kong, and a page in English
4120
+ can show Shanghai prices. If the language of your page does decide its market,
4121
+ select on the language yourself:
4122
+
4123
+ \`\`\`css
4124
+ :is(:root, [data-helix-theme]):is(:lang(zh-CN), :lang(zh-TW), :lang(ja)) {
4125
+ --helix-color-fall: var(--helix-color-success);
4126
+ --helix-color-rise: var(--helix-color-danger);
4127
+ }
4128
+ \`\`\`
4129
+
4130
+ A page in \`zh-HK\` matches none of these, so it keeps green for a rise.
4131
+
4132
+ Other components color a series by its place in the list, or by a tone you
4133
+ name. To draw one in the color of a rise or a fall, set its own color to the
4134
+ token, such as \`--sparkline-color: var(--helix-color-rise)\` on the sparkline of
4135
+ a price that went up.
4136
+
4137
+ Sources:
4138
+
4139
+ - [Benzinga, on the colors of stock prices in China, Japan, and Taiwan](https://www.benzinga.com/general/education/21/10/23258001/did-you-know-you-dont-want-to-be-in-the-green-if-youre-trading-in-china-japan-or-taiwan)
4140
+ - [Feng Jiang, Rui Zhang, and Su Lu, on red and green in the Chinese and Hong Kong markets](https://www.behavioraleconomics.com/when-red-means-go)
4141
+
3557
4142
  ## What to check when you add a language
3558
4143
 
3559
4144
  - **Long strings.** Labels in German and Finnish are often long. A control sized
@@ -3562,6 +4147,9 @@ translation system, either to \`registerWords\` or to the properties.
3562
4147
  buttons may now fit four.
3563
4148
  - **The \`lang\` attribute.** Set it as well as translating the strings.
3564
4149
  Hyphenation, quotation marks, and font fallback all depend on it.
4150
+ - **A market's colors.** If the new language comes with a market that draws a
4151
+ rise in red, swap the rise and fall tokens. See
4152
+ [Rise and fall colors](#rise-and-fall-colors).
3565
4153
  - **Numerals.** \`Intl\` formats the digits. A \`%\` or a currency symbol hardcoded
3566
4154
  in your own markup does not move to the correct side.
3567
4155
  - **Icons that point.** A caret that a component draws turns by itself. An icon
@@ -3717,6 +4305,11 @@ theme. Map labels meet a contrast target of 4.5 against the land. Pass
3717
4305
  \`contrast\` to change it, \`language\` to choose which name each place is labeled
3718
4306
  with, and \`tiles\` and \`glyphs\` to use your own tile server.
3719
4307
 
4308
+ The tiles come from OpenFreeMap unless you pass \`tiles\`, and so do the glyphs
4309
+ unless you pass \`glyphs\` or \`font\`. OpenFreeMap needs no key. Your reader's
4310
+ browser requests the map's tiles and glyphs from OpenFreeMap's servers, and
4311
+ \`mapSnapshot\` requests its tiles from them on your server.
4312
+
3720
4313
  Pass \`font\` to draw the labels in your theme's typeface. MapLibre draws the
3721
4314
  letters in the page from the font your page has loaded, so there are no glyph
3722
4315
  files to fetch. \`mapFontFrom(frame)\` reads the typeface from your theme, and
@@ -3724,6 +4317,8 @@ files to fetch. \`mapFontFrom(frame)\` reads the typeface from your theme, and
3724
4317
  create the map, because MapLibre keeps each letter as first drawn:
3725
4318
 
3726
4319
  \`\`\`js
4320
+ import { fontReady, mapFontFrom } from '@fusion.dev/helix-map'
4321
+
3727
4322
  const font = mapFontFrom(frame)
3728
4323
  await fontReady(font)
3729
4324
  const map = new maplibregl.Map({ container: 'map', style: mapLibreStyle(colors, { font }) })
@@ -3803,7 +4398,7 @@ const view = {
3803
4398
  zoom: 14,
3804
4399
  }
3805
4400
  const snapshot = await mapSnapshot(view)
3806
- // Inside helix-map, after <div id="map"></div>, write the snapshot's markup.
4401
+ // Inside helix-map, after <div id="map"></div>, write snapshot.markup.
3807
4402
  \`\`\`
3808
4403
 
3809
4404
  Give MapLibre the same \`center\`, \`zoom\`, \`pitch\`, and \`bearing\`, and the snapshot
@@ -3811,15 +4406,57 @@ lines up with the map that replaces it. \`width\` and \`height\` are the box it
3811
4406
  drawn for, and a smaller box shows its middle. A snapshot draws the land, water,
3812
4407
  parks, buildings, and roads, and leaves the labels to MapLibre. Its colors are
3813
4408
  your theme's tokens, so it is light or dark with the page and follows a theme
3814
- change. It resolves to an empty string when any tile it needs cannot be read
3815
- in time, so a snapshot is whole or absent and the map draws in its place. Each
4409
+ change. Its \`picture\` is an empty string when any tile it needs cannot be read
4410
+ in time, so the picture is whole or absent and the map draws in its place. Each
3816
4411
  request is given \`timeout\` milliseconds, 3000 by default. Cache only a snapshot
3817
- that is not empty.
3818
-
3819
- In a framework that renders the markup for you, such as React, put the snapshot
4412
+ whose \`picture\` is not empty.
4413
+
4414
+ A snapshot reads OpenFreeMap's tiles from your server by default. Pass the
4415
+ \`tiles\` you give \`mapLibreStyle\` as \`mapSnapshot(view, { tiles })\` to read
4416
+ your own.
4417
+
4418
+ **The snapshot carries the map credit.** Most tile licenses require the credit
4419
+ wherever the map is shown, including before MapLibre loads and on a page that
4420
+ never loads it. \`markup\` is the picture followed by the credit your tiles
4421
+ name, in a \`<span slot="attribution">\` marked \`data-helix-map-credit\`, so the
4422
+ frame shows the credit from the first paint. \`connectMap\` hides that credit
4423
+ once it writes MapLibre's, which names the same tiles. When a tile cannot be
4424
+ read, the credit stays and \`markup\` is the credit alone, so write \`markup\`
4425
+ whether or not the picture was drawn.
4426
+
4427
+ A snapshot also holds the two apart: \`picture\`, the map alone, and \`credit\`,
4428
+ the credit as runs of words and links, each \`{ text }\` or \`{ href, text }\`.
4429
+ In a framework that renders the markup for you, such as React, put \`picture\`
3820
4430
  in an element that is a direct child of the frame and has the
3821
- \`data-helix-map-snapshot\` attribute. \`connectMap\` hides that element, and leaves
3822
- removing it to your framework.
4431
+ \`data-helix-map-snapshot\` attribute. Write \`credit\` in a
4432
+ \`<span slot="attribution" data-helix-map-credit>\`, also a direct child of the
4433
+ frame:
4434
+
4435
+ \`\`\`jsx
4436
+ <helix-map label="Downtown Austin">
4437
+ <div id="map" />
4438
+ <div dangerouslySetInnerHTML={{ __html: snapshot.picture }} data-helix-map-snapshot="" />
4439
+ {snapshot.credit.length > 0 ? (
4440
+ <span data-helix-map-credit="" slot="attribution">
4441
+ {snapshot.credit.map((part, index) =>
4442
+ part.href ? (
4443
+ <a data-link="quiet" href={part.href} key={index} rel="noopener noreferrer" target="_blank">
4444
+ {part.text}
4445
+ </a>
4446
+ ) : (
4447
+ part.text
4448
+ ),
4449
+ )}
4450
+ </span>
4451
+ ) : null}
4452
+ </helix-map>
4453
+ \`\`\`
4454
+
4455
+ \`connectMap\` hides both elements, and leaves removing them to your framework.
4456
+
4457
+ A credit you slot yourself, with no \`data-helix-map-credit\`, wins. Write
4458
+ \`picture\` alone beside it. A snapshot credit already in the frame is hidden
4459
+ when \`connectMap\` connects.
3823
4460
 
3824
4461
  A snapshot is a flat map. A map zoomed out past about 6 is a globe in MapLibre,
3825
4462
  so draw snapshots of city and street views.
@@ -3897,7 +4534,9 @@ finished.
3897
4534
  | \`helix-map-contours\` | Contour lines, each at one height |
3898
4535
  | \`helix-map-landcover\` | What covers the ground: forest, grass, farmland, sand, rock, and ice |
3899
4536
 
3900
- Each element takes \`tone\`, such as \`primary\` or \`danger\`, for its color.
4537
+ Markers, routes, areas, positions, points, and buildings take \`tone\`, such as
4538
+ \`primary\` or \`danger\`, for their color. Raster tiles keep their own colors, and
4539
+ terrain, contours, and landcover take theirs from your theme.
3901
4540
 
3902
4541
  ### Markers
3903
4542
 
@@ -3912,11 +4551,14 @@ Each element takes \`tone\`, such as \`primary\` or \`danger\`, for its color.
3912
4551
  </helix-map-marker>
3913
4552
  \`\`\`
3914
4553
 
3915
- A marker is a button placed at its latitude and longitude. Its \`label\` appears
3916
- when you hover over or focus the marker. The \`icon\` slot holds a letter, a
3917
- number, or an icon, and \`shape\` sets the marker's form: \`ring\`, \`rounded\`,
3918
- \`square\`, \`diamond\`, or \`pin\`. Pressing a marker sends \`helix-activate\` from the
3919
- frame with the marker's \`name\`, or its \`label\` if it has no name.
4554
+ A marker is a button placed at its latitude and longitude. Its point is as
4555
+ large as the map's landmarks, in your primary color, with a dot in the middle
4556
+ and a halo of the same color around it, so your places stand out from the
4557
+ landmarks. Its \`label\` appears when you hover over or focus the marker. The
4558
+ \`icon\` slot holds a letter, a number, or an icon in place of the dot, and
4559
+ \`shape\` sets the marker's form: \`ring\`, \`rounded\`, \`square\`, \`diamond\`, or
4560
+ \`pin\`. Pressing a marker sends \`helix-activate\` from the frame with the
4561
+ marker's \`name\`, or its \`label\` if it has no name.
3920
4562
 
3921
4563
  To show that a place has been visited, add \`passed\`. The marker is drawn in a
3922
4564
  muted color, and screen readers hear its name followed by "passed". Use
@@ -4429,11 +5071,13 @@ We publish the method so you can repeat each measurement.
4429
5071
  Each component has two columns. Components share a runtime, so no single
4430
5072
  number gives the cost of one component:
4431
5073
 
4432
- - **Alone** is a page that uses the component and nothing else. It includes the runtime the component needs, so a sum of Alone numbers counts the runtime once for every component.
5074
+ - **Alone** is a page that imports the component's own entry and nothing else. It includes the runtime the component needs, so a sum of Alone numbers counts the runtime once for every component.
4433
5075
  - **Added** is what the component adds to a page that already has the runtime. It leaves out the shared runtime, so it understates the cost of your first component.
4434
5076
 
4435
5077
  The cost on your page falls between the two numbers.
4436
5078
 
5079
+ A container that holds another family's elements does not register them, so neither column includes them. A page with a Select also imports \`option\`, and a page with a Data Table also imports \`table\`. Each component's page lists the entries to import beside it.
5080
+
4437
5081
  ## The shared runtime
4438
5082
 
4439
5083
  The shared runtime is Lit and \`@fusion.dev/helix-core\`. Every element needs it,
@@ -4447,7 +5091,7 @@ on its own.
4447
5091
 
4448
5092
  | | Raw | Gzip | Brotli |
4449
5093
  |---|---|---|---|
4450
- | Lit and \`@fusion.dev/helix-core\` | 254,802 | 82,008 | 70,115 |
5094
+ | Lit and \`@fusion.dev/helix-core\` | 264,965 | 85,796 | 73,210 |
4451
5095
 
4452
5096
  ## Whole packages
4453
5097
 
@@ -4458,12 +5102,12 @@ either barrel. The per-component tables show what your page pays.
4458
5102
 
4459
5103
  | Package | Raw | Gzip | Brotli | |
4460
5104
  |---|---|---|---|---|
4461
- | \`@fusion.dev/helix-core\` | 254,011 | 81,843 | 69,762 | The element base class, mixins, decorators, and floating positioning. |
4462
- | \`@fusion.dev/helix-headless\` | 1,432,689 | 399,675 | 300,018 | Every headless family. The barrel entry includes all of them. |
5105
+ | \`@fusion.dev/helix-core\` | 264,174 | 85,657 | 72,846 | The element base class, mixins, decorators, and floating positioning. |
5106
+ | \`@fusion.dev/helix-headless\` | 1,457,984 | 408,952 | 306,850 | Every headless family. The barrel entry includes all of them. |
4463
5107
  | \`@fusion.dev/helix-icons\` | 56,111 | 17,847 | 15,166 | The whole glyph set. |
4464
- | \`@fusion.dev/helix-theme\` | 3,626 | 1,597 | 1,384 | The JavaScript that runs the derivation engine. Most sites run it at build time, and a theme switcher runs it in the browser. |
4465
- | \`fusion_helix_theme_bg.wasm\` | 490,971 | 155,905 | 125,923 | The derivation engine itself, in WebAssembly. The entry above loads it when the engine runs. |
4466
- | \`@fusion.dev/helix\` | 2,151,139 | 554,602 | 395,565 | Every styled family. |
5108
+ | \`@fusion.dev/helix-theme\` | 4,030 | 1,791 | 1,560 | The JavaScript that runs the derivation engine. Most sites run it at build time, and a theme switcher runs it in the browser. |
5109
+ | \`fusion_helix_theme_bg.wasm\` | 408,830 | 152,342 | 122,874 | The derivation engine itself, in WebAssembly. The entry above loads it when the engine runs. |
5110
+ | \`@fusion.dev/helix\` | 2,179,209 | 564,636 | 402,737 | Every styled family. |
4467
5111
  | \`lit\` | 15,394 | 6,118 | 5,460 | Lit itself, listed so you can subtract it from every row above. |
4468
5112
 
4469
5113
  ## Stylesheets
@@ -4476,21 +5120,21 @@ shows the whole cost of using the base layer on its own.
4476
5120
 
4477
5121
  | File | Raw | Gzip | Brotli | |
4478
5122
  |---|---|---|---|---|
4479
- | \`base.css\` | 34,352 | 4,806 | 4,174 | The base layer. It styles markup Helix does not render, and it works with no JavaScript. |
5123
+ | \`base.css\` | 33,901 | 4,783 | 4,149 | The base layer. It styles markup Helix does not render, and it works with no JavaScript. |
4480
5124
  | \`elements.css\` | 19,633 | 2,937 | 2,632 | Hides each element until its definition loads. It is generated from the tags in \`@fusion.dev/helix\`. |
4481
5125
  | \`helix.css\` | 207 | 148 | 118 | The other four stylesheets in one import, in the order they must load. |
4482
5126
  | \`primitives.css\` | 7,511 | 1,401 | 1,221 | The primitive layer. It is private, and it ships because \`tokens.css\` refers to it. |
4483
- | \`tokens.css\` | 22,747 | 2,757 | 2,314 | The 197 semantic tokens. Every styled component needs them. |
5127
+ | \`tokens.css\` | 22,846 | 2,778 | 2,332 | The 199 semantic tokens. Every styled component needs them. |
4484
5128
 
4485
5129
  ## What the numbers say
4486
5130
 
4487
- **Most of your first component is the shared runtime.** The median styled family is 18,850 bytes gzipped alone, and it adds 4,385 to a page that already has Helix. The shared runtime is 77 percent of the first component, and the component itself is 23 percent. Ten typical components cost the first number once, plus nine of the added number. The two columns in the tables below let you work that out for your own components.
5131
+ **Most of your first component is the shared runtime.** The median styled family is 19,331 bytes gzipped alone, and it adds 4,376 to a page that already has Helix. The shared runtime is 77 percent of the first component, and the component itself is 23 percent. Ten typical components cost the first number once, plus nine of the added number. The two columns in the tables below let you work that out for your own components.
4488
5132
 
4489
- Across the 113 styled components, the smallest is \`code\` at 8,332 bytes gzipped alone, and the largest is \`data-table\` at 66,817. Every family together is 554,602.
5133
+ Across the 113 styled components, the smallest is \`code\` at 8,561 bytes gzipped alone, and the largest is \`data-table\` at 67,583. Every family together is 564,636.
4490
5134
 
4491
- No family imports a barrel. The \`barrels\` rule in \`fusion x workspace\` enforces that. \`@fusion.dev/helix-headless\` marks every module in \`dist\` as having side effects, which is true of a file that calls \`customElements.define\`. A bundler can remove none of it. One \`import { HelixHeadlessButton } from '@fusion.dev/helix-headless'\` costs 399,675 bytes gzipped, whatever else the page imports. That is 21 times the median family above. All 113 styled families import the headless family they extend directly. The rule checks the shape of the import, so it applies to any package that sets \`sideEffects\`, whatever its name.
5135
+ No family imports a barrel. The \`barrels\` rule in \`fusion x workspace\` enforces that. \`@fusion.dev/helix-headless\` marks every module in \`dist\` as having side effects, which is true of a file that calls \`customElements.define\`. A bundler can remove none of it. One \`import { HelixHeadlessButton } from '@fusion.dev/helix-headless'\` costs 408,952 bytes gzipped, whatever else the page imports. That is 21 times the median family above. All 113 styled families import the headless family they extend directly. The rule checks the shape of the import, so it applies to any package that sets \`sideEffects\`, whatever its name.
4492
5136
 
4493
- The headless tier never had the problem, so it serves as the control. \`@fusion.dev/helix-core\` is \`sideEffects: false\`, and the median headless family is 17,663 bytes gzipped alone.
5137
+ The headless tier never had the problem, so it serves as the control. \`@fusion.dev/helix-core\` is \`sideEffects: false\`, and the median headless family is 18,197 bytes gzipped alone.
4494
5138
 
4495
5139
  ## Compared with other systems
4496
5140
 
@@ -4502,43 +5146,43 @@ The table has no whole-library row. That row would compare how many components e
4502
5146
 
4503
5147
  | Component | System | Raw | Gzip | Brotli |
4504
5148
  |---|---|---|---|---|
4505
- | \`button\` | **Helix** | 41,612 | 14,008 | 12,455 |
5149
+ | \`button\` | **Helix** | 42,638 | 14,385 | 12,833 |
4506
5150
  | | Carbon | 133,058 | 20,959 | 17,685 |
4507
5151
  | | Material Web | 47,060 | 14,173 | 12,591 |
4508
5152
  | | Spectrum | 98,792 | 22,045 | 18,674 |
4509
5153
  | | Web Awesome | 136,044 | 33,730 | 28,760 |
4510
- | \`checkbox\` | **Helix** | 46,865 | 15,190 | 13,521 |
5154
+ | \`checkbox\` | **Helix** | 47,728 | 15,518 | 13,864 |
4511
5155
  | | Carbon | 60,255 | 15,609 | 13,550 |
4512
5156
  | | Material Web | 43,785 | 13,556 | 12,015 |
4513
5157
  | | Spectrum | 62,492 | 15,437 | 13,481 |
4514
5158
  | | Web Awesome | 115,251 | 28,728 | 24,420 |
4515
- | \`dialog\` | **Helix** | 43,407 | 14,900 | 13,275 |
5159
+ | \`dialog\` | **Helix** | 44,311 | 15,226 | 13,609 |
4516
5160
  | | Carbon | 510,866 | 67,390 | 39,189 |
4517
5161
  | | Material Web | 34,238 | 11,510 | 10,229 |
4518
5162
  | | Spectrum | 155,708 | 31,899 | 26,262 |
4519
5163
  | | Web Awesome | 148,657 | 36,692 | 31,388 |
4520
- | \`radio\` | **Helix** | 50,349 | 15,976 | 14,205 |
5164
+ | \`radio\` | **Helix** | 51,120 | 16,282 | 14,513 |
4521
5165
  | | Carbon | 63,055 | 16,887 | 14,605 |
4522
5166
  | | Material Web | 41,044 | 13,234 | 11,757 |
4523
5167
  | | Spectrum | 38,685 | 10,660 | 9,370 |
4524
5168
  | | Web Awesome | 112,026 | 27,416 | 23,128 |
4525
- | \`switch\` | **Helix** | 43,562 | 14,636 | 13,040 |
5169
+ | \`switch\` | **Helix** | 44,449 | 14,978 | 13,325 |
4526
5170
  | | Carbon | 69,423 | 17,127 | 14,764 |
4527
5171
  | | Material Web | 50,155 | 14,577 | 12,935 |
4528
5172
  | | Spectrum | 62,322 | 15,059 | 13,321 |
4529
5173
  | | Web Awesome | 45,876 | 15,003 | 13,475 |
4530
- | \`tabs\` | **Helix** | 55,263 | 17,752 | 15,831 |
5174
+ | \`tabs\` | **Helix** | 56,031 | 18,047 | 16,061 |
4531
5175
  | | Carbon | 578,766 | 73,537 | 43,235 |
4532
5176
  | | Material Web | 38,883 | 12,820 | 11,368 |
4533
5177
  | | Spectrum | 64,875 | 18,298 | 16,283 |
4534
5178
  | | Web Awesome | 153,439 | 37,030 | 31,737 |
4535
- | \`tooltip\` | **Helix** | 61,420 | 21,834 | 19,360 |
5179
+ | \`tooltip\` | **Helix** | 62,502 | 22,262 | 19,753 |
4536
5180
  | | Carbon | 189,382 | 30,354 | 22,273 |
4537
5181
  | | Material Web | | not shipped | |
4538
5182
  | | Spectrum | 136,599 | 40,552 | 35,286 |
4539
5183
  | | Web Awesome | 70,568 | 24,005 | 21,107 |
4540
5184
 
4541
- At least one system is smaller than Helix on \`checkbox\`, \`dialog\`, \`radio\`, \`switch\`, \`tabs\`. The largest gap is radio against Spectrum, at 5,316 bytes gzipped. 6 of 27 peer measurements are smaller than the Helix row.
5185
+ At least one system is smaller than Helix on \`button\`, \`checkbox\`, \`dialog\`, \`radio\`, \`switch\`, \`tabs\`. The largest gap is radio against Spectrum, at 5,622 bytes gzipped. 8 of 27 peer measurements are smaller than the Helix row.
4542
5186
 
4543
5187
  Helix's \`radio\` family includes Radio Group, so its row carries an element that some peer rows measure without.
4544
5188
 
@@ -4569,8 +5213,8 @@ A system covers a job when its manifest defines an element with one of a fixed l
4569
5213
  | Chat | \`helix-chat\` | | | | |
4570
5214
  | Command palette | \`helix-command\` | | | | |
4571
5215
  | Product tour | \`helix-tour\` | | | \`sp-coachmark\` | |
4572
- | Node diagrams | \`helix-canvas\` (experimental) | | | | |
4573
- | Maps | \`helix-map\` (experimental) | | | | |
5216
+ | Node diagrams | \`helix-canvas\` | | | | |
5217
+ | Maps | \`helix-map\` | | | | |
4574
5218
  | Video player | \`helix-video\` | | | | |
4575
5219
  | Carousel | \`helix-carousel\` | | | | \`wa-carousel\` (experimental) |
4576
5220
  | **Jobs covered** | 16 of 16 | 6 of 16 | 0 of 16 | 5 of 16 | 3 of 16 |
@@ -4612,112 +5256,112 @@ Structure, accessibility, state, and form participation. It has no presentation
4612
5256
 
4613
5257
  | Family | Alone raw | Alone gzip | Alone brotli | Added raw | Added gzip | Added brotli |
4614
5258
  |---|---|---|---|---|---|---|
4615
- | \`accordion\` | 31,879 | 11,804 | 10,469 | 5,328 | 1,580 | 1,165 |
4616
- | \`announcement\` | 36,692 | 13,312 | 11,874 | 4,681 | 1,368 | 960 |
4617
- | \`app-header\` | 32,289 | 11,898 | 10,586 | 5,870 | 1,747 | 1,194 |
4618
- | \`area-chart\` | 90,301 | 31,726 | 28,040 | 14,057 | 4,342 | 3,430 |
4619
- | \`avatar\` | 30,463 | 11,595 | 10,389 | 3,957 | 1,213 | 963 |
4620
- | \`badge\` | 27,088 | 10,568 | 9,406 | 2,942 | 889 | 599 |
4621
- | \`bar-chart\` | 94,416 | 32,820 | 29,035 | 18,022 | 5,410 | 4,321 |
4622
- | \`blockquote\` | 24,107 | 9,421 | 8,330 | 2,401 | 877 | 594 |
4623
- | \`breadcrumbs\` | 36,279 | 13,907 | 12,433 | 7,357 | 2,410 | 1,979 |
4624
- | \`button\` | 35,194 | 12,591 | 11,206 | 4,922 | 1,424 | 1,043 |
4625
- | \`button-group\` | 27,640 | 10,381 | 9,203 | 4,926 | 1,515 | 1,168 |
4626
- | \`callout\` | 35,256 | 13,593 | 12,065 | 2,884 | 964 | 718 |
4627
- | \`canvas\` | 108,483 | 34,750 | 30,415 | 68,924 | 20,931 | 17,512 |
4628
- | \`card\` | 23,824 | 9,092 | 8,084 | 2,090 | 626 | 425 |
4629
- | \`carousel\` | 43,576 | 15,462 | 13,785 | 15,235 | 4,220 | 3,263 |
4630
- | \`chat\` | 79,505 | 26,469 | 23,447 | 27,570 | 8,044 | 6,267 |
4631
- | \`checkbox\` | 40,374 | 13,680 | 12,220 | 3,107 | 960 | 604 |
4632
- | \`checkbox-group\` | 47,602 | 15,599 | 13,904 | 8,324 | 2,348 | 1,734 |
4633
- | \`code\` | 20,301 | 8,015 | 7,118 | 138 | 66 | 61 |
4634
- | \`code-block\` | 102,738 | 34,889 | 30,795 | 36,843 | 10,711 | 8,686 |
4635
- | \`color-input\` | 141,792 | 45,090 | 39,350 | 55,133 | 14,292 | 11,566 |
4636
- | \`color-picker\` | 103,415 | 32,966 | 28,872 | 47,251 | 12,669 | 10,154 |
4637
- | \`combobox\` | 132,023 | 41,951 | 36,953 | 35,789 | 9,084 | 7,127 |
4638
- | \`command\` | 99,560 | 31,521 | 27,741 | 30,059 | 8,103 | 6,352 |
4639
- | \`copy-button\` | 80,548 | 27,648 | 24,494 | 18,340 | 4,938 | 3,819 |
4640
- | \`data-table\` | 199,900 | 61,427 | 52,554 | 89,488 | 23,798 | 19,031 |
4641
- | \`date-input\` | 141,223 | 44,511 | 39,196 | 38,227 | 9,423 | 7,354 |
4642
- | \`date-picker\` | 61,360 | 20,936 | 18,757 | 20,345 | 5,600 | 4,295 |
4643
- | \`description-list\` | 22,900 | 8,765 | 7,767 | 1,565 | 491 | 327 |
4644
- | \`dialog\` | 38,294 | 13,634 | 12,133 | 5,837 | 1,715 | 1,352 |
4645
- | \`divider\` | 22,991 | 8,923 | 7,885 | 1,415 | 503 | 294 |
4646
- | \`donut-chart\` | 89,212 | 31,128 | 27,565 | 14,639 | 4,495 | 3,565 |
4647
- | \`drawer\` | 42,939 | 15,158 | 13,507 | 7,697 | 2,296 | 1,845 |
4648
- | \`drilldown\` | 42,756 | 14,941 | 13,294 | 9,708 | 2,702 | 2,043 |
4649
- | \`empty\` | 24,241 | 9,287 | 8,241 | 1,951 | 639 | 446 |
4650
- | \`file-input\` | 57,757 | 20,018 | 17,801 | 13,093 | 4,121 | 3,261 |
4651
- | \`filter-input\` | 97,846 | 32,818 | 29,104 | 21,628 | 6,186 | 4,812 |
4652
- | \`format-date\` | 30,158 | 11,290 | 10,084 | 4,431 | 1,265 | 1,031 |
4653
- | \`graph\` | 75,588 | 24,761 | 21,885 | 23,095 | 6,734 | 5,372 |
4654
- | \`heatmap\` | 98,128 | 33,502 | 29,622 | 18,941 | 5,574 | 4,404 |
4655
- | \`icon\` | 82,157 | 28,049 | 23,966 | 58,303 | 19,610 | 16,156 |
4656
- | \`icon-button\` | 35,305 | 12,620 | 11,226 | 5,031 | 1,446 | 1,101 |
4657
- | \`image\` | 25,504 | 9,479 | 8,419 | 3,822 | 1,132 | 974 |
4658
- | \`input\` | 46,361 | 15,975 | 14,079 | 7,351 | 2,387 | 1,659 |
4659
- | \`kbd\` | 27,511 | 10,569 | 9,333 | 3,516 | 1,111 | 777 |
4660
- | \`legend\` | 35,184 | 12,934 | 11,520 | 2,956 | 978 | 650 |
4661
- | \`lightbox\` | 55,724 | 18,817 | 16,716 | 16,728 | 4,631 | 3,404 |
4662
- | \`line-chart\` | 91,335 | 31,879 | 28,263 | 14,760 | 4,398 | 3,569 |
4663
- | \`list\` | 61,803 | 21,293 | 18,953 | 15,241 | 4,414 | 3,389 |
4664
- | \`listbox\` | 76,711 | 25,427 | 22,460 | 15,562 | 4,563 | 3,379 |
4665
- | \`loader\` | 26,379 | 10,040 | 8,940 | 2,774 | 913 | 710 |
4666
- | \`log\` | 79,881 | 27,342 | 24,218 | 37,541 | 11,701 | 9,828 |
4667
- | \`map\` | 79,178 | 24,571 | 21,536 | 37,042 | 10,074 | 7,981 |
4668
- | \`megamenu\` | 60,089 | 21,721 | 19,225 | 8,657 | 2,397 | 1,743 |
4669
- | \`menu\` | 97,473 | 32,552 | 28,757 | 30,180 | 7,919 | 6,266 |
4670
- | \`meter\` | 31,721 | 12,025 | 10,727 | 4,893 | 1,409 | 1,203 |
4671
- | \`motion-toggle\` | 25,678 | 9,986 | 8,876 | 1,426 | 509 | 354 |
4672
- | \`navigation\` | 33,088 | 12,436 | 11,040 | 3,365 | 1,157 | 886 |
4673
- | \`notifications\` | 58,346 | 19,880 | 17,760 | 18,207 | 5,058 | 3,845 |
4674
- | \`number-input\` | 56,271 | 18,841 | 16,806 | 13,522 | 3,584 | 2,705 |
4675
- | \`object-inspector\` | 113,929 | 38,373 | 33,742 | 35,491 | 10,254 | 8,197 |
4676
- | \`option\` | 26,477 | 9,763 | 8,666 | 4,523 | 1,184 | 896 |
4677
- | \`outline\` | 33,612 | 12,597 | 11,180 | 7,856 | 2,558 | 1,958 |
4678
- | \`pagination\` | 136,258 | 43,650 | 37,976 | 45,379 | 11,839 | 9,383 |
4679
- | \`pin-input\` | 47,068 | 16,414 | 14,643 | 6,735 | 2,224 | 1,638 |
4680
- | \`popover\` | 63,853 | 22,981 | 20,296 | 6,373 | 1,853 | 1,293 |
4681
- | \`progress\` | 28,084 | 10,727 | 9,550 | 2,969 | 909 | 633 |
4682
- | \`qr-code\` | 42,719 | 16,647 | 14,821 | 17,001 | 6,288 | 5,239 |
4683
- | \`quote\` | 22,516 | 8,884 | 7,826 | 1,210 | 510 | 385 |
4684
- | \`radio\` | 42,561 | 14,464 | 12,890 | 3,817 | 1,200 | 751 |
4685
- | \`rating\` | 50,257 | 17,292 | 15,436 | 8,027 | 2,434 | 1,801 |
4686
- | \`scatter-chart\` | 91,226 | 31,675 | 27,987 | 16,123 | 4,842 | 3,865 |
4687
- | \`scroll-area\` | 30,430 | 11,078 | 9,814 | 8,499 | 2,573 | 2,022 |
4688
- | \`select\` | 115,289 | 38,175 | 33,424 | 26,031 | 7,119 | 5,380 |
4689
- | \`skeleton\` | 22,328 | 8,643 | 7,676 | 991 | 396 | 245 |
4690
- | \`skip-link\` | 23,871 | 9,337 | 8,264 | 1,224 | 474 | 348 |
4691
- | \`slider\` | 52,873 | 17,827 | 15,953 | 13,158 | 3,777 | 2,938 |
4692
- | \`sparkline\` | 47,984 | 17,499 | 15,598 | 5,979 | 2,127 | 1,638 |
4693
- | \`spatial-bar-chart\` | 101,617 | 35,955 | 31,747 | 19,779 | 6,524 | 5,174 |
4694
- | \`spatial-heatmap\` | 113,473 | 38,794 | 34,033 | 31,592 | 9,582 | 7,685 |
4695
- | \`spatial-ribbon-chart\` | 100,738 | 35,553 | 31,418 | 20,718 | 6,906 | 5,498 |
4696
- | \`spatial-scatter-chart\` | 98,971 | 34,841 | 30,696 | 19,012 | 6,269 | 4,923 |
4697
- | \`spatial-surface-chart\` | 101,895 | 36,125 | 31,812 | 21,876 | 7,441 | 6,027 |
4698
- | \`spatial-treemap\` | 103,011 | 36,754 | 32,306 | 21,297 | 7,375 | 5,894 |
4699
- | \`speed-dial\` | 68,751 | 24,717 | 21,895 | 10,914 | 2,968 | 2,274 |
4700
- | \`splitter\` | 43,751 | 15,168 | 13,534 | 14,396 | 3,840 | 3,025 |
4701
- | \`stat\` | 25,255 | 9,674 | 8,602 | 2,302 | 753 | 539 |
4702
- | \`steps\` | 53,623 | 18,635 | 16,600 | 13,601 | 3,845 | 2,936 |
4703
- | \`switch\` | 39,233 | 13,496 | 12,037 | 1,679 | 600 | 398 |
4704
- | \`table\` | 118,358 | 37,061 | 32,295 | 45,146 | 12,424 | 9,986 |
4705
- | \`tabs\` | 46,310 | 15,908 | 14,141 | 10,491 | 2,875 | 2,214 |
4706
- | \`tag\` | 42,676 | 15,621 | 13,970 | 8,453 | 2,544 | 1,941 |
4707
- | \`textarea\` | 43,852 | 14,940 | 13,325 | 5,401 | 1,602 | 1,190 |
4708
- | \`time-input\` | 169,269 | 52,676 | 45,775 | 48,509 | 12,418 | 9,883 |
4709
- | \`time-picker\` | 99,603 | 31,517 | 27,750 | 33,932 | 9,177 | 7,227 |
4710
- | \`timeline\` | 31,286 | 11,863 | 10,613 | 4,060 | 1,184 | 830 |
4711
- | \`toast\` | 50,871 | 17,940 | 15,980 | 12,122 | 3,516 | 2,664 |
4712
- | \`toggle\` | 45,577 | 15,269 | 13,579 | 6,007 | 1,805 | 1,324 |
4713
- | \`toolbar\` | 31,047 | 11,572 | 10,293 | 5,157 | 1,639 | 1,148 |
4714
- | \`tooltip\` | 58,616 | 21,090 | 18,716 | 8,432 | 2,348 | 1,819 |
4715
- | \`tour\` | 83,717 | 28,762 | 25,407 | 19,811 | 5,426 | 4,294 |
4716
- | \`tree\` | 67,586 | 22,717 | 20,230 | 17,197 | 4,927 | 3,902 |
4717
- | \`trend\` | 29,717 | 11,568 | 10,334 | 3,485 | 1,070 | 826 |
4718
- | \`video\` | 142,270 | 44,463 | 38,732 | 66,678 | 16,800 | 13,853 |
4719
- | \`video-playlist\` | 127,686 | 41,301 | 36,371 | 42,775 | 11,169 | 9,117 |
4720
- | \`virtual-list\` | 63,451 | 21,631 | 19,238 | 23,304 | 7,149 | 5,704 |
5259
+ | \`accordion\` | 32,671 | 12,095 | 10,738 | 5,377 | 1,558 | 1,128 |
5260
+ | \`announcement\` | 37,661 | 13,708 | 12,240 | 4,700 | 1,380 | 955 |
5261
+ | \`app-header\` | 33,168 | 12,242 | 10,881 | 5,900 | 1,760 | 1,194 |
5262
+ | \`area-chart\` | 99,355 | 34,966 | 30,874 | 14,417 | 4,436 | 3,486 |
5263
+ | \`avatar\` | 31,887 | 12,153 | 10,894 | 3,999 | 1,246 | 868 |
5264
+ | \`badge\` | 28,118 | 10,944 | 9,778 | 2,960 | 887 | 631 |
5265
+ | \`bar-chart\` | 107,369 | 37,644 | 33,296 | 21,901 | 6,878 | 5,603 |
5266
+ | \`blockquote\` | 24,679 | 9,638 | 8,541 | 2,400 | 883 | 572 |
5267
+ | \`breadcrumbs\` | 37,419 | 14,329 | 12,787 | 7,381 | 2,413 | 1,885 |
5268
+ | \`button\` | 36,221 | 12,969 | 11,566 | 5,127 | 1,522 | 1,143 |
5269
+ | \`button-group\` | 28,214 | 10,617 | 9,418 | 4,923 | 1,508 | 1,060 |
5270
+ | \`callout\` | 36,284 | 13,988 | 12,430 | 2,884 | 960 | 676 |
5271
+ | \`canvas\` | 109,962 | 35,313 | 30,873 | 68,989 | 21,024 | 17,528 |
5272
+ | \`card\` | 24,397 | 9,311 | 8,260 | 2,090 | 634 | 392 |
5273
+ | \`carousel\` | 45,040 | 16,012 | 14,307 | 15,317 | 4,235 | 3,233 |
5274
+ | \`chat\` | 80,951 | 26,986 | 23,875 | 27,678 | 8,076 | 6,418 |
5275
+ | \`checkbox\` | 41,242 | 14,016 | 12,511 | 3,107 | 959 | 577 |
5276
+ | \`checkbox-group\` | 48,465 | 15,944 | 14,236 | 8,321 | 2,373 | 1,581 |
5277
+ | \`code\` | 20,869 | 8,248 | 7,311 | 138 | 67 | 0 |
5278
+ | \`code-block\` | 104,774 | 35,631 | 31,445 | 37,349 | 10,789 | 8,804 |
5279
+ | \`color-input\` | 143,744 | 45,826 | 39,935 | 55,780 | 14,227 | 11,659 |
5280
+ | \`color-picker\` | 105,363 | 33,633 | 29,490 | 47,900 | 12,700 | 10,341 |
5281
+ | \`combobox\` | 133,766 | 42,655 | 37,472 | 35,968 | 9,135 | 7,097 |
5282
+ | \`command\` | 101,170 | 32,143 | 28,265 | 30,351 | 8,182 | 6,540 |
5283
+ | \`copy-button\` | 82,225 | 28,295 | 25,070 | 18,756 | 5,070 | 4,021 |
5284
+ | \`data-table\` | 201,919 | 62,149 | 53,104 | 89,974 | 24,051 | 19,023 |
5285
+ | \`date-input\` | 143,049 | 45,174 | 39,724 | 38,666 | 9,515 | 7,534 |
5286
+ | \`date-picker\` | 63,093 | 21,512 | 19,285 | 20,720 | 5,656 | 4,480 |
5287
+ | \`description-list\` | 23,472 | 8,993 | 7,982 | 1,565 | 498 | 352 |
5288
+ | \`dialog\` | 39,175 | 13,972 | 12,447 | 5,948 | 1,764 | 1,214 |
5289
+ | \`divider\` | 23,564 | 9,144 | 8,085 | 1,414 | 495 | 285 |
5290
+ | \`donut-chart\` | 92,673 | 32,268 | 28,545 | 14,717 | 4,498 | 3,591 |
5291
+ | \`drawer\` | 43,812 | 15,483 | 13,793 | 7,798 | 2,316 | 1,847 |
5292
+ | \`drilldown\` | 43,527 | 15,249 | 13,608 | 9,709 | 2,684 | 2,069 |
5293
+ | \`empty\` | 25,007 | 9,579 | 8,500 | 1,976 | 646 | 441 |
5294
+ | \`file-input\` | 59,118 | 20,550 | 18,273 | 13,152 | 4,118 | 3,253 |
5295
+ | \`filter-input\` | 99,578 | 33,493 | 29,715 | 21,569 | 6,178 | 4,854 |
5296
+ | \`format-date\` | 31,209 | 11,674 | 10,419 | 4,474 | 1,290 | 957 |
5297
+ | \`graph\` | 76,986 | 25,237 | 22,383 | 23,180 | 6,769 | 5,453 |
5298
+ | \`heatmap\` | 101,735 | 34,694 | 30,620 | 19,071 | 5,600 | 4,513 |
5299
+ | \`icon\` | 83,516 | 28,588 | 24,537 | 58,618 | 20,085 | 16,003 |
5300
+ | \`icon-button\` | 36,332 | 13,001 | 11,591 | 5,236 | 1,561 | 1,158 |
5301
+ | \`image\` | 26,373 | 9,844 | 8,746 | 3,845 | 1,148 | 780 |
5302
+ | \`input\` | 47,049 | 16,178 | 14,339 | 7,005 | 2,242 | 1,428 |
5303
+ | \`kbd\` | 28,084 | 10,789 | 9,553 | 3,518 | 1,104 | 678 |
5304
+ | \`legend\` | 36,396 | 13,369 | 11,949 | 2,955 | 984 | 789 |
5305
+ | \`lightbox\` | 57,138 | 19,382 | 17,204 | 16,760 | 4,614 | 3,580 |
5306
+ | \`line-chart\` | 102,350 | 35,823 | 31,682 | 16,812 | 5,062 | 4,135 |
5307
+ | \`list\` | 62,949 | 21,723 | 19,351 | 15,258 | 4,394 | 3,471 |
5308
+ | \`listbox\` | 78,070 | 25,948 | 23,003 | 15,605 | 4,579 | 3,417 |
5309
+ | \`loader\` | 27,341 | 10,451 | 9,311 | 2,785 | 940 | 698 |
5310
+ | \`log\` | 80,937 | 27,698 | 24,549 | 37,582 | 11,597 | 9,915 |
5311
+ | \`map\` | 81,046 | 25,212 | 22,083 | 37,496 | 10,232 | 8,037 |
5312
+ | \`megamenu\` | 61,165 | 22,169 | 19,603 | 8,723 | 2,442 | 1,726 |
5313
+ | \`menu\` | 98,510 | 32,960 | 29,097 | 30,262 | 7,971 | 6,298 |
5314
+ | \`meter\` | 32,921 | 12,484 | 11,133 | 4,980 | 1,464 | 1,221 |
5315
+ | \`motion-toggle\` | 26,353 | 10,250 | 9,108 | 1,426 | 514 | 292 |
5316
+ | \`navigation\` | 33,861 | 12,731 | 11,316 | 3,364 | 1,161 | 944 |
5317
+ | \`notifications\` | 59,539 | 20,332 | 18,161 | 18,280 | 5,038 | 3,951 |
5318
+ | \`number-input\` | 57,629 | 19,358 | 17,239 | 13,579 | 3,609 | 2,749 |
5319
+ | \`object-inspector\` | 115,897 | 39,008 | 34,349 | 36,018 | 10,397 | 8,366 |
5320
+ | \`option\` | 27,049 | 9,970 | 8,860 | 4,522 | 1,202 | 808 |
5321
+ | \`outline\` | 34,490 | 12,937 | 11,489 | 7,878 | 2,548 | 1,859 |
5322
+ | \`pagination\` | 138,030 | 44,305 | 38,503 | 45,681 | 11,904 | 9,402 |
5323
+ | \`pin-input\` | 48,848 | 17,045 | 15,229 | 7,207 | 2,364 | 1,670 |
5324
+ | \`popover\` | 64,882 | 23,350 | 20,626 | 6,460 | 1,852 | 1,282 |
5325
+ | \`progress\` | 29,235 | 11,153 | 9,999 | 3,006 | 943 | 664 |
5326
+ | \`qr-code\` | 43,669 | 17,003 | 15,118 | 17,002 | 6,288 | 5,378 |
5327
+ | \`quote\` | 23,088 | 9,112 | 8,040 | 1,209 | 499 | 418 |
5328
+ | \`radio\` | 43,333 | 14,777 | 13,184 | 3,815 | 1,205 | 783 |
5329
+ | \`rating\` | 51,593 | 17,779 | 15,843 | 8,057 | 2,440 | 1,743 |
5330
+ | \`scatter-chart\` | 98,654 | 34,303 | 30,275 | 20,338 | 6,374 | 5,212 |
5331
+ | \`scroll-area\` | 31,294 | 11,434 | 10,149 | 8,518 | 2,583 | 1,852 |
5332
+ | \`select\` | 116,719 | 38,717 | 33,919 | 26,155 | 7,165 | 5,479 |
5333
+ | \`skeleton\` | 22,900 | 8,871 | 7,876 | 991 | 377 | 202 |
5334
+ | \`skip-link\` | 24,547 | 9,608 | 8,524 | 1,223 | 456 | 415 |
5335
+ | \`slider\` | 54,642 | 18,411 | 16,503 | 13,623 | 3,851 | 2,916 |
5336
+ | \`sparkline\` | 49,258 | 17,982 | 16,092 | 5,982 | 2,086 | 1,529 |
5337
+ | \`spatial-bar-chart\` | 105,167 | 37,122 | 32,704 | 19,854 | 6,518 | 5,323 |
5338
+ | \`spatial-heatmap\` | 117,089 | 39,972 | 35,103 | 31,736 | 9,641 | 7,736 |
5339
+ | \`spatial-ribbon-chart\` | 104,288 | 36,728 | 32,375 | 20,791 | 6,921 | 5,511 |
5340
+ | \`spatial-scatter-chart\` | 102,521 | 35,980 | 31,686 | 19,085 | 6,281 | 5,041 |
5341
+ | \`spatial-surface-chart\` | 105,443 | 37,265 | 32,810 | 21,949 | 7,465 | 6,102 |
5342
+ | \`spatial-treemap\` | 106,561 | 37,910 | 33,386 | 21,370 | 7,346 | 5,920 |
5343
+ | \`speed-dial\` | 69,618 | 25,068 | 22,197 | 10,934 | 2,964 | 2,300 |
5344
+ | \`splitter\` | 45,071 | 15,639 | 13,982 | 14,506 | 3,884 | 3,031 |
5345
+ | \`stat\` | 26,285 | 10,056 | 8,958 | 2,322 | 765 | 438 |
5346
+ | \`steps\` | 54,926 | 19,123 | 17,012 | 13,804 | 3,887 | 2,991 |
5347
+ | \`switch\` | 40,096 | 13,832 | 12,333 | 1,679 | 618 | 301 |
5348
+ | \`table\` | 119,408 | 37,410 | 32,513 | 45,149 | 12,317 | 9,933 |
5349
+ | \`tabs\` | 47,080 | 16,201 | 14,404 | 10,491 | 2,847 | 2,042 |
5350
+ | \`tag\` | 43,623 | 15,994 | 14,323 | 8,450 | 2,507 | 1,737 |
5351
+ | \`textarea\` | 44,973 | 15,362 | 13,745 | 5,489 | 1,613 | 1,141 |
5352
+ | \`time-input\` | 170,882 | 53,314 | 46,344 | 48,574 | 12,340 | 9,807 |
5353
+ | \`time-picker\` | 101,126 | 32,099 | 28,234 | 33,987 | 9,197 | 7,242 |
5354
+ | \`timeline\` | 32,429 | 12,281 | 11,015 | 4,086 | 1,199 | 899 |
5355
+ | \`toast\` | 52,298 | 18,497 | 16,505 | 12,164 | 3,516 | 2,675 |
5356
+ | \`toggle\` | 46,246 | 15,541 | 13,887 | 6,005 | 1,800 | 1,280 |
5357
+ | \`toolbar\` | 31,817 | 11,859 | 10,566 | 5,156 | 1,626 | 1,161 |
5358
+ | \`tooltip\` | 59,698 | 21,530 | 19,131 | 8,504 | 2,352 | 1,819 |
5359
+ | \`tour\` | 85,264 | 29,314 | 25,880 | 20,091 | 5,517 | 4,279 |
5360
+ | \`tree\` | 68,641 | 23,150 | 20,615 | 17,198 | 4,915 | 3,908 |
5361
+ | \`trend\` | 30,937 | 12,024 | 10,725 | 3,590 | 1,102 | 830 |
5362
+ | \`video\` | 143,863 | 45,087 | 39,328 | 66,787 | 16,894 | 13,955 |
5363
+ | \`video-playlist\` | 128,994 | 41,833 | 36,784 | 42,860 | 11,206 | 9,154 |
5364
+ | \`virtual-list\` | 64,382 | 21,913 | 19,490 | 23,393 | 7,169 | 5,835 |
4721
5365
 
4722
5366
  ### Styled tier
4723
5367
 
@@ -4725,119 +5369,119 @@ The headless tier plus presentation. \`bun add @fusion.dev/helix\` installs this
4725
5369
 
4726
5370
  | Family | Alone raw | Alone gzip | Alone brotli | Added raw | Added gzip | Added brotli |
4727
5371
  |---|---|---|---|---|---|---|
4728
- | \`accordion\` | 35,311 | 12,653 | 11,234 | 8,743 | 2,443 | 1,945 |
4729
- | \`announcement\` | 41,307 | 14,364 | 12,795 | 9,277 | 2,477 | 1,910 |
4730
- | \`app-header\` | 37,449 | 13,102 | 11,678 | 11,015 | 2,965 | 2,404 |
4731
- | \`area-chart\` | 98,393 | 33,575 | 29,660 | 22,128 | 6,245 | 4,997 |
4732
- | \`aura\` | 58,599 | 19,870 | 17,603 | 31,639 | 9,617 | 8,046 |
4733
- | \`avatar\` | 33,464 | 12,351 | 11,079 | 6,945 | 1,964 | 1,455 |
4734
- | \`badge\` | 32,595 | 11,820 | 10,528 | 8,436 | 2,150 | 1,738 |
4735
- | \`bar-chart\` | 108,944 | 35,725 | 31,579 | 32,531 | 8,343 | 6,870 |
4736
- | \`blockquote\` | 26,332 | 9,968 | 8,810 | 4,611 | 1,416 | 1,209 |
4737
- | \`breadcrumbs\` | 43,220 | 15,115 | 13,521 | 14,282 | 3,647 | 2,889 |
4738
- | \`button\` | 41,612 | 14,008 | 12,455 | 11,321 | 2,870 | 2,237 |
4739
- | \`button-group\` | 31,138 | 11,066 | 9,812 | 8,407 | 2,205 | 1,651 |
4740
- | \`callout\` | 39,049 | 14,536 | 12,915 | 6,659 | 1,915 | 1,478 |
4741
- | \`canvas\` | 129,523 | 38,715 | 33,910 | 89,608 | 24,825 | 20,816 |
4742
- | \`card\` | 27,725 | 10,099 | 8,962 | 5,976 | 1,633 | 1,319 |
4743
- | \`carousel\` | 54,063 | 17,260 | 15,433 | 25,704 | 6,036 | 4,919 |
4744
- | \`chat\` | 94,925 | 29,209 | 25,823 | 42,981 | 10,843 | 8,730 |
4745
- | \`checkbox\` | 46,865 | 15,190 | 13,521 | 9,580 | 2,483 | 1,896 |
4746
- | \`checkbox-group\` | 54,826 | 17,244 | 15,384 | 15,537 | 3,997 | 3,023 |
4747
- | \`code\` | 21,154 | 8,332 | 7,400 | 976 | 381 | 287 |
4748
- | \`code-block\` | 127,213 | 38,714 | 34,179 | 61,298 | 14,674 | 12,043 |
4749
- | \`color-input\` | 184,176 | 51,349 | 44,895 | 97,504 | 20,448 | 17,029 |
4750
- | \`color-picker\` | 140,348 | 38,509 | 33,849 | 84,175 | 18,133 | 15,092 |
4751
- | \`combobox\` | 147,503 | 44,927 | 39,514 | 51,252 | 12,065 | 9,617 |
4752
- | \`command\` | 110,838 | 33,878 | 29,872 | 41,331 | 10,480 | 8,487 |
4753
- | \`copy-button\` | 92,577 | 29,825 | 26,424 | 30,351 | 7,146 | 5,767 |
4754
- | \`data-table\` | 230,715 | 66,817 | 57,223 | 120,287 | 29,325 | 23,912 |
4755
- | \`date-input\` | 162,349 | 48,215 | 42,508 | 59,338 | 13,170 | 10,667 |
4756
- | \`date-picker\` | 69,281 | 22,363 | 19,996 | 28,248 | 7,038 | 5,662 |
4757
- | \`description-list\` | 25,855 | 9,517 | 8,465 | 4,508 | 1,254 | 970 |
4758
- | \`dialog\` | 43,407 | 14,900 | 13,275 | 10,933 | 3,020 | 2,453 |
4759
- | \`divider\` | 25,291 | 9,546 | 8,433 | 3,698 | 1,124 | 881 |
4760
- | \`donut-chart\` | 99,170 | 33,262 | 29,410 | 24,574 | 6,693 | 5,431 |
4761
- | \`drawer\` | 48,772 | 16,542 | 14,790 | 13,511 | 3,736 | 2,963 |
4762
- | \`drilldown\` | 48,438 | 16,238 | 14,468 | 15,376 | 4,007 | 3,098 |
4763
- | \`empty\` | 25,550 | 9,761 | 8,642 | 3,242 | 1,092 | 766 |
4764
- | \`file-input\` | 69,285 | 22,419 | 19,894 | 24,610 | 6,500 | 5,231 |
4765
- | \`filter-input\` | 108,688 | 34,990 | 30,998 | 32,463 | 8,378 | 6,774 |
4766
- | \`flex\` | 25,740 | 9,194 | 8,146 | 4,762 | 1,032 | 929 |
4767
- | \`format-date\` | 30,414 | 11,403 | 10,154 | 4,669 | 1,383 | 1,002 |
4768
- | \`graph\` | 91,503 | 27,441 | 24,189 | 38,992 | 9,539 | 7,678 |
4769
- | \`grid\` | 27,002 | 9,845 | 8,732 | 5,637 | 1,435 | 1,229 |
4770
- | \`heatmap\` | 106,453 | 35,370 | 31,213 | 27,245 | 7,425 | 5,968 |
4771
- | \`icon\` | 83,995 | 28,566 | 24,451 | 60,128 | 20,162 | 16,415 |
4772
- | \`icon-button\` | 43,040 | 13,980 | 12,441 | 12,750 | 2,859 | 2,203 |
4773
- | \`image\` | 26,892 | 9,946 | 8,816 | 5,192 | 1,598 | 1,225 |
4774
- | \`input\` | 50,619 | 17,064 | 15,045 | 11,592 | 3,501 | 2,678 |
4775
- | \`kbd\` | 29,504 | 11,109 | 9,821 | 5,494 | 1,668 | 1,221 |
4776
- | \`legend\` | 40,423 | 14,213 | 12,591 | 8,066 | 2,255 | 1,775 |
4777
- | \`lightbox\` | 62,016 | 20,155 | 17,914 | 23,004 | 5,960 | 4,593 |
4778
- | \`line-chart\` | 102,454 | 34,256 | 30,369 | 25,861 | 6,815 | 5,532 |
4779
- | \`list\` | 68,150 | 22,813 | 20,367 | 21,568 | 5,926 | 4,682 |
4780
- | \`listbox\` | 81,154 | 26,541 | 23,418 | 19,991 | 5,668 | 4,240 |
4781
- | \`loader\` | 29,263 | 10,815 | 9,653 | 5,641 | 1,684 | 1,370 |
4782
- | \`log\` | 91,550 | 29,627 | 26,315 | 49,199 | 14,063 | 11,709 |
4783
- | \`map\` | 112,126 | 29,780 | 26,174 | 69,978 | 15,410 | 12,648 |
4784
- | \`megamenu\` | 65,058 | 22,889 | 20,256 | 13,607 | 3,558 | 2,679 |
4785
- | \`menu\` | 108,403 | 34,710 | 30,630 | 41,092 | 10,114 | 8,143 |
4786
- | \`meter\` | 37,896 | 13,376 | 11,953 | 11,049 | 2,795 | 2,305 |
4787
- | \`motion-toggle\` | 27,312 | 10,459 | 9,307 | 3,044 | 972 | 719 |
4788
- | \`navigation\` | 36,814 | 13,267 | 11,776 | 7,076 | 2,006 | 1,676 |
4789
- | \`notifications\` | 71,839 | 22,457 | 20,075 | 31,690 | 7,637 | 6,153 |
4790
- | \`number-input\` | 61,253 | 20,025 | 17,833 | 18,489 | 4,793 | 3,736 |
4791
- | \`object-inspector\` | 130,338 | 41,205 | 36,235 | 51,885 | 13,165 | 10,703 |
4792
- | \`option\` | 29,672 | 10,761 | 9,548 | 7,711 | 2,181 | 1,777 |
4793
- | \`orb\` | 36,209 | 13,750 | 12,270 | 10,015 | 3,713 | 3,102 |
4794
- | \`outline\` | 37,846 | 13,457 | 11,935 | 12,073 | 3,433 | 2,707 |
4795
- | \`pagination\` | 153,264 | 46,929 | 40,876 | 62,367 | 15,172 | 12,140 |
4796
- | \`pin-input\` | 50,409 | 17,316 | 15,441 | 10,057 | 3,146 | 2,472 |
4797
- | \`popover\` | 67,005 | 23,748 | 21,050 | 9,507 | 2,642 | 1,928 |
4798
- | \`progress\` | 35,232 | 12,380 | 11,056 | 10,100 | 2,587 | 2,222 |
4799
- | \`qr-code\` | 44,321 | 17,120 | 15,275 | 18,585 | 6,757 | 5,635 |
4800
- | \`quote\` | 23,074 | 9,079 | 8,007 | 1,751 | 685 | 482 |
4801
- | \`radio\` | 50,349 | 15,976 | 14,205 | 11,592 | 2,738 | 2,078 |
4802
- | \`rating\` | 54,190 | 18,518 | 16,515 | 11,944 | 3,635 | 2,813 |
4803
- | \`sandbox\` | 106,904 | 32,470 | 28,434 | 74,624 | 20,229 | 17,015 |
4804
- | \`scatter-chart\` | 100,317 | 33,784 | 29,867 | 25,193 | 6,964 | 5,590 |
4805
- | \`scroll-area\` | 35,437 | 12,025 | 10,665 | 13,489 | 3,564 | 2,801 |
4806
- | \`select\` | 124,418 | 40,070 | 35,144 | 35,145 | 9,048 | 7,095 |
4807
- | \`skeleton\` | 26,090 | 9,663 | 8,583 | 4,738 | 1,387 | 1,097 |
4808
- | \`skip-link\` | 25,441 | 9,851 | 8,691 | 2,779 | 953 | 679 |
4809
- | \`slider\` | 67,842 | 20,140 | 18,046 | 28,107 | 6,147 | 4,915 |
4810
- | \`sparkline\` | 53,442 | 18,850 | 16,823 | 11,419 | 3,468 | 2,707 |
4811
- | \`spatial-bar-chart\` | 110,683 | 37,998 | 33,460 | 28,827 | 8,573 | 6,953 |
4812
- | \`spatial-heatmap\` | 123,212 | 40,986 | 36,071 | 41,313 | 11,815 | 9,777 |
4813
- | \`spatial-ribbon-chart\` | 109,655 | 37,575 | 33,186 | 29,617 | 8,969 | 7,397 |
4814
- | \`spatial-scatter-chart\` | 107,952 | 36,809 | 32,463 | 27,965 | 8,237 | 6,655 |
4815
- | \`spatial-surface-chart\` | 110,331 | 38,025 | 33,535 | 30,294 | 9,367 | 7,788 |
4816
- | \`spatial-treemap\` | 112,031 | 38,807 | 34,114 | 30,297 | 9,418 | 7,675 |
4817
- | \`speed-dial\` | 75,797 | 26,105 | 23,141 | 17,945 | 4,385 | 3,422 |
4818
- | \`splitter\` | 49,804 | 16,207 | 14,499 | 20,430 | 4,897 | 3,960 |
4819
- | \`stat\` | 28,117 | 10,371 | 9,278 | 5,156 | 1,447 | 1,194 |
4820
- | \`steps\` | 67,449 | 21,377 | 19,020 | 27,416 | 6,615 | 5,346 |
4821
- | \`strands\` | 62,788 | 23,354 | 20,585 | 38,012 | 13,681 | 11,870 |
4822
- | \`switch\` | 43,562 | 14,636 | 13,040 | 5,998 | 1,756 | 1,330 |
4823
- | \`table\` | 135,900 | 40,155 | 34,911 | 62,691 | 15,518 | 12,666 |
4824
- | \`tabs\` | 55,263 | 17,752 | 15,831 | 19,435 | 4,713 | 3,775 |
4825
- | \`tag\` | 47,247 | 16,757 | 14,968 | 13,012 | 3,667 | 2,791 |
4826
- | \`text\` | 28,756 | 9,942 | 8,773 | 7,394 | 1,497 | 1,221 |
4827
- | \`textarea\` | 47,785 | 15,949 | 14,223 | 9,317 | 2,646 | 1,979 |
4828
- | \`time-input\` | 193,597 | 56,836 | 49,517 | 72,822 | 16,542 | 13,531 |
4829
- | \`time-picker\` | 111,928 | 33,946 | 29,876 | 46,251 | 11,610 | 9,278 |
4830
- | \`timeline\` | 38,179 | 13,325 | 11,919 | 10,945 | 2,702 | 2,232 |
4831
- | \`toast\` | 58,767 | 19,783 | 17,645 | 20,004 | 5,404 | 4,311 |
4832
- | \`toggle\` | 50,439 | 16,509 | 14,676 | 10,854 | 3,074 | 2,458 |
4833
- | \`toolbar\` | 33,726 | 12,065 | 10,720 | 7,821 | 2,144 | 1,747 |
4834
- | \`tooltip\` | 61,420 | 21,834 | 19,360 | 11,218 | 3,096 | 2,475 |
4835
- | \`tour\` | 93,708 | 30,740 | 27,174 | 29,780 | 7,465 | 6,014 |
4836
- | \`tree\` | 76,275 | 24,651 | 21,967 | 25,873 | 6,877 | 5,599 |
4837
- | \`trend\` | 31,231 | 12,049 | 10,723 | 4,980 | 1,532 | 1,282 |
4838
- | \`video\` | 162,777 | 48,192 | 42,003 | 87,167 | 20,547 | 17,118 |
4839
- | \`video-playlist\` | 143,639 | 44,279 | 38,929 | 58,705 | 14,169 | 11,616 |
4840
- | \`virtual-list\` | 65,445 | 22,138 | 19,717 | 25,281 | 7,639 | 6,158 |
5372
+ | \`accordion\` | 36,104 | 12,942 | 11,494 | 8,794 | 2,432 | 1,881 |
5373
+ | \`announcement\` | 42,276 | 14,775 | 13,183 | 9,296 | 2,461 | 1,809 |
5374
+ | \`app-header\` | 38,326 | 13,450 | 11,973 | 11,045 | 2,971 | 2,437 |
5375
+ | \`area-chart\` | 107,559 | 36,865 | 32,484 | 22,602 | 6,333 | 5,179 |
5376
+ | \`aura\` | 59,567 | 20,254 | 17,948 | 31,767 | 9,666 | 8,168 |
5377
+ | \`avatar\` | 34,890 | 12,911 | 11,564 | 6,987 | 1,978 | 1,543 |
5378
+ | \`badge\` | 33,626 | 12,189 | 10,865 | 8,458 | 2,158 | 1,698 |
5379
+ | \`bar-chart\` | 122,404 | 40,671 | 35,868 | 36,918 | 9,892 | 8,185 |
5380
+ | \`blockquote\` | 26,905 | 10,186 | 9,001 | 4,610 | 1,426 | 1,122 |
5381
+ | \`breadcrumbs\` | 44,356 | 15,535 | 13,890 | 14,306 | 3,646 | 3,034 |
5382
+ | \`button\` | 42,638 | 14,385 | 12,833 | 11,526 | 2,919 | 2,239 |
5383
+ | \`button-group\` | 31,709 | 11,294 | 10,030 | 8,408 | 2,202 | 1,639 |
5384
+ | \`callout\` | 40,077 | 14,932 | 13,284 | 6,659 | 1,926 | 1,470 |
5385
+ | \`canvas\` | 131,001 | 39,287 | 34,421 | 89,671 | 24,923 | 20,893 |
5386
+ | \`card\` | 28,297 | 10,317 | 9,175 | 5,976 | 1,633 | 1,204 |
5387
+ | \`carousel\` | 55,526 | 17,813 | 15,971 | 25,788 | 6,085 | 4,877 |
5388
+ | \`chat\` | 96,367 | 29,721 | 26,365 | 43,087 | 10,858 | 8,827 |
5389
+ | \`checkbox\` | 47,728 | 15,518 | 13,864 | 9,579 | 2,487 | 1,895 |
5390
+ | \`checkbox-group\` | 55,688 | 17,587 | 15,650 | 15,535 | 4,008 | 3,041 |
5391
+ | \`code\` | 21,726 | 8,561 | 7,606 | 976 | 377 | 324 |
5392
+ | \`code-block\` | 129,247 | 39,457 | 34,774 | 61,803 | 14,528 | 12,176 |
5393
+ | \`color-input\` | 186,124 | 52,091 | 45,487 | 98,147 | 20,720 | 17,300 |
5394
+ | \`color-picker\` | 142,298 | 39,184 | 34,443 | 84,819 | 18,371 | 15,249 |
5395
+ | \`combobox\` | 149,243 | 45,632 | 40,119 | 51,428 | 12,037 | 9,803 |
5396
+ | \`command\` | 112,469 | 34,490 | 30,347 | 41,644 | 10,582 | 8,468 |
5397
+ | \`copy-button\` | 94,256 | 30,466 | 26,952 | 30,769 | 7,291 | 5,796 |
5398
+ | \`data-table\` | 232,707 | 67,583 | 57,854 | 120,745 | 29,581 | 23,946 |
5399
+ | \`date-input\` | 164,353 | 48,935 | 43,033 | 59,950 | 13,125 | 10,864 |
5400
+ | \`date-picker\` | 71,139 | 22,979 | 20,564 | 28,748 | 7,139 | 5,758 |
5401
+ | \`description-list\` | 26,427 | 9,731 | 8,675 | 4,508 | 1,275 | 963 |
5402
+ | \`dialog\` | 44,311 | 15,226 | 13,609 | 11,064 | 3,040 | 2,278 |
5403
+ | \`divider\` | 25,863 | 9,772 | 8,637 | 3,697 | 1,132 | 903 |
5404
+ | \`donut-chart\` | 102,652 | 34,418 | 30,433 | 24,673 | 6,687 | 5,459 |
5405
+ | \`drawer\` | 49,642 | 16,871 | 15,075 | 13,612 | 3,747 | 2,968 |
5406
+ | \`drilldown\` | 49,204 | 16,541 | 14,757 | 15,375 | 4,004 | 3,147 |
5407
+ | \`empty\` | 26,316 | 10,056 | 8,903 | 3,267 | 1,090 | 757 |
5408
+ | \`file-input\` | 70,648 | 22,941 | 20,387 | 24,668 | 6,540 | 5,372 |
5409
+ | \`filter-input\` | 110,412 | 35,679 | 31,616 | 32,396 | 8,372 | 6,794 |
5410
+ | \`flex\` | 26,312 | 9,424 | 8,374 | 4,761 | 1,041 | 808 |
5411
+ | \`format-date\` | 31,463 | 11,790 | 10,598 | 4,712 | 1,389 | 933 |
5412
+ | \`graph\` | 92,941 | 27,948 | 24,599 | 39,115 | 9,599 | 7,752 |
5413
+ | \`grid\` | 27,762 | 10,137 | 9,023 | 5,655 | 1,457 | 1,237 |
5414
+ | \`heatmap\` | 110,057 | 36,548 | 32,268 | 27,373 | 7,430 | 5,950 |
5415
+ | \`icon\` | 85,551 | 29,193 | 25,100 | 60,473 | 20,643 | 16,563 |
5416
+ | \`icon-button\` | 44,066 | 14,350 | 12,781 | 12,955 | 2,917 | 2,229 |
5417
+ | \`image\` | 27,760 | 10,306 | 9,130 | 5,216 | 1,623 | 1,181 |
5418
+ | \`input\` | 51,304 | 17,264 | 15,289 | 11,243 | 3,350 | 2,439 |
5419
+ | \`kbd\` | 30,078 | 11,329 | 10,047 | 5,494 | 1,656 | 1,138 |
5420
+ | \`legend\` | 41,634 | 14,661 | 13,042 | 8,065 | 2,233 | 1,679 |
5421
+ | \`lightbox\` | 63,432 | 20,712 | 18,441 | 23,035 | 5,934 | 4,625 |
5422
+ | \`line-chart\` | 113,713 | 38,277 | 33,817 | 28,159 | 7,540 | 6,259 |
5423
+ | \`list\` | 69,295 | 23,266 | 20,686 | 21,585 | 5,916 | 4,672 |
5424
+ | \`listbox\` | 82,511 | 27,062 | 23,951 | 20,034 | 5,683 | 4,451 |
5425
+ | \`loader\` | 30,222 | 11,214 | 10,003 | 5,652 | 1,707 | 1,271 |
5426
+ | \`log\` | 92,605 | 29,988 | 26,614 | 49,238 | 13,837 | 11,945 |
5427
+ | \`map\` | 114,692 | 30,616 | 26,893 | 71,126 | 15,703 | 12,867 |
5428
+ | \`megamenu\` | 66,134 | 23,308 | 20,635 | 13,673 | 3,614 | 2,670 |
5429
+ | \`menu\` | 109,435 | 35,105 | 30,924 | 41,177 | 10,171 | 8,287 |
5430
+ | \`meter\` | 39,093 | 13,830 | 12,347 | 11,136 | 2,821 | 2,233 |
5431
+ | \`motion-toggle\` | 27,988 | 10,726 | 9,544 | 3,044 | 996 | 781 |
5432
+ | \`navigation\` | 37,585 | 13,566 | 12,045 | 7,077 | 2,005 | 1,565 |
5433
+ | \`notifications\` | 73,027 | 22,893 | 20,429 | 31,757 | 7,639 | 6,257 |
5434
+ | \`number-input\` | 62,610 | 20,544 | 18,304 | 18,544 | 4,834 | 3,662 |
5435
+ | \`object-inspector\` | 132,306 | 41,847 | 36,792 | 52,407 | 13,126 | 10,803 |
5436
+ | \`option\` | 30,245 | 10,972 | 9,756 | 7,708 | 2,180 | 1,665 |
5437
+ | \`orb\` | 36,885 | 14,019 | 12,487 | 10,014 | 3,695 | 3,111 |
5438
+ | \`outline\` | 38,722 | 13,793 | 12,265 | 12,095 | 3,423 | 2,566 |
5439
+ | \`pagination\` | 155,030 | 47,597 | 41,353 | 62,664 | 15,116 | 12,303 |
5440
+ | \`pin-input\` | 52,228 | 17,955 | 16,035 | 10,570 | 3,313 | 2,468 |
5441
+ | \`popover\` | 68,034 | 24,118 | 21,334 | 9,594 | 2,638 | 2,003 |
5442
+ | \`progress\` | 36,415 | 12,815 | 11,459 | 10,169 | 2,605 | 2,046 |
5443
+ | \`qr-code\` | 45,270 | 17,490 | 15,562 | 18,586 | 6,787 | 5,732 |
5444
+ | \`quote\` | 23,646 | 9,303 | 8,218 | 1,750 | 694 | 493 |
5445
+ | \`radio\` | 51,120 | 16,282 | 14,513 | 11,591 | 2,749 | 2,108 |
5446
+ | \`rating\` | 55,527 | 19,005 | 16,936 | 11,973 | 3,667 | 2,759 |
5447
+ | \`sandbox\` | 108,218 | 32,974 | 28,846 | 74,725 | 20,351 | 16,974 |
5448
+ | \`scatter-chart\` | 108,028 | 36,473 | 32,173 | 29,693 | 8,586 | 6,981 |
5449
+ | \`scroll-area\` | 36,301 | 12,384 | 11,025 | 13,508 | 3,564 | 2,809 |
5450
+ | \`select\` | 125,846 | 40,631 | 35,638 | 35,267 | 9,129 | 7,178 |
5451
+ | \`skeleton\` | 26,848 | 9,947 | 8,859 | 4,755 | 1,398 | 1,157 |
5452
+ | \`skip-link\` | 26,118 | 10,115 | 8,948 | 2,778 | 966 | 716 |
5453
+ | \`slider\` | 69,605 | 20,728 | 18,554 | 28,572 | 6,253 | 5,031 |
5454
+ | \`sparkline\` | 54,716 | 19,331 | 17,289 | 11,422 | 3,445 | 2,786 |
5455
+ | \`spatial-bar-chart\` | 114,232 | 39,173 | 34,495 | 28,899 | 8,556 | 7,018 |
5456
+ | \`spatial-heatmap\` | 126,828 | 42,153 | 36,989 | 41,457 | 11,702 | 9,629 |
5457
+ | \`spatial-ribbon-chart\` | 113,204 | 38,749 | 34,144 | 29,690 | 8,956 | 7,330 |
5458
+ | \`spatial-scatter-chart\` | 111,501 | 37,966 | 33,426 | 28,039 | 8,231 | 6,745 |
5459
+ | \`spatial-surface-chart\` | 113,878 | 39,172 | 34,578 | 30,366 | 9,350 | 7,686 |
5460
+ | \`spatial-treemap\` | 115,578 | 39,956 | 35,186 | 30,367 | 9,373 | 7,646 |
5461
+ | \`speed-dial\` | 76,667 | 26,451 | 23,463 | 17,966 | 4,376 | 3,430 |
5462
+ | \`splitter\` | 51,121 | 16,689 | 14,958 | 20,542 | 4,916 | 3,912 |
5463
+ | \`stat\` | 29,171 | 10,751 | 9,606 | 5,193 | 1,467 | 1,129 |
5464
+ | \`steps\` | 68,915 | 21,887 | 19,475 | 27,781 | 6,739 | 5,460 |
5465
+ | \`strands\` | 63,680 | 23,697 | 20,891 | 38,177 | 13,705 | 11,865 |
5466
+ | \`switch\` | 44,449 | 14,978 | 13,325 | 6,021 | 1,773 | 1,338 |
5467
+ | \`table\` | 136,948 | 40,477 | 35,226 | 62,692 | 15,338 | 12,793 |
5468
+ | \`tabs\` | 56,031 | 18,047 | 16,061 | 19,434 | 4,733 | 3,695 |
5469
+ | \`tag\` | 48,197 | 17,126 | 15,273 | 13,011 | 3,621 | 2,775 |
5470
+ | \`text\` | 29,286 | 10,213 | 9,031 | 7,182 | 1,494 | 1,049 |
5471
+ | \`textarea\` | 48,905 | 16,377 | 14,615 | 9,403 | 2,654 | 2,091 |
5472
+ | \`time-input\` | 195,260 | 57,479 | 50,006 | 72,937 | 16,508 | 13,665 |
5473
+ | \`time-picker\` | 113,448 | 34,519 | 30,336 | 46,303 | 11,562 | 9,316 |
5474
+ | \`timeline\` | 39,321 | 13,742 | 12,314 | 10,973 | 2,701 | 2,082 |
5475
+ | \`toast\` | 60,192 | 20,339 | 18,143 | 20,046 | 5,402 | 4,332 |
5476
+ | \`toggle\` | 51,106 | 16,791 | 14,924 | 10,851 | 3,065 | 2,391 |
5477
+ | \`toolbar\` | 34,496 | 12,352 | 10,989 | 7,820 | 2,142 | 1,512 |
5478
+ | \`tooltip\` | 62,502 | 22,262 | 19,753 | 11,290 | 3,110 | 2,430 |
5479
+ | \`tour\` | 95,265 | 31,293 | 27,699 | 30,070 | 7,561 | 6,139 |
5480
+ | \`tree\` | 77,329 | 25,058 | 22,319 | 25,871 | 6,863 | 5,681 |
5481
+ | \`trend\` | 32,854 | 12,576 | 11,249 | 5,487 | 1,641 | 1,210 |
5482
+ | \`video\` | 164,366 | 48,811 | 42,541 | 87,272 | 20,643 | 17,220 |
5483
+ | \`video-playlist\` | 144,942 | 44,812 | 39,360 | 58,789 | 14,041 | 11,654 |
5484
+ | \`virtual-list\` | 66,376 | 22,417 | 19,953 | 25,370 | 7,675 | 6,274 |
4841
5485
 
4842
5486
  ## What an install adds to \`node_modules\`
4843
5487
 
@@ -4845,16 +5489,17 @@ These sizes are what an install adds to your disk, which is what an install
4845
5489
  budget limits. Each row is the unpacked size of exactly what a package
4846
5490
  publishes, as listed in its \`files\` field. The sizes do not include source
4847
5491
  maps. We strip them from the tarball when we pack it, and the Source maps
4848
- stripped column shows the bytes we removed.
5492
+ stripped column shows the bytes we removed. We also pack each JSON file
5493
+ without its whitespace, and the sizes count each one that way.
4849
5494
 
4850
5495
  | Package | Unpacked | Files | Source maps stripped | Runtime dependencies |
4851
5496
  |---|---|---|---|---|
4852
- | \`@fusion.dev/helix-core\` | 2,613,946 | 571 | 754,871 | \`@floating-ui/dom\`, \`@fusion.dev/helix-icons\`, \`tslib\` |
4853
- | \`@fusion.dev/helix-headless\` | 27,117,081 | 768 | 2,496,473 | \`@fusion.dev/helix-core\`, \`@fusion.dev/helix-icons\`, \`tslib\`, \`uqr\` |
4854
- | \`@fusion.dev/helix-icons\` | 150,509 | 21 | 48,154 | \`svgo\` |
4855
- | \`@fusion.dev/helix-styles\` | 121,672 | 6 | 0 | none |
4856
- | \`@fusion.dev/helix-theme\` | 529,299 | 8 | 5,393 | none |
4857
- | \`@fusion.dev/helix\` | 28,632,821 | 1,021 | 565,595 | \`@fusion.dev/helix-core\`, \`@fusion.dev/helix-headless\`, \`@fusion.dev/helix-styles\`, \`tslib\` |
5497
+ | \`@fusion.dev/helix-core\` | 2,191,244 | 587 | 791,148 | \`@floating-ui/dom\`, \`@fusion.dev/helix-icons\`, \`tslib\` |
5498
+ | \`@fusion.dev/helix-headless\` | 23,979,650 | 772 | 2,534,725 | \`@fusion.dev/helix-core\`, \`@fusion.dev/helix-icons\`, \`tslib\`, \`uqr\` |
5499
+ | \`@fusion.dev/helix-icons\` | 152,695 | 21 | 49,995 | \`svgo\` |
5500
+ | \`@fusion.dev/helix-styles\` | 121,825 | 6 | 0 | none |
5501
+ | \`@fusion.dev/helix-theme\` | 449,779 | 8 | 6,255 | none |
5502
+ | \`@fusion.dev/helix\` | 24,617,831 | 1,023 | 566,915 | \`@fusion.dev/helix-core\`, \`@fusion.dev/helix-headless\`, \`@fusion.dev/helix-styles\`, \`tslib\` |
4858
5503
 
4859
5504
  ## Not measured yet
4860
5505
 
@@ -5017,9 +5662,9 @@ trailing: true
5017
5662
 
5018
5663
  # Other frameworks
5019
5664
 
5020
- This page covers Solid, Qwik, Lit, Ember, Alpine, htmx, and any other framework
5021
- that puts elements in a document. They all follow the same three rules and need
5022
- nothing else.
5665
+ This page covers Preact, Solid, Qwik, Lit, Ember, Alpine, htmx, and any other
5666
+ framework that puts elements in a document. They all follow the same three
5667
+ rules and need nothing else.
5023
5668
 
5024
5669
  ## The three rules
5025
5670
 
@@ -5053,11 +5698,12 @@ in full.
5053
5698
 
5054
5699
  | Framework | Notes |
5055
5700
  |---|---|
5056
- | Solid | Sets properties on custom elements by default. Use \`prop:\` to force one and \`attr:\` to force the other. \`on:helix-before-change\` binds the literal name |
5057
- | Qwik | Works as JSX. Bind with \`document:on…\` or a ref, since a plain \`on*\` prop is normalized |
5058
- | Lit | \`.prop=\`, \`?bool=\`, and \`@helix-before-change=\` work with no setup |
5059
- | Ember | Angle-bracket invocation passes attributes only, so set properties from a modifier |
5060
5701
  | Alpine, htmx | Markup and attributes only, which work with every component. Refusing a change needs script |
5702
+ | Ember | Angle-bracket invocation passes attributes only, so set properties from a modifier |
5703
+ | Lit | \`.prop=\`, \`?bool=\`, and \`@helix-before-change=\` work with no setup |
5704
+ | Preact | Sets a property when the element has one, and an attribute otherwise. \`onhelix-before-change\` binds the literal name. [Typings](#typings) says how to type the tags |
5705
+ | Qwik | Works as JSX. Bind with \`document:on…\` or a ref, since a plain \`on*\` prop is normalized |
5706
+ | Solid | Sets properties on custom elements by default. Use \`prop:\` to force one and \`attr:\` to force the other. \`on:helix-before-change\` binds the literal name. [Typings](#typings) says how to type the tags |
5061
5707
 
5062
5708
  We do not test these frameworks, so they share one page. If your framework needs
5063
5709
  something the three rules do not cover, tell us. It is a gap in this page or in
@@ -5065,11 +5711,50 @@ the library.
5065
5711
 
5066
5712
  ## Typings
5067
5713
 
5068
- \`@fusion.dev/helix/jsx\` is the base for typing any framework that uses JSX, and
5069
- the Svelte typings are built from it. If your framework needs a different
5070
- format, use the manifest at \`@fusion.dev/helix/custom-elements.json\`. It
5071
- describes every tag, prop, event, and part, and we generate all our typings from
5072
- it.
5714
+ \`@fusion.dev/helix/jsx\` types the tags for React, Preact, and Solid, and it is
5715
+ the base for typing any other framework that uses JSX. The Svelte typings read
5716
+ their props from it. It needs only the types of the framework you use, and it
5717
+ compiles with \`skipLibCheck: false\`.
5718
+
5719
+ \`ref\` and \`style\` take the types of the framework the tag is written in:
5720
+
5721
+ - React: React's \`Ref\` and \`CSSProperties\`.
5722
+ - Preact: Preact's \`Ref\`, and the \`style\` a \`<div>\` takes, which is a string or
5723
+ an object.
5724
+ - Solid: the element or a callback that receives it, and the \`style\` a \`<div>\`
5725
+ takes, which is a string or an object.
5726
+ - Any other framework: the element or a callback that receives it, and an
5727
+ object of style properties.
5728
+
5729
+ In Preact and Solid, import the typings once, anywhere TypeScript sees it:
5730
+
5731
+ \`\`\`ts
5732
+ import '@fusion.dev/helix/jsx'
5733
+ \`\`\`
5734
+
5735
+ Preact reads the tags with \`jsxImportSource: "preact"\` and with \`h\` as the JSX
5736
+ factory. If your \`paths\` alias \`react\` to \`preact/compat\`, the tags take \`ref\`
5737
+ and \`style\` as \`preact/compat\` types React's, so \`style\` takes an object.
5738
+
5739
+ Solid reads the tags with \`jsxImportSource: "solid-js"\`, and each tag also
5740
+ takes \`prop:\`, \`attr:\`, and \`on:\` forms of its props and events.
5741
+
5742
+ With \`jsxImportSource: "solid-js/h"\`, Solid runs without its compiler, and the
5743
+ same import types the tags. Each prop also takes a function that returns its
5744
+ value, and \`h\` calls that function again when a signal it reads changes. A
5745
+ property whose value is a function, such as a slider's \`formatter\`, takes only a
5746
+ function that returns it, because \`h\` calls any function it is given:
5747
+
5748
+ \`\`\`tsx
5749
+ <helix-slider disabled={() => busy()} formatter={() => format} label="Volume" />
5750
+ \`\`\`
5751
+
5752
+ \`ref\`, \`children\`, and the handlers named \`on…\`, such as
5753
+ \`on:helix-before-change\`, take their values as they are.
5754
+
5755
+ If your framework needs a different format, use the manifest at
5756
+ \`@fusion.dev/helix/custom-elements.json\`. It describes every tag, prop, event,
5757
+ and part, and we generate all our typings from it.
5073
5758
 
5074
5759
  ## Register the elements
5075
5760
 
@@ -5349,7 +6034,7 @@ helix-select::part(trigger) {
5349
6034
  font-variant-numeric: tabular-nums;
5350
6035
  }
5351
6036
 
5352
- helix-dialog::part(dialog) {
6037
+ helix-dialog::part(surface) {
5353
6038
  max-inline-size: 40rem;
5354
6039
  }
5355
6040
  \`\`\`
@@ -5379,16 +6064,17 @@ helix-video::part(overflow-header) { … }
5379
6064
  \`\`\`
5380
6065
 
5381
6066
  \`<helix-date-input>\` renders a date picker in the \`picker\` role, so the picker's
5382
- \`cell\` part is \`picker-cell\`. When the child's part is the role itself, the
5383
- forwarded name is the role alone. The tooltip inside \`<helix-copy-button>\` is
5384
- \`tooltip\`, and its arrow is \`tooltip-arrow\`.
6067
+ \`cell\` part is \`picker-cell\`. The tooltip inside \`<helix-copy-button>\` is in the
6068
+ \`tooltip\` role, so its \`surface\` is \`tooltip-surface\` and its arrow is
6069
+ \`tooltip-arrow\`. When the child's part is the role itself, the forwarded name is
6070
+ the role alone: each line inside \`<helix-log>\` is \`line\`.
5385
6071
 
5386
6072
  A part that changes with a state has a second name while the state is active,
5387
6073
  and that name is forwarded too. A tooltip placed below its button is also
5388
- \`tooltip-bottom\`, and its arrow is also \`tooltip-arrow-bottom\`. Your stylesheet
5389
- can use these names to style each side, which it cannot detect any other way.
5390
- The arrow already faces the button on every side. Use the names for styles you
5391
- add on top.
6074
+ \`tooltip-surface-bottom\`, and its arrow is also \`tooltip-arrow-bottom\`. Your
6075
+ stylesheet can use these names to style each side, which it cannot detect any
6076
+ other way. The arrow already faces the button on every side. Use the names for
6077
+ styles you add on top.
5392
6078
 
5393
6079
  **The prefix keeps part names unique.** A component and its child can use the
5394
6080
  same word. This happened once: \`Command\` forwarded the listbox's \`search-field\`
@@ -5418,8 +6104,8 @@ so you learn each name once:
5418
6104
  | \`container\` | An element's own outer box. |
5419
6105
  | \`content\` | A region you fill. |
5420
6106
  | \`panel\` | A region switched between, or a sheet anchored to an edge. |
5421
- | \`surface\` | A floating panel drawn over the page. |
5422
- | \`viewport\` | A scrollport the component reads the position of. |
6107
+ | \`surface\` | A floating panel drawn over the page, such as a menu, a popover, a tooltip, or a dialog. |
6108
+ | \`viewport\` | The box that shows part of a larger area: a scrollport, or the frame a map or a graph is panned and zoomed in. |
5423
6109
 
5424
6110
  A part that names something specific to one component, such as \`thumb\`,
5425
6111
  \`swatch\`, or \`weekday\`, is listed on that component's page.
@@ -5591,6 +6277,8 @@ starts with that value and keeps applying its own changes, so the markup below
5591
6277
  works without a handler.
5592
6278
 
5593
6279
  \`\`\`tsx
6280
+ import { flushSync } from 'react-dom'
6281
+
5594
6282
  // Seeded: the element owns it from there.
5595
6283
  <helix-input default-value="Ada" label="Name" />
5596
6284
 
@@ -5602,7 +6290,7 @@ works without a handler.
5602
6290
  label="Name"
5603
6291
  onhelix-before-change={(event) => {
5604
6292
  event.preventDefault()
5605
- setName(clean(event.detail.value))
6293
+ flushSync(() => setName(clean(event.detail.value)))
5606
6294
  }}
5607
6295
  value={name}
5608
6296
  />
@@ -5613,6 +6301,18 @@ refuse it. React restores a native \`<input>\` whose state did not change, and i
5613
6301
  does not do this for a custom element. A handler that does not call \`setState\`
5614
6302
  leaves the element with the new value.
5615
6303
 
6304
+ **Set the state inside \`flushSync\` when you write your own value.** React sets
6305
+ the priority of a state update from the type of the event being handled.
6306
+ \`helix-before-change\` is not one of the types React lists, so the update gets
6307
+ the default priority and renders in a later task. A key that arrives before that
6308
+ task lands in the text from before the update, and the keys typed in between are
6309
+ lost. A barcode scanner, a password manager, and a test send keys that fast:
6310
+ Playwright typing \`Ada L!\` with no delay into a field that keeps lowercase
6311
+ letters left it empty. \`flushSync\` renders the update before your handler
6312
+ returns, so the element holds your value when the next key arrives. The same
6313
+ applies to every field you type into, such as \`helix-textarea\`,
6314
+ \`helix-number-input\`, and \`helix-pin-input\`.
6315
+
5616
6316
  *Until August 26, 2026, this page described the opposite behavior, and the typings
5617
6317
  enforced it. Writing \`value\` made you the owner of the state. The element
5618
6318
  applied no change until you set the value, and \`<helix-input value="Ada" />\`
@@ -5789,7 +6489,7 @@ Three Markdown constructs need components, because HTML alone cannot hold what
5789
6489
  they need:
5790
6490
 
5791
6491
  - **Fenced code** becomes \`helix-code-block\`, with the fence's language as its
5792
- \`label\`. A \`<pre>\` has no label or copy button.
6492
+ \`label\` and its \`language\`. A \`<pre>\` has no label or copy button.
5793
6493
  - **Alerts** such as \`> [!WARNING]\` become \`helix-callout\`. A \`<blockquote>\` has no
5794
6494
  tone. \`NOTE\` becomes \`tone="info"\`, \`TIP\` becomes \`success\`, \`WARNING\` becomes
5795
6495
  \`warning\`, and \`CAUTION\` becomes \`danger\`. \`IMPORTANT\` has no tone. Each
@@ -5804,13 +6504,29 @@ The \`rehype\` plugin from \`@fusion.dev/helix/rehype\` makes all three changes
5804
6504
  tree your Markdown library has already built. It parses nothing, so your
5805
6505
  library, its dialect, and your sanitizing stay yours. Add it to any pipeline
5806
6506
  that reaches rehype, such as \`remark\` with \`remark-rehype\`, \`react-markdown\`, or
5807
- MDX:
6507
+ MDX.
6508
+
6509
+ Tables are GitHub Flavored Markdown, so your pipeline needs \`remark-gfm\` to
6510
+ parse them. Without it, a table stays a paragraph of pipes. For a \`remark\`
6511
+ pipeline, install:
6512
+
6513
+ \`\`\`sh
6514
+ bun add unified remark-parse remark-gfm remark-rehype rehype-stringify
6515
+ \`\`\`
6516
+
6517
+ Then add the plugin after \`remark-rehype\`:
5808
6518
 
5809
6519
  \`\`\`js
5810
6520
  import rehypeHelix from '@fusion.dev/helix/rehype'
6521
+ import rehypeStringify from 'rehype-stringify'
6522
+ import remarkGfm from 'remark-gfm'
6523
+ import remarkParse from 'remark-parse'
6524
+ import remarkRehype from 'remark-rehype'
6525
+ import { unified } from 'unified'
5811
6526
 
5812
6527
  const html = await unified()
5813
6528
  .use(remarkParse)
6529
+ .use(remarkGfm)
5814
6530
  .use(remarkRehype)
5815
6531
  .use(rehypeHelix)
5816
6532
  .use(rehypeStringify)
@@ -6095,8 +6811,10 @@ You can edit every example on a component's page. When you change the markup,
6095
6811
  the preview redraws. When you change the stylesheet beside it, the preview
6096
6812
  restyles.
6097
6813
 
6098
- To build a playground of your own, you would use the same elements. The
6099
- isolation described below makes it safe to run markup that someone else wrote.
6814
+ To build a playground of your own, use the same elements: the sandbox from
6815
+ \`@fusion.dev/helix/sandbox\`, and the editor from \`@fusion.dev/helix-sandbox\`.
6816
+ The isolation described below makes it safe to run markup that someone else
6817
+ wrote.
6100
6818
 
6101
6819
  ## How the preview is isolated
6102
6820
 
@@ -6243,7 +6961,7 @@ preview, and \`helix-caret\` from it fills the status bar's position. A wrapper
6243
6961
  around CodeMirror:
6244
6962
 
6245
6963
  \`\`\`ts
6246
- import { HelixBeforeEditEvent } from '@fusion.dev/helix'
6964
+ import { HelixBeforeEditEvent } from '@fusion.dev/helix-core'
6247
6965
  import { EditorView, basicSetup } from 'codemirror'
6248
6966
 
6249
6967
  class CodeEditor extends HTMLElement {
@@ -6300,16 +7018,18 @@ own message from \`:state(failed)\`.
6300
7018
 
6301
7019
  ## The frame document
6302
7020
 
6303
- \`helix-sandbox-preview\` loads \`src\` into a sandboxed frame. You provide that
6304
- document. It loads the styles and the elements your examples use, and it talks
6305
- to the preview through \`postMessage\`.
7021
+ \`helix-sandbox-preview\` runs each example in a sandboxed frame. The frame
7022
+ document either comes with the preview or is one you host.
6306
7023
 
6307
- Without \`src\`, the preview loads a built-in document. It runs the example's
6308
- HTML, CSS, and scripts, and reports the console, uncaught errors, the height,
6309
- and presses, so a sandbox renders with nothing to host. It defines no element,
6310
- so set \`src\` to a document of your own when your examples use Helix
6311
- components. Its script is inline, so a page whose Content Security Policy
6312
- refuses inline scripts needs a \`src\` too.
7024
+ **Without \`src\`, the preview loads a built-in document**, so a sandbox needs
7025
+ nothing hosted. It runs the example's HTML, CSS, and scripts, and reports the
7026
+ console, uncaught errors, the height, and presses. It defines no element, so
7027
+ set \`src\` to a document of your own when your examples use Helix components.
7028
+ Its script is inline, so a page whose Content Security Policy refuses inline
7029
+ scripts needs a \`src\` too.
7030
+
7031
+ **With \`src\`, the preview loads your document.** It loads the styles and the
7032
+ elements your examples use, and it talks to the preview through \`postMessage\`.
6313
7033
 
6314
7034
  The messages:
6315
7035
 
@@ -6333,9 +7053,18 @@ it was given. The types are exported from \`@fusion.dev/helix/sandbox\` as
6333
7053
  you. Call it once in the frame document, after your elements are defined. It
6334
7054
  writes each render into \`#preview-root\`, runs the example's scripts, relays
6335
7055
  its console, its uncaught errors, and its unhandled promise rejections,
6336
- reports its height and its presses, and adds the rules for the four layouts. A frame follows. \`/helix.css\` is the stylesheet from
6337
- \`@fusion.dev/helix-styles\`, and \`/frame.js\` is a classic script you bundle,
6338
- for the reason under "How the preview is isolated":
7056
+ reports its height and its presses, and adds the rules for the four layouts.
7057
+
7058
+ The frame's own script must be a classic script, bundled into one file. A
7059
+ module does not load there. The frame's origin is opaque, so a module import
7060
+ back to your site is a cross-origin request, which fails unless your server
7061
+ answers it with CORS headers. A stylesheet \`<link>\` is not checked, so the
7062
+ styles load as they are.
7063
+
7064
+ A frame follows. \`/frame.js\` is the bundle. \`/helix.css\` is the stylesheet
7065
+ from \`@fusion.dev/helix-styles\`, and it imports \`primitives.css\`, \`tokens.css\`,
7066
+ \`base.css\`, and \`elements.css\` from beside it, so serve the five files from
7067
+ the package's \`dist\` folder together:
6339
7068
 
6340
7069
  \`\`\`html
6341
7070
  <!doctype html>
@@ -6367,19 +7096,56 @@ Logs from the example appear in the console panel. Logs from the page around it
6367
7096
  do not. We patch the console inside the frame on purpose, so the panel shows
6368
7097
  what the example logs and nothing else.
6369
7098
 
6370
- ## The package is private
7099
+ ## The editor and the highlighter
7100
+
7101
+ \`@fusion.dev/helix-sandbox\` is the editor the component pages use, and the
7102
+ highlighter that colors their code. Install it beside the library:
7103
+
7104
+ \`\`\`sh
7105
+ npm install @fusion.dev/helix @fusion.dev/helix-styles @fusion.dev/helix-sandbox
7106
+ \`\`\`
7107
+
7108
+ Each entry does one thing:
7109
+
7110
+ - \`@fusion.dev/helix-sandbox/editor\` registers \`helix-sandbox-editor\`, a
7111
+ source pane with CodeMirror behind it.
7112
+ - \`@fusion.dev/helix-sandbox/highlight\` turns a snippet into highlighted HTML,
7113
+ on a server or in a page, with no editor.
7114
+ - \`@fusion.dev/helix-sandbox/code-block\` colors every Code Block on the page.
7115
+
7116
+ The editor is a source pane, so it goes in the \`source\` slot, with a \`name\`
7117
+ the sandbox reads:
7118
+
7119
+ \`\`\`html
7120
+ <helix-sandbox>
7121
+ <helix-sandbox-editor
7122
+ language="html"
7123
+ name="html"
7124
+ slot="source"
7125
+ value="<p>Hello</p>"
7126
+ ></helix-sandbox-editor>
7127
+ <helix-sandbox-preview slot="preview"></helix-sandbox-preview>
7128
+ </helix-sandbox>
7129
+ \`\`\`
7130
+
7131
+ \`\`\`ts
7132
+ import '@fusion.dev/helix/sandbox'
7133
+ import '@fusion.dev/helix-sandbox/editor'
7134
+ \`\`\`
6371
7135
 
6372
- \`@fusion.dev/helix-sandbox\` is not published. It is the editor and spec runner
6373
- for this repository, and it is Helix tooling. It depends on the repository:
7136
+ The [Sandbox reference](/helix/components/sandbox#helix-sandbox-editor) lists
7137
+ the editor's properties, events, methods, and states.
6374
7138
 
6375
- - The elements depend on the frame.
6376
- - The frame depends on the build.
6377
- - The build depends on this repository's layout.
7139
+ \`highlight\` reads the same grammars as the editor. By default each token is a
7140
+ span with Lezer's \`tok-\` class, for a stylesheet of your own. Pass
7141
+ \`syntaxHighlighter\` for the editor's classes, and put \`syntaxRules\` in the
7142
+ stylesheet where the result is drawn for the editor's colors:
6378
7143
 
6379
- To add editable examples to your own documentation today, you would build the
6380
- editor yourself, from the manifest and the \`.examples\` files. This is a gap we
6381
- have not filled, and we have no policy against filling it. Tell us if you need
6382
- it.
7144
+ \`\`\`ts
7145
+ import { highlight, syntaxHighlighter } from '@fusion.dev/helix-sandbox/highlight'
7146
+
7147
+ const markup = highlight('const total = 1', 'ts', syntaxHighlighter)
7148
+ \`\`\`
6383
7149
  `,
6384
7150
  'server-rendering': `---
6385
7151
  description: How components render on a server with no DOM, and what waits for the browser.
@@ -6468,8 +7234,8 @@ Run your page's own scripts after the definitions load. Hydration expects that
6468
7234
  order. A property you set before hydration is one the server did not render
6469
7235
  with.
6470
7236
 
6471
- \`bun run check:spec:dsd\` renders every example with Lit SSR, hydrates it in
6472
- Chromium, and compares it with the same example rendered on a client.
7237
+ We render every component example in both tiers with Lit SSR, hydrate it in
7238
+ Chromium, and compare it with the same example rendered on a client.
6473
7239
 
6474
7240
  React server components do not use declarative shadow DOM. Their output is
6475
7241
  replayed through DOM APIs, and declarative shadow DOM does not activate for
@@ -7228,12 +7994,18 @@ state yourself, cancel the change and assign your own value:
7228
7994
  label="Name"
7229
7995
  onhelix-before-change={(event) => {
7230
7996
  event.preventDefault()
7231
- setName(sanitize(event.detail.value))
7997
+ flushSync(() => setName(sanitize(event.detail.value)))
7232
7998
  }}
7233
7999
  value={name}
7234
8000
  />
7235
8001
  \`\`\`
7236
8002
 
8003
+ Assign your value before the next key arrives, or that key lands in the text
8004
+ from before your value. Vue, Svelte, and Solid apply a bound value in time. In
8005
+ React, set the state inside \`flushSync\`, as [React](./react) explains. In
8006
+ Angular without zone.js, call \`detectChanges()\` after you set the signal, as
8007
+ [Angular](./angular) explains.
8008
+
7237
8009
  The platform uses the same pattern. You call \`preventDefault()\` to cancel:
7238
8010
 
7239
8011
  - a form submit
@@ -7431,7 +8203,8 @@ its own components, and the same applies to anything you build with it.
7431
8203
  The theme engine measures each ink against the ground it is actually painted
7432
8204
  on. The ladder records which pairs occur. For example, \`foreground\` on
7433
8205
  \`surface-overlay\` is a real pair, and \`foreground-on-primary\` on \`surface\` is
7434
- not. See [Color](./color).
8206
+ not. A field's fill, \`well\`, is translucent, so the text in a field is measured
8207
+ against the well over each surface. See [Color](./color).
7435
8208
  `,
7436
8209
  svelte: `---
7437
8210
  description: The Svelte typings, and how Svelte passes props and binds events.
@@ -7447,30 +8220,74 @@ import '@fusion.dev/helix/svelte'
7447
8220
  \`\`\`
7448
8221
 
7449
8222
  Add this types-only import once. It augments \`SvelteHTMLElements\`, so every
7450
- Helix tag is typed in a component. The import re-exports the JSX types. Svelte
7451
- binds a handler as \`onhelix-before-change={…}\`, which is the shape those types
7452
- already describe. With one shared copy of the types, the Svelte and JSX typings
7453
- cannot disagree.
8223
+ Helix tag is typed in a component. Svelte binds a handler as
8224
+ \`onhelix-before-change={…}\`, which is the shape the JSX typings already
8225
+ describe, so the Svelte typings read each tag's props from
8226
+ \`@fusion.dev/helix/jsx\`. The two cannot disagree.
8227
+
8228
+ The typings need only Svelte's own types, and they compile with
8229
+ \`skipLibCheck: false\`.
7454
8230
 
7455
8231
  ## Pass props and bind events
7456
8232
 
7457
8233
  \`\`\`svelte
7458
- <helix-input label="Name" onchange={(event) => (name = event.target.value)} value={name} />
8234
+ <helix-input
8235
+ label="Name"
8236
+ onchange={(event) => (name = event.currentTarget.value)}
8237
+ value={name}
8238
+ ></helix-input>
7459
8239
  \`\`\`
7460
8240
 
7461
8241
  Svelte sets a property when the element has one, and an attribute otherwise. It
7462
8242
  binds \`on*\` with \`addEventListener\`. Svelte shows no unknown-element warning,
7463
8243
  and you do not need a compiler flag.
7464
8244
 
8245
+ Read the value from \`event.currentTarget\`, which is typed as the element.
8246
+ \`event.target\` is an \`EventTarget | null\`, which has no \`value\`.
8247
+
7465
8248
  Svelte passes objects and arrays as properties on its own:
7466
8249
 
7467
8250
  \`\`\`svelte
7468
- <helix-data-table label="People" rows={people} />
8251
+ <helix-data-table label="People" rows={people}></helix-data-table>
7469
8252
  \`\`\`
7470
8253
 
7471
8254
  Svelte's own attributes are typed on every Helix tag as well, so \`onclick\`,
7472
8255
  \`class\`, and a \`style\` string check as they do on a \`div\`.
7473
8256
 
8257
+ ## Hold the element
8258
+
8259
+ \`bind:this\` holds the element, typed as its class:
8260
+
8261
+ \`\`\`svelte
8262
+ <script lang="ts">
8263
+ import type { HelixInput } from '@fusion.dev/helix'
8264
+
8265
+ let input: HelixInput | undefined = $state()
8266
+ </script>
8267
+
8268
+ <helix-input bind:this={input} label="Name"></helix-input>
8269
+ \`\`\`
8270
+
8271
+ ## Type a handler in the script
8272
+
8273
+ A handler written on the tag takes its event type from the tag. A handler
8274
+ written in the script names its event type, which \`@fusion.dev/helix/jsx\`
8275
+ exports for each element and event:
8276
+
8277
+ \`\`\`svelte
8278
+ <script lang="ts">
8279
+ import type { HelixInputHelixBeforeChangeElementEvent } from '@fusion.dev/helix/jsx'
8280
+
8281
+ const refuseLong = (event: HelixInputHelixBeforeChangeElementEvent) => {
8282
+ if (event.detail.value.length > 40) {
8283
+ event.preventDefault()
8284
+ }
8285
+ }
8286
+ </script>
8287
+
8288
+ <helix-input label="Handle" onhelix-before-change={refuseLong}></helix-input>
8289
+ \`\`\`
8290
+
7474
8291
  ## Ignore the click warnings on a control
7475
8292
 
7476
8293
  \`svelte-check\` warns about an \`onclick\` on any Helix element. Its accessibility
@@ -7490,8 +8307,10 @@ right. Use a control there.
7490
8307
 
7491
8308
  These two rules apply in every framework.
7492
8309
 
7493
- \`bind:\` does not work on a Helix control, because custom elements have no
7494
- two-way binding convention to opt into. Read the value from the event.
8310
+ Two-way bindings do not work on a Helix control, because custom elements have
8311
+ no two-way binding convention to opt into. Svelte accepts \`bind:value\` only on
8312
+ \`<input>\`, \`<textarea>\`, and \`<select>\`, and \`bind:checked\` only on \`<input>\`.
8313
+ Read the value from the event.
7495
8314
 
7496
8315
  A declared \`value\`, \`open\`, \`checked\`, or \`active\` is a seed. The element starts
7497
8316
  with that value and then applies its own changes. To refuse a change, cancel the
@@ -7573,9 +8392,10 @@ its target. Its three tabs are:
7573
8392
  - **Contrast.** The pairs that fail, or a line saying that every pair passes.
7574
8393
  - **Collisions.** Colors too close to each other to tell apart, such as the
7575
8394
  primary color and a status color.
7576
- - **Unverifiable.** Pairs whose background is translucent, so there is no known
7577
- color to measure against. This is the cost of a translucent material, and
7578
- the theme builder reports it.
8395
+ - **Unverifiable.** Pairs whose background is translucent over something the
8396
+ engine cannot know, such as a photograph or the page under a translucent
8397
+ material, so there is no known color to measure against. This is the cost of
8398
+ a translucent material, and the theme builder reports it.
7579
8399
 
7580
8400
  ## What the theme builder gives you
7581
8401
 
@@ -7589,6 +8409,17 @@ stylesheet:
7589
8409
  }
7590
8410
  \`\`\`
7591
8411
 
8412
+ The block holds one mode, the one the preview shows, under the scope
8413
+ \`[data-theme="preview"]\`. Put that attribute on the element to theme, or change
8414
+ the selector. Under any selector other than \`:root\`, also add
8415
+ \`data-helix-theme\` to the element. The block sets the seeds the stylesheet
8416
+ derives the chart colors, the spacing, and the corner radii from, and the
8417
+ stylesheet reads seeds only at \`:root\` or on an element with
8418
+ \`data-helix-theme\`. See [Two ways to theme](./theming#two-ways-to-theme).
8419
+
8420
+ For a page with both modes, derive the intent with \`deriveModes\` or
8421
+ \`helix theme build\`, as [Derive both modes](./theming#derive-both-modes) shows.
8422
+
7592
8423
  The page's link holds every input you changed, so a link is the theme. To derive
7593
8424
  the theme in your own build, write the same inputs as an intent object:
7594
8425
 
@@ -7639,7 +8470,11 @@ changing them one at a time. A change to one value can break the contrast of a
7639
8470
  component elsewhere, and nothing tells you.
7640
8471
 
7641
8472
  \`@fusion.dev/helix-theme\` computes a complete token set from a few intent
7642
- inputs, and every text and background pair meets its contrast target.
8473
+ inputs. It solves each text color against the backgrounds it lands on, measures
8474
+ every pair in its surface model, and reports any pair that misses its contrast
8475
+ target. \`foreground-subtle\` is not in the model. It is a fixed step of the
8476
+ stylesheet's gray ramp, and the engine does not solve it, so its contrast is not
8477
+ guaranteed.
7643
8478
 
7644
8479
  ## Two ways to theme
7645
8480
 
@@ -7855,8 +8690,8 @@ depends only on the packages below it.
7855
8690
  Neither is a tier, and you do not need either one to render a component.
7856
8691
 
7857
8692
  There is no React package. We generate the framework typings into
7858
- \`@fusion.dev/helix\`, and the bridge that repairs React's hydration of custom
7859
- events is in \`@fusion.dev/helix-core\`. See [React](./react).
8693
+ \`@fusion.dev/helix\` and \`@fusion.dev/helix-headless\`, and the bridge that
8694
+ repairs React's hydration of custom events is in \`@fusion.dev/helix-core\`. See [React](./react).
7860
8695
 
7861
8696
  ## Why the tiers are separate packages
7862
8697
 
@@ -7898,7 +8733,7 @@ export class HelixHeadlessAccordionItem extends MixinControllerOpen(HelixElement
7898
8733
  }
7899
8734
  }
7900
8735
 
7901
- // web/src/accordion/root/accordion-root.ts
8736
+ // styled/src/accordion/root/accordion-root.ts
7902
8737
  // extends the headless one, adds presentation only
7903
8738
  import { styles } from './accordion-root.css.js'
7904
8739
 
@@ -7912,7 +8747,7 @@ export class HelixAccordion extends HelixHeadlessAccordion {
7912
8747
  \`\`\`
7913
8748
 
7914
8749
  \`\`\`css
7915
- /* web/src/accordion/item/accordion-item.css */
8750
+ /* styled/src/accordion/item/accordion-item.css */
7916
8751
  [part='trigger'] {
7917
8752
  min-block-size: var(--helix-size-control);
7918
8753
  padding: var(--helix-space-sm);
@@ -7949,8 +8784,8 @@ as unstyled text and then changes into a card. The delay is short. It is most
7949
8784
  visible on a server-rendered page, where the markup arrives long before the
7950
8785
  JavaScript.
7951
8786
 
7952
- \`@fusion.dev/helix\` includes \`elements.css\`, which hides each of its tags until
7953
- the tag's definition loads:
8787
+ \`@fusion.dev/helix-styles\` includes \`elements.css\`, which hides each tag of the
8788
+ styled tier until the tag's definition loads:
7954
8789
 
7955
8790
  \`\`\`css
7956
8791
  helix-card:not(:defined) {
@@ -7958,7 +8793,7 @@ helix-card:not(:defined) {
7958
8793
  }
7959
8794
  \`\`\`
7960
8795
 
7961
- **The base tier ships no such file on purpose.** There are three reasons, and
8796
+ **The headless tier ships no such file on purpose.** There are three reasons, and
7962
8797
  the third one affects you most.
7963
8798
 
7964
8799
  First, hiding an element is a design choice. Below the styled tier, we ship only
@@ -7972,20 +8807,21 @@ inside, because there is no shadow root before the upgrade. The rule has to
7972
8807
  live in your stylesheet, so the choice is yours.
7973
8808
 
7974
8809
  Third, one file for every tag would not fit your project. You import only the
7975
- base elements you use, so a generated list would include tags you never ship.
8810
+ headless elements you use, so a generated list would include tags you never ship.
7976
8811
  Write the rule for the elements you have:
7977
8812
 
7978
8813
  \`\`\`css
7979
- helix-base-accordion:not(:defined),
7980
- helix-base-dialog:not(:defined) {
8814
+ helix-headless-accordion:not(:defined),
8815
+ helix-headless-dialog:not(:defined) {
7981
8816
  visibility: hidden;
7982
8817
  }
7983
8818
  \`\`\`
7984
8819
 
7985
- Use \`visibility\` so the element keeps its place in the layout and the page does
7986
- not reflow when it upgrades. \`display: none\` collapses the box, and the content
7987
- jumps in when the definition arrives. That replaces a flash of unstyled text
7988
- with a shift of everything below it.
8820
+ Use \`visibility\`, so a hidden element keeps the size of its unstyled content.
8821
+ The layout still settles when the element upgrades and takes its own size.
8822
+ \`display: none\` collapses the box, so the element takes no space until the
8823
+ definition arrives, and then everything below it shifts by the element's full
8824
+ size.
7989
8825
 
7990
8826
  You may want to hide nothing. If your first paint matters more than the final
7991
8827
  layout, or your page has to be readable with JavaScript turned off, let the
@@ -8289,8 +9125,11 @@ writes where an installed family stands in for it, as \`native-element\`.
8289
9125
 
8290
9126
  Each family's API is in the packages you installed, and on this site:
8291
9127
 
8292
- - \`helix api family <family>\`, or the MCP tool \`helix_family\`
8293
- - \`node_modules/@fusion.dev/helix/api.json\`
9128
+ - \`helix api family <family>\`, or the MCP tool \`helix_family\`, which read every
9129
+ Helix package you installed
9130
+ - \`node_modules/@fusion.dev/helix/api.json\`, and the \`api.json\` of each other
9131
+ Helix package you installed, which lists the elements it adds to a family,
9132
+ such as \`helix-sandbox-editor\` in \`@fusion.dev/helix-sandbox\`
8294
9133
  - \`https://fusion.dev/helix/components/<family>.md\` and \`.json\`
8295
9134
 
8296
9135
  Each lists the import lines, every tag, every property with its literal union,
@@ -8344,12 +9183,15 @@ block a change, call \`preventDefault()\` on the matching \`helix-before-*\` eve
8344
9183
  label="Name"
8345
9184
  onhelix-before-change={(event) => {
8346
9185
  event.preventDefault()
8347
- setName(clean(event.detail.value))
9186
+ flushSync(() => setName(clean(event.detail.value)))
8348
9187
  }}
8349
9188
  value={name}
8350
9189
  />
8351
9190
  \`\`\`
8352
9191
 
9192
+ Set the state inside \`flushSync\` from \`react-dom\`. Without it, React renders the
9193
+ update in a later task, and keys typed faster than that are lost.
9194
+
8353
9195
  A form reset returns the element to \`default-value\` and the matching attributes
8354
9196
  for the other states. See [State and control](./state-and-control).
8355
9197
 
@@ -8459,12 +9301,13 @@ Run these after you write Helix code, and fix every finding at \`error\` and
8459
9301
 
8460
9302
  | Command | MCP tool | What it finds |
8461
9303
  |---|---|---|
8462
- | \`helix scan\` | \`helix_check\` | Unknown tags, attributes, values, slots, parts, hooks, events, and tokens, a native element a Helix family replaces, a missing import, and the rest, each with its file and line |
9304
+ | \`helix scan\` | \`helix_check\` | Unknown tags, attributes, values, slots, parts, hooks, events, and tokens, a native element a Helix family replaces, a missing import, a button inside a button, \`disabled="false"\`, and the rest, each with its file and line |
8463
9305
  | \`helix diagnostics --once <url>\` | \`helix_check_page\` | The same on the running page, and what the library logged as it ran |
8464
9306
  | \`helix theme audit\` | \`helix_audit_theme\` | Every foreground and background pair in your theme that misses its contrast target |
8465
9307
 
8466
9308
  \`helix scan --file <path>\` reports one file's findings and still reads the whole
8467
- project, so an import in another file counts.
9309
+ project, so an import in another file counts. [Diagnostics](./diagnostics)
9310
+ lists every kind of finding, and what the checks do not cover.
8468
9311
 
8469
9312
  ## What this guide does not cover
8470
9313
 
@@ -8579,8 +9422,14 @@ import '@fusion.dev/helix-styles/helix.css'
8579
9422
  base layer. If you import the layers one at a time, import \`elements.css\` last.
8580
9423
 
8581
9424
  \`elements.css\` hides each tag with \`:not(:defined)\` until the element upgrades.
8582
- It uses \`visibility\`, so the page does not reflow. Without it, a card appears as
8583
- unstyled text and then jumps into its card layout.
9425
+ It uses \`visibility\`, so a hidden element keeps the size of its unstyled
9426
+ content. The layout settles when the elements upgrade and take their own size.
9427
+ Without the file, a card appears as unstyled text and then jumps into its card
9428
+ layout.
9429
+
9430
+ A data table that serves its first page as rows is the one element shown before
9431
+ it upgrades. \`elements.css\` lays out those rows as the table draws them. See
9432
+ [React](./react).
8584
9433
 
8585
9434
  We generate the file from the element sources, and it lists every tag by name.
8586
9435
  CSS has no selector for any custom element, and a bare \`:not(:defined)\` would
@@ -8598,36 +9447,31 @@ use a button.
8598
9447
 
8599
9448
  ## Turn off an attribute that is on by default
8600
9449
 
8601
- A boolean attribute is off until you write it, with four exceptions. These are
8602
- on until you write the string \`"false"\`:
8603
-
8604
- - \`modal\` on Drawer
8605
- - \`playsinline\` on Video
8606
- - \`default-pinned\` on Chat
8607
- - \`tab-indents\` on Sandbox Source
9450
+ A boolean attribute is off until you write it, unless its default is \`true\`. The
9451
+ property table on each component's page shows the default. An attribute whose
9452
+ default is \`true\` stays on until you write the string \`"false"\`, such as \`modal\`
9453
+ on Drawer:
8608
9454
 
8609
9455
  \`\`\`html
8610
9456
  <helix-drawer modal="false"></helix-drawer>
8611
9457
  \`\`\`
8612
9458
 
8613
- In JSX, \`modal={false}\` sets the property and does the same. Other features
8614
- that are on by default turn off with an attribute named \`no-\`, such as
8615
- \`no-copy\`.
9459
+ \`"false"\` is the only value that turns it off. An empty value, or any other
9460
+ string, leaves it on. In JSX, \`modal={false}\` sets the property and does the
9461
+ same. Other features that are on by default turn off with an attribute named
9462
+ \`no-\`, such as \`no-copy\`.
8616
9463
 
8617
9464
  ## Declarative shadow DOM is opt-in
8618
9465
 
8619
- The site ships no declarative shadow DOM (DSD). The shadow styles load with the
8620
- element definition. You do not need to configure anything. If you render with
8621
- Lit SSR yourself, its declarative shadow DOM hydrates. See
8622
- [Server rendering](./server-rendering).
8623
-
8624
- The site ships no DSD for three reasons:
9466
+ The elements need no declarative shadow DOM (DSD). Each element attaches its
9467
+ shadow root and loads its styles with its definition, so you do not need to
9468
+ configure anything. If you render with Lit SSR yourself, the DSD it writes
9469
+ hydrates. See [Server rendering](./server-rendering).
8625
9470
 
8626
- - DSD cannot activate for nodes inserted by script.
8627
- - React inserts a server component's output by replaying the flight payload
8628
- through DOM APIs, without running the component again.
8629
- - The patch that made DSD work hooked into webpack, and Turbopack ignores that
8630
- hook without an error.
9471
+ DSD does not work in a React server component. React inserts a server
9472
+ component's output by replaying the flight payload through DOM APIs, without
9473
+ running the component again, and DSD does not activate for nodes that a script
9474
+ inserts.
8631
9475
  `,
8632
9476
  versioning: `---
8633
9477
  description: What a Helix version number means, and which packages are released together.
@@ -8662,13 +9506,13 @@ the same version were tested together.
8662
9506
 
8663
9507
  ## The version number comes from the Git tag
8664
9508
 
8665
- Every manifest in the repository has the version \`0.0.0-dev\`. CI writes the real
8666
- number into each manifest seconds before packing.
9509
+ CI computes each version from the Git tags and writes it into every package
9510
+ manifest seconds before packing. Once the release is promoted, CI commits the
9511
+ same number back to \`main\`. The \`version\` in each Helix \`package.json\` in the
9512
+ repository is therefore the last release, and the tag is the record it copies.
8667
9513
 
8668
- If you read the source, you will see \`0.0.0-dev\` on every package, including
8669
- released ones. We do not commit the real number back. That would need a bot
8670
- with push access that overrides branch protection, and \`git tag\` already records
8671
- the number.
9514
+ The Rust crates stay at \`0.0.0-dev\`. The \`helix\` binary reports the version of
9515
+ the npm package it was installed with.
8672
9516
 
8673
9517
  Releases use two kinds of tag:
8674
9518
 
@@ -8700,8 +9544,15 @@ one of:
8700
9544
  - \`stable\`
8701
9545
  - \`deprecated\`
8702
9546
 
8703
- Most components are experimental today, and their status says so. We will not
8704
- change a status to make the library look more finished than it is.
9547
+ Most components are stable. These families are experimental, and their status
9548
+ says so:
9549
+
9550
+ - the spatial charts, which draw in three dimensions
9551
+ - Log
9552
+ - Strands
9553
+ - Motion Toggle
9554
+
9555
+ We will not change a status to make the library look more finished than it is.
8705
9556
 
8706
9557
  ## Release notes
8707
9558
 
@@ -8739,7 +9590,7 @@ accept a subset of an axis's values.
8739
9590
  tone="primary"
8740
9591
  >Save</helix-button>
8741
9592
  <helix-card separation="outlined">…</helix-card>
8742
- <helix-megamenu material="glass">…</helix-megamenu>
9593
+ <helix-drawer material="glass">…</helix-drawer>
8743
9594
  \`\`\`
8744
9595
 
8745
9596
  ## Why there is no \`appearance\` attribute
@@ -8763,9 +9614,16 @@ build.
8763
9614
  ## When a property needs two axes
8764
9615
 
8765
9616
  **If two values can apply at the same time, they belong on two axes.**
8766
- \`Megamenu\` is the example. \`variant="band"\` sets the layout of its panel, and
8767
- glass is a \`material\`. A panel can be a band and glass at once, so the two
8768
- values sit on two axes. A panel can also be glass and outlined at once.
9617
+ \`Card\` is the example. \`separation\` sets where its surface ends, and \`material\`
9618
+ sets what fills it. A card can be outlined and glass at once, so the two values
9619
+ sit on two axes:
9620
+
9621
+ \`\`\`html
9622
+ <helix-card
9623
+ material="glass"
9624
+ separation="outlined"
9625
+ >…</helix-card>
9626
+ \`\`\`
8769
9627
 
8770
9628
  If you are adding a property and cannot decide which axis it belongs on, it is
8771
9629
  usually two properties.
@@ -9141,6 +9999,23 @@ Helix tag in an SFC template has typed props and events. The generated file
9141
9999
  declares the augmentation under both \`vue\` and \`@vue/runtime-core\`, because
9142
10000
  different tools read different ones.
9143
10001
 
10002
+ ## Report a misspelled prop
10003
+
10004
+ \`vue-tsc\` checks the value of each prop a Helix tag declares. By default, it
10005
+ does not report a prop name that the tag does not declare, so a misspelled prop
10006
+ compiles. To report one, turn on \`checkUnknownProps\` in \`tsconfig.json\`:
10007
+
10008
+ \`\`\`json
10009
+ {
10010
+ "vueCompilerOptions": {
10011
+ "checkUnknownProps": true
10012
+ }
10013
+ }
10014
+ \`\`\`
10015
+
10016
+ \`strictTemplates\` turns it on with the rest of Vue's strict template checks.
10017
+ Either option also reports a \`data-*\` attribute on a Helix tag.
10018
+
9144
10019
  ## Mark Helix tags as custom elements
9145
10020
 
9146
10021
  Without this setting, Vue treats \`<helix-button>\` as a component it cannot find
@@ -9195,7 +10070,7 @@ work. See [State and control](./state-and-control).
9195
10070
  <helix-input default-value="Ada" label="Name" />
9196
10071
 
9197
10072
  <!-- Mirrored: bind the notification and let the element apply. -->
9198
- <helix-input label="Name" @change="name = $event.target.value" />
10073
+ <helix-input label="Name" @change="name = ($event.currentTarget as HelixInput).value" />
9199
10074
 
9200
10075
  <!-- Refused: you take the decision for this one change. -->
9201
10076
  <helix-input
@@ -9204,6 +10079,16 @@ work. See [State and control](./state-and-control).
9204
10079
  @helix-before-change="$event.preventDefault(); name = clean($event.detail.value)"
9205
10080
  />
9206
10081
  \`\`\`
10082
+
10083
+ For a native event such as \`change\`, \`vue-tsc\` types \`$event.currentTarget\` as
10084
+ \`EventTarget | null\`, which has no \`value\`. The mirrored line casts it to the
10085
+ element's class, which you import as a type from the family's subpath:
10086
+
10087
+ \`\`\`vue
10088
+ <script setup lang="ts">
10089
+ import type { HelixInput } from '@fusion.dev/helix/input'
10090
+ </script>
10091
+ \`\`\`
9207
10092
  `,
9208
10093
  'without-a-bundler': `---
9209
10094
  description: Use the Helix styles on a page with no build step, and why the elements need a bundler for now.
@@ -9239,10 +10124,10 @@ own URL. Copy the whole \`dist\` folder and link \`helix.css\`. We checked this
9239
10124
  Chromium: one \`<link>\` makes five requests, and every layer resolves.
9240
10125
 
9241
10126
  *Until August 24, 2026, this page listed \`tokens.css\` and \`base.css\` without
9242
- \`primitives.css\`. \`tokens.css\` refers to a primitive 297 times and imports
9243
- nothing. If you followed the old instructions, all 185 semantic tokens are
9244
- declared and every color resolves to \`rgba(0, 0, 0, 0)\`. If your page looks
9245
- unstyled in that way, link \`helix.css\`.*
10127
+ \`primitives.css\`. \`tokens.css\` refers to the primitives throughout and imports
10128
+ nothing. If you followed the old instructions, every semantic token is declared
10129
+ and every color resolves to \`rgba(0, 0, 0, 0)\`. If your page looks unstyled in
10130
+ that way, link \`helix.css\`.*
9246
10131
 
9247
10132
  \`elements.css\` matters most on a page with no bundler. Without a bundler, the
9248
10133
  module graph loads over the network, so the markup is on the page longer before
@@ -9255,9 +10140,11 @@ package made its import relative. Only a bundler resolves a bare specifier in an
9255
10140
 
9256
10141
  \`@fusion.dev/helix\` imports its dependencies by bare specifiers, such as
9257
10142
  \`@fusion.dev/helix-core\` and \`lit\`. A browser resolves a bare specifier only
9258
- through an import map, and the elements import 97 separate subpaths of
9259
- \`@fusion.dev/helix-headless\`. An import map would need an entry for each one,
9260
- and for each subpath of \`lit\`.
10143
+ through an import map. The elements import a separate subpath of
10144
+ \`@fusion.dev/helix-headless\` for each family, and the package's \`exports\` maps
10145
+ each one to an \`index.js\` in a folder of its own, so an import map would need an
10146
+ entry for each one. A subpath of \`lit\` is the path of its file, so one \`"lit/"\` entry
10147
+ covers all of them.
9261
10148
 
9262
10149
  To use the elements, load them through a bundler, as
9263
10150
  [Installation](./installation) shows. A CDN that rewrites bare specifiers into