@fusion.dev/helix-docs 0.104.1 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/content/accessibility.mdx +92 -71
  2. package/content/angular.mdx +26 -24
  3. package/content/browser-support.mdx +61 -58
  4. package/content/building-on-the-headless-tier.mdx +100 -74
  5. package/content/charting.mdx +98 -79
  6. package/content/coding-agents.mdx +87 -69
  7. package/content/color-modes.mdx +40 -37
  8. package/content/color.mdx +67 -54
  9. package/content/composition.mdx +85 -83
  10. package/content/elevation-and-material.mdx +32 -29
  11. package/content/events.mdx +77 -65
  12. package/content/forms.mdx +69 -58
  13. package/content/iconography.mdx +75 -71
  14. package/content/installation.mdx +77 -68
  15. package/content/intent-reference.mdx +59 -54
  16. package/content/introduction.mdx +25 -23
  17. package/content/localization.mdx +122 -82
  18. package/content/maps.mdx +431 -0
  19. package/content/metrics.mdx +296 -299
  20. package/content/motion.mdx +44 -36
  21. package/content/other-frameworks.mdx +33 -31
  22. package/content/overlays.mdx +72 -54
  23. package/content/overriding-a-component.mdx +85 -74
  24. package/content/react.mdx +72 -64
  25. package/content/recipes.mdx +64 -47
  26. package/content/routing.mdx +100 -93
  27. package/content/sandbox.mdx +39 -39
  28. package/content/server-rendering.mdx +95 -84
  29. package/content/shape-and-borders.mdx +44 -37
  30. package/content/size-and-density.mdx +51 -46
  31. package/content/space-and-size.mdx +41 -34
  32. package/content/state-and-control.mdx +63 -51
  33. package/content/surfaces-and-separation.mdx +55 -53
  34. package/content/svelte.mdx +20 -19
  35. package/content/theme-builder.mdx +42 -31
  36. package/content/theming.mdx +58 -51
  37. package/content/tiers.mdx +69 -61
  38. package/content/tokens.mdx +72 -53
  39. package/content/typography.mdx +47 -47
  40. package/content/using-helix-with-an-agent.mdx +147 -115
  41. package/content/using-the-elements.mdx +55 -50
  42. package/content/versioning.mdx +42 -35
  43. package/content/visual-axes.mdx +168 -146
  44. package/content/vue.mdx +24 -24
  45. package/content/without-a-bundler.mdx +40 -23
  46. package/dist/pages.generated.js +49 -43
  47. package/dist/sources.generated.js +3548 -2682
  48. package/package.json +3 -3
@@ -1,12 +1,12 @@
1
1
  ---
2
- description: Four durations, five curves, and the rule every animating component has to answer.
2
+ description: The duration and easing tokens, and how components handle reduced motion.
3
3
  section: Foundations
4
4
  title: Motion
5
5
  ---
6
6
 
7
7
  # Motion
8
8
 
9
- ## The tokens
9
+ ## Duration and easing tokens
10
10
 
11
11
  [//]: # (tokens:duration)
12
12
 
@@ -31,33 +31,40 @@ title: Motion
31
31
 
32
32
  [//]: # (/tokens)
33
33
 
34
- Four durations and five curves. `standard` for most things, `decelerate` for
35
- something arriving, `accelerate` for something leaving, `emphasized` for a
36
- movement the reader is meant to follow, and `spring` for the few places
37
- overshoot reads as responsive rather than as a bug.
34
+ Helix has four durations and five curves. Use each curve for one kind of
35
+ movement:
38
36
 
39
- The theme engine takes a `motion` intent input, so a product can be uniformly
40
- quicker or slower without every component being retuned.
37
+ - `standard` for most movement
38
+ - `decelerate` for something arriving
39
+ - `accelerate` for something leaving
40
+ - `emphasized` for a movement you want the user to follow
41
+ - `spring` for the few places where overshoot looks responsive and does not
42
+ look like a bug
41
43
 
42
- ## Reduced motion is per component
44
+ The theme engine takes a `motion` intent input. Use it to make every component
45
+ faster or slower at once.
43
46
 
44
- The base layer has a global rule capping animation and transition duration for a
45
- reader who asked not to be moved. **It does not reach a single component.**
47
+ ## Each component handles reduced motion
48
+
49
+ The base layer has a global rule that caps animation and transition durations
50
+ when a user has asked for reduced motion. **The global rule does not apply
51
+ inside any component.**
46
52
 
47
53
  ```css
48
54
  *, *::before, *::after { … }
49
55
  ```
50
56
 
51
- `*` does not cross a shadow boundary. So that rule reaches your prose, your
52
- layout and the base layer's own recipes, and none of the ninety-odd components
53
- that draw inside a shadow root. Each one answers `prefers-reduced-motion` in its
54
- own stylesheet, fifty-nine of them do because fifty-nine of them animate, and a
55
- component that animates without one fails the build.
57
+ The `*` selector does not cross a shadow boundary. The rule applies to your
58
+ prose, your layout, and the base layer's own recipes. It does not apply to the
59
+ ninety-odd components that draw inside a shadow root. Each component that
60
+ animates handles `prefers-reduced-motion` in its own stylesheet. Fifty-nine
61
+ components animate, and all fifty-nine have this rule. A component that
62
+ animates without one fails the build.
56
63
 
57
- **The same is true of yours.** If you write a component with a shadow root, the
58
- global rule will not cover it either. That is not a Helix behavior, it is how
59
- `*` works, and it is the single easiest accessibility regression to ship without
60
- noticing.
64
+ The same applies to your own components. If you write a component with a shadow
65
+ root, the global rule does not cover it. This comes from how `*` works in CSS,
66
+ so it applies outside Helix too. It is the easiest accessibility regression to
67
+ ship without noticing.
61
68
 
62
69
  ```css
63
70
  @media (prefers-reduced-motion: reduce) {
@@ -68,24 +75,25 @@ noticing.
68
75
  }
69
76
  ```
70
77
 
71
- ## What reduced motion means here
78
+ ## What reduced motion changes
79
+
80
+ Some transitions show a relationship, such as a panel that slides out of the
81
+ control that opened it. Others are decorative, such as a floating effect. With
82
+ reduced motion, Helix removes decorative movement. It shortens transitions that
83
+ show a relationship until they are close to instant, without an abrupt jump.
72
84
 
73
- Not "no movement". A transition that communicates a relationship, like a panel
74
- sliding out of the control that opened it, is doing work; a decorative float is
75
- not. The library's own policy is to remove the second and shorten the first to
76
- the point where it is instant but not jarring.
85
+ Anything that moves continuously without input stops completely:
77
86
 
78
- The one place this is absolute is anything that moves continuously without
79
- input: the aura shader, marquees, and looping loaders all stop rather than speed
80
- up.
87
+ - The aura shader
88
+ - Marquees
89
+ - Looping loaders
81
90
 
82
- ## Two things that are not motion tokens
91
+ ## Starting styles and view transitions
83
92
 
84
- **`@starting-style`** is how overlays animate in, and it needs no duration token
85
- of its own: it is a starting value, and the transition that runs from it uses
86
- the tokens above.
93
+ Overlays use `@starting-style` to animate in. It sets a starting value and needs
94
+ no duration token of its own. The transition that runs from it uses the tokens
95
+ above.
87
96
 
88
- **View transitions** are not used anywhere in the library. A component cannot
89
- own a view transition, because the transition is between two states of the
90
- document rather than of the component, so if you want them that is yours to
91
- drive.
97
+ The library does not use view transitions. A view transition runs between two
98
+ states of the document, so a component cannot own one. If you want view
99
+ transitions, start them from your own code.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: The three rules every framework reduces to, and what they look like in each.
2
+ description: The three rules for using Helix in any framework, and how each framework applies them.
3
3
  parent: installation
4
4
  section: Getting started
5
5
  title: Other frameworks
@@ -8,64 +8,66 @@ trailing: true
8
8
 
9
9
  # Other frameworks
10
10
 
11
- Solid, Qwik, Lit, Ember, Alpine, htmx, and anything else that puts elements in a
12
- document. All of them need the same three rules, and nothing beyond them.
11
+ This page covers Solid, Qwik, Lit, Ember, Alpine, htmx, and any other framework
12
+ that puts elements in a document. They all follow the same three rules and need
13
+ nothing else.
13
14
 
14
15
  ## The three rules
15
16
 
16
17
  **Strings go through attributes. Everything else goes through properties.** An
17
- attribute is a string, so an array, an object or a function has to be assigned
18
- to the element rather than written in the markup. Most frameworks do this for
19
- you when the element has a matching property.
18
+ attribute is a string, so assign an array, an object, or a function to the
19
+ element as a property. Most frameworks do this for you when the element has a
20
+ matching property.
20
21
 
21
22
  ```js
22
23
  element.items = tabs
23
24
  ```
24
25
 
25
- **Events bind with `addEventListener`, and the name is the event's own.** Helix
26
- events are `helix-before-open`, `helix-before-change`, `helix-select` and so on:
27
- lowercase,
28
- hyphenated, no camel-case variant. Whatever your framework's `on*` syntax is, it
29
- should end up passing that string through.
26
+ **Bind events with `addEventListener`, using the event's own name.** Helix event
27
+ names, such as `helix-before-open`, `helix-before-change`, and `helix-select`,
28
+ are lowercase and hyphenated. They have no camel-case variant. Your framework's
29
+ `on*` syntax needs to pass that exact name through.
30
30
 
31
31
  ```js
32
32
  element.addEventListener('helix-before-change', (event) => console.log(event.detail))
33
33
  ```
34
34
 
35
- **A declared `value`, `open`, `checked` or `active` is a seed.** The element
36
- starts there and goes on managing itself, exactly as `<input value="Ada">` does.
37
- To take one decision, listen for the matching `helix-before-*` event and call
38
- `preventDefault()`; to own the state outright, refuse and assign your own.
39
- `default-value`, `default-open` and their siblings are what a reset returns to.
40
- [State and control](./state-and-control) is the page about it.
35
+ **A declared `value`, `open`, `checked`, or `active` is a seed.** The element
36
+ starts with that value and then manages its own state, as `<input value="Ada">`
37
+ does. To refuse one change, listen for the matching `helix-before-*` event and
38
+ call `preventDefault()`. To own the state fully, refuse every change and assign
39
+ your own value. A reset returns to `default-value`, `default-open`, and the
40
+ similar attributes. [State and control](./state-and-control) explains the model
41
+ in full.
41
42
 
42
- ## What each one needs
43
+ ## Notes for each framework
43
44
 
44
45
  | Framework | Notes |
45
46
  |---|---|
46
47
  | 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 |
47
48
  | Qwik | Works as JSX. Bind with `document:on…` or a ref, since a plain `on*` prop is normalized |
48
- | Lit | `.prop=`, `?bool=` and `@helix-before-change=` are already the right three channels |
49
+ | Lit | `.prop=`, `?bool=`, and `@helix-before-change=` work with no setup |
49
50
  | Ember | Angle-bracket invocation passes attributes only, so set properties from a modifier |
50
- | Alpine, htmx | Markup and attributes only, which reaches every component. Refusing a change needs script |
51
+ | Alpine, htmx | Markup and attributes only, which work with every component. Refusing a change needs script |
51
52
 
52
- Nothing here is tested by us, which is the honest reason it is one page. If your
53
- framework needs something the three rules above do not cover, that is worth
54
- telling us: it is either a gap in this page or a gap in the library.
53
+ We do not test these frameworks, so they share one page. If your framework needs
54
+ something the three rules do not cover, tell us. It is a gap in this page or in
55
+ the library.
55
56
 
56
57
  ## Typings
57
58
 
58
- `@fusion.dev/helix/jsx` is the base for anything JSX-shaped, and Svelte's own
59
- typings are built from it. If your framework needs a different shape, the
60
- manifest ships as `@fusion.dev/helix/custom-elements.json` and describes every tag,
61
- prop, event and part, which is what all of ours are generated from.
59
+ `@fusion.dev/helix/jsx` is the base for typing any framework that uses JSX, and
60
+ the Svelte typings are built from it. If your framework needs a different
61
+ format, use the manifest at `@fusion.dev/helix/custom-elements.json`. It
62
+ describes every tag, prop, event, and part, and we generate all our typings from
63
+ it.
62
64
 
63
- ## Registration
65
+ ## Register the elements
64
66
 
65
67
  ```ts
66
68
  import '@fusion.dev/helix'
67
69
  ```
68
70
 
69
- Once, anywhere that runs in the browser. See [Installation](./installation) for
70
- the stylesheets, which are the same in every framework and are the thing most
71
- often left out.
71
+ Import it once, anywhere that runs in the browser. The stylesheets are the same
72
+ in every framework, and they are the part most often left out. See
73
+ [Installation](./installation) for the stylesheets.
@@ -1,71 +1,90 @@
1
1
  ---
2
- description: The top layer, what dismisses what, and where focus goes.
2
+ description: How overlays use the top layer, how they close, and where focus goes.
3
3
  section: Essentials
4
4
  title: Overlays and layering
5
5
  ---
6
6
 
7
7
  # Overlays and layering
8
8
 
9
- `Dialog`, `Drawer`, `Popover`, `Menu`, `Select`, `Combobox`, `Tooltip`,
10
- `Megamenu`, `Speed Dial`, `Command` and `Tour` are all overlays, and they all
11
- work the same way underneath. Knowing that way saves you from most of the
12
- problems overlays cause.
9
+ These components are overlays, and they all work the same way:
13
10
 
14
- ## They render in the top layer
11
+ - `Dialog`
12
+ - `Drawer`
13
+ - `Popover`
14
+ - `Menu`
15
+ - `Select`
16
+ - `Combobox`
17
+ - `Tooltip`
18
+ - `Megamenu`
19
+ - `Speed Dial`
20
+ - `Command`
21
+ - `Tour`
15
22
 
16
- Every one uses the popover API, so the browser draws it in the top layer: above
17
- everything, regardless of `z-index`, `overflow: hidden`, `transform` or a
18
- stacking context somewhere up the tree.
23
+ Knowing how they work helps you avoid most overlay problems.
19
24
 
20
- **This is the fix for the oldest bug in the category.** A menu inside a
21
- scrolling panel does not get clipped. A tooltip inside a `transform`ed card does
22
- not get trapped in its stacking context. There is no portal to configure and no
23
- `appendTo` to pass, because the platform is doing it.
25
+ ## Overlays render in the top layer
24
26
 
25
- The consequence for your CSS: **do not try to raise an overlay with `z-index`.**
26
- It has no effect on a top-layer element, and reaching for it means something
27
- else is wrong.
27
+ Every overlay uses the popover API, so the browser draws it in the top layer.
28
+ The top layer is above everything on the page, and these do not affect it:
28
29
 
29
- ## Positioning is JavaScript
30
+ - `z-index`
31
+ - `overflow: hidden`
32
+ - `transform`
33
+ - a stacking context in an ancestor
30
34
 
31
- Placement uses `@floating-ui/dom` rather than CSS anchor positioning: flip when
32
- there is no room below, move below or above when a panel placed beside its
33
- anchor has room on neither side, shift to stay on screen, size to the available
34
- space. Anchor positioning would be the platform answer and is not yet available
35
- everywhere the rest of the library's requirements are. See [Browser
36
- support](./browser-support).
35
+ As a result, a menu inside a scrolling panel is not clipped, and a tooltip
36
+ inside a `transform`ed card is not trapped in its stacking context. You do not
37
+ configure a portal or pass `appendTo`, because the browser handles the layering.
37
38
 
38
- That means an overlay's position is recomputed while it is open, on scroll and
39
- resize, and stops when it closes.
39
+ **Do not raise an overlay with `z-index`.** It has no effect on a top-layer
40
+ element. If you want to use it, something else is wrong.
40
41
 
41
- ## Dismissal
42
+ ## How overlays are positioned
42
43
 
43
- Light dismiss is the platform's: click outside, or press `Escape`. What Helix
44
- adds is making that correct across shadow boundaries.
44
+ Overlays are placed with `@floating-ui/dom`, which does the following:
45
45
 
46
- **`document.activeElement` stops at the outermost host.** Focus inside a shadow
47
- tree is reported as the element containing that tree, so a naive "is focus still
48
- inside me" check never matches and closes the overlay the moment anything inside
49
- it is focused. Every dismissal path here walks through the roots instead.
46
+ - It flips a panel to the other side of its anchor when there is no room below.
47
+ - It moves a panel placed beside its anchor below or above when there is no room
48
+ on either side.
49
+ - It shifts a panel to stay on screen.
50
+ - It sizes a panel to the available space.
50
51
 
51
- **Nesting works because lifecycle events do not bubble.** `helix-before-open` and
52
- `helix-before-close` are neither bubbling nor composed, so a menu closing inside a
53
- dialog is not the dialog closing. `Escape` closes the innermost overlay and
54
- leaves the rest.
52
+ CSS anchor positioning is the platform's own way to do this. We do not use it
53
+ yet, because it is not available in every browser the rest of Helix supports.
54
+ See [Browser support](./browser-support).
55
55
 
56
- ## Focus
56
+ While an overlay is open, its position is recomputed on scroll and resize. The
57
+ recomputing stops when the overlay closes.
57
58
 
58
- A modal overlay traps `Tab` inside itself while open and returns focus to
59
- whatever opened it. `Dialog` and `Drawer` do this; `Popover` does it in its
60
- modal mode and not otherwise.
59
+ ## How overlays close
61
60
 
62
- **A tooltip never takes focus**, which is the one to remember: it is
63
- described-by rather than interactive, so it cannot contain a button. If you want
64
- something with a button in it, that is a `Popover`.
61
+ Light dismiss comes from the platform: click outside the overlay, or press
62
+ `Escape`. Helix makes light dismiss work correctly across shadow boundaries.
65
63
 
66
- ## Refusing to close
64
+ `document.activeElement` stops at the outermost host. Focus inside a shadow tree
65
+ is reported as the element that contains the tree. A simple check for whether
66
+ focus is still inside the overlay never matches, so the overlay would close as
67
+ soon as anything inside it is focused. Every way of closing an overlay in Helix
68
+ checks focus by walking through the shadow roots.
67
69
 
68
- The close is announced before it happens:
70
+ Nested overlays work because lifecycle events do not bubble.
71
+ `helix-before-open` and `helix-before-close` do not bubble and are not composed,
72
+ so a menu that closes inside a dialog does not close the dialog. `Escape` closes
73
+ the innermost overlay and leaves the others open.
74
+
75
+ ## Where focus goes
76
+
77
+ A modal overlay keeps `Tab` inside itself while it is open, and it returns focus
78
+ to the element that opened it. `Dialog` and `Drawer` always do this. `Popover`
79
+ does it only in its modal mode.
80
+
81
+ **A tooltip never takes focus.** A tooltip only describes a control, so it
82
+ cannot contain a button. For something with a button in it, use a `Popover`.
83
+
84
+ ## Keep an overlay open
85
+
86
+ An overlay sends `helix-before-close` before it closes. Cancel the event to keep
87
+ the overlay open:
69
88
 
70
89
  ```ts
71
90
  dialog.addEventListener('helix-before-close', (event) => {
@@ -75,20 +94,19 @@ dialog.addEventListener('helix-before-close', (event) => {
75
94
  })
76
95
  ```
77
96
 
78
- That covers `Escape`, the backdrop click and the close button together, because
79
- all three go through the same request. See [State and
80
- control](./state-and-control).
97
+ Canceling covers `Escape`, a backdrop click, and the close button, because all
98
+ three send the same request. See [State and control](./state-and-control).
81
99
 
82
100
  ## Entry and exit animation
83
101
 
84
- Overlays animate in with `@starting-style` and out with a transition on the
85
- `overlay` and `display` properties, which is what allows an exit animation on an
86
- element the browser is about to remove from the top layer. Every one answers
87
- `prefers-reduced-motion`.
102
+ Overlays animate in with `@starting-style`. They animate out with a transition
103
+ on the `overlay` and `display` properties, which allows an exit animation on an
104
+ element the browser is about to remove from the top layer. Every overlay
105
+ responds to `prefers-reduced-motion`.
88
106
 
89
- ## Which one to reach for
107
+ ## Choose an overlay
90
108
 
91
- | | |
109
+ | Component | Use it for |
92
110
  |---|---|
93
111
  | `Dialog` | A task that blocks the page |
94
112
  | `Drawer` | The same, from an edge, usually larger |
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: The three ways in past a shadow root, and which one to reach for.
2
+ description: Change a component's styles with tokens, per-component hooks, or parts, and choose which one to use.
3
3
  section: Theming
4
4
  title: Overriding a component
5
5
  ---
@@ -7,11 +7,10 @@ title: Overriding a component
7
7
  # Overriding a component
8
8
 
9
9
  Every Helix component draws inside a shadow root, so your stylesheet cannot
10
- reach in. That is the point: a rule you write can never accidentally break a
11
- component's internals, and a component can be rewritten without breaking your
12
- page.
10
+ reach the elements inside it. Your rules cannot break a component's internals
11
+ by accident, and we can rewrite a component without breaking your page.
13
12
 
14
- Three doors are open, and they are in the order you should try them.
13
+ You can change a component's styles in three ways. Try them in the order below.
15
14
 
16
15
  ## 1. Tokens
17
16
 
@@ -21,15 +20,17 @@ Three doors are open, and they are in the order you should try them.
21
20
  }
22
21
  ```
23
22
 
24
- If every card, dialog and button should be squarer, that is a token change and
25
- you are done. The seed rather than `--helix-radii-md`, because the four rungs
26
- are derived from it: `md` on its own is one step of four, and the buttons and
27
- dialogs are on a different one. See [Shape and borders](./shape-and-borders). Tokens cross shadow boundaries on their own, because custom
28
- properties inherit, so one declaration reaches every component at once.
23
+ To make every card, dialog, and button squarer, change a token. Set the seed.
24
+ The four radius steps, such as `--helix-radii-md`, are derived from it. `md` is
25
+ one step of the four, and buttons and dialogs use a different step. See
26
+ [Shape and borders](./shape-and-borders).
29
27
 
30
- Reach for this first. Most requests that arrive as "can I restyle the button"
31
- are really "our brand has less rounding than yours", and the token is both the
32
- smaller change and the one that stays consistent.
28
+ Custom properties inherit, so tokens cross shadow boundaries. One declaration
29
+ reaches every component at once.
30
+
31
+ Try tokens first. A request to restyle the button is often a brand difference,
32
+ such as less rounding. A token is the smaller change, and it keeps every
33
+ component consistent.
33
34
 
34
35
  See [Tokens](./tokens) and [Deriving a theme](./theming).
35
36
 
@@ -45,15 +46,19 @@ helix-accordion {
45
46
  }
46
47
  ```
47
48
 
48
- A hook is a custom property a component publishes and reads. It defaults to a
49
- semantic token, so leaving it alone gets you the themed value, and setting it
50
- changes that component and nothing else.
49
+ A hook is a custom property that a component publishes and reads. It defaults
50
+ to a semantic token, so a hook you leave unset uses the themed value. Setting a
51
+ hook changes that one component.
52
+
53
+ **Hooks are component API.** We check that they stay separate from tokens. A
54
+ hook is named after its component, such as `--card-padding` or
55
+ `--drawer-radius`. A token is named `--helix-*`. We also check each hook in both
56
+ directions:
51
57
 
52
- **They are API, not tokens**, and the distinction is enforced. A hook is named
53
- after the component (`--card-padding`, `--drawer-radius`) and a token is named
54
- `--helix-*`. Every documented hook is held to a rule that actually reads it, in
55
- both directions: declared and never read is a promise with nothing behind it,
56
- and read and never declared is a working feature nobody is told about.
58
+ - A rule in the component reads every documented hook. A hook that is declared
59
+ and never read would do nothing.
60
+ - Every hook that a rule reads is documented. Otherwise you would not know the
61
+ hook exists.
57
62
 
58
63
  Each component's page lists its hooks under Custom properties.
59
64
 
@@ -69,23 +74,23 @@ helix-dialog::part(dialog) {
69
74
  }
70
75
  ```
71
76
 
72
- `::part()` reaches a named element inside the shadow root and lets you write any
73
- declaration you like on it. It is the most powerful door and the one to try
74
- last, because it is the one that couples your stylesheet to a component's
75
- internal structure.
77
+ `::part()` selects a named element inside the shadow root, and you can write any
78
+ declaration on it. Parts give you the most control. Try them last, because they
79
+ tie your stylesheet to a component's internal structure.
76
80
 
77
- **A published part is a promise**, held both ways:
78
- a documented part that nothing renders, and a rendered part nobody was told
79
- about. The first is the worse of the two for you, because a `::part()` that
80
- resolves to nothing is valid CSS that matches nothing and reports nothing.
81
+ **Every documented part is rendered.** We check parts in both directions. A
82
+ documented part must be rendered, and a rendered part must be documented. A
83
+ documented part that nothing renders would be the worse failure for you. A
84
+ `::part()` that matches nothing is still valid CSS, so you get no error.
81
85
 
82
86
  Each component's page lists its parts.
83
87
 
84
- ### A composed component forwards what it renders
88
+ ### Parts of a component inside another component
85
89
 
86
- `::part()` reaches one shadow boundary and stops, so a component that renders
87
- another would hide that child's whole part surface. Every one of them forwards
88
- it instead, and the forwarded name is prefixed with the role the child plays:
90
+ `::part()` crosses only one shadow boundary. When a component renders another
91
+ component, the child's parts would be out of reach. Every such component
92
+ forwards its child's parts, and each forwarded name starts with the role the
93
+ child plays:
89
94
 
90
95
  ```css
91
96
  helix-date-input::part(picker-cell) { … }
@@ -93,34 +98,39 @@ helix-select::part(listbox-items) { … }
93
98
  helix-video::part(overflow-header) { … }
94
99
  ```
95
100
 
96
- `<helix-date-input>` renders a date picker as its `picker`, so the picker's
97
- `cell` is `picker-cell` here. Where the child's part is already the role, the
98
- role stands alone: the tooltip inside `<helix-copy-button>` is `tooltip`, and
99
- its arrow is `tooltip-arrow`.
100
-
101
- A part that changes with a state takes a second name while the state holds, and
102
- that name is forwarded too. A tooltip that landed below its button is also
103
- `tooltip-bottom`, and its arrow `tooltip-arrow-bottom`, which is how a sheet out
104
- here draws by a side it cannot otherwise see. The arrow already faces the button
105
- whichever side that is; the name is for what you draw on top.
106
-
107
- **The prefix is not decoration.** Two levels can agree on a word, and did:
108
- `Command` forwarded the listbox's `search-field` under a name it also rendered
109
- itself, so `::part(search-field)` matched two elements at once.
110
-
111
- This is checked in both directions. `helix component`'s `parts` holds a published part and a
112
- rendered one to each other, and `helix component`'s `exportparts` holds a composition to
113
- forwarding everything its child publishes, under one prefix, colliding with
114
- nothing. So every part the color picker publishes is reachable from inside a
101
+ `<helix-date-input>` renders a date picker in the `picker` role, so the picker's
102
+ `cell` part is `picker-cell`. When the child's part is the role itself, the
103
+ forwarded name is the role alone. The tooltip inside `<helix-copy-button>` is
104
+ `tooltip`, and its arrow is `tooltip-arrow`.
105
+
106
+ A part that changes with a state has a second name while the state is active,
107
+ and that name is forwarded too. A tooltip placed below its button is also
108
+ `tooltip-bottom`, and its arrow is also `tooltip-arrow-bottom`. Your stylesheet
109
+ can use these names to style each side, which it cannot detect any other way.
110
+ The arrow already faces the button on every side. Use the names for styles you
111
+ add on top.
112
+
113
+ **The prefix keeps part names unique.** A component and its child can use the
114
+ same word. This happened once: `Command` forwarded the listbox's `search-field`
115
+ under a name it also rendered itself, so `::part(search-field)` matched two
116
+ elements at once.
117
+
118
+ We check forwarding in two ways:
119
+
120
+ - `helix component`'s `parts` matches every published part with a rendered one,
121
+ and every rendered part with a published one.
122
+ - `helix component`'s `exportparts` requires a composed component to forward
123
+ every part its child publishes, under one prefix, with no collisions.
124
+
125
+ As a result, you can reach every part the color picker publishes from inside a
115
126
  color input, and every part the date picker publishes from inside a date input.
116
127
 
117
- If you still find a part you cannot reach, that is worth reporting rather than
118
- working around: the missing part is the bug.
128
+ If you find a part you cannot reach, report it to us. The missing part is a bug.
119
129
 
120
- ### Words that name a position mean one thing
130
+ ### Part names shared by every component
121
131
 
122
- A part that names a *role* rather than a thing draws from a fixed vocabulary, so
123
- you learn it once instead of per component:
132
+ Some parts name a role. Every component uses the same fixed set of role names,
133
+ so you learn each name once:
124
134
 
125
135
  | Part | What it is |
126
136
  |---|---|
@@ -131,32 +141,33 @@ you learn it once instead of per component:
131
141
  | `surface` | A floating panel drawn over the page. |
132
142
  | `viewport` | A scrollport the component reads the position of. |
133
143
 
134
- Anything naming a thing the component actually has, a `thumb`, a `swatch`, a
135
- `weekday`, is that component's own word and is on its page.
144
+ A part that names something specific to one component, such as `thumb`,
145
+ `swatch`, or `weekday`, is listed on that component's page.
136
146
 
137
147
  ## What not to do
138
148
 
139
- **Do not raise specificity to fight a component.** `helix-button.thing.thing` is
140
- still outside the shadow root, so it wins nothing it was not already winning.
141
- Selector weight is not what is stopping you.
149
+ **Do not raise specificity to override a component.** `helix-button.thing.thing`
150
+ is still outside the shadow root, so it reaches nothing that `helix-button` did
151
+ not already reach. The shadow boundary blocks your rule, and selector weight
152
+ cannot change that.
142
153
 
143
154
  **Do not restyle through `!important` on the host.** Some properties inherit
144
- into a shadow root and most do not, so this works for `color` and `font-family`
145
- and fails silently for everything else, which is a worse outcome than failing
146
- loudly.
155
+ into a shadow root and most do not. `!important` on the host works for `color`
156
+ and `font-family` and fails with no error for everything else. A silent failure
157
+ is harder to find than an error.
147
158
 
148
- **Do not fork the component to change a color.** The headless tier exists for
149
- the case where you want none of our styling, and it is a different decision from
150
- overriding one value. See [Tiers](./tiers).
159
+ **Do not fork the component to change a color.** The headless tier is for when
160
+ you want none of our styling. Changing one value does not need it. See
161
+ [Tiers](./tiers).
151
162
 
152
- ## When you want all of it
163
+ ## Replace all of the styling
153
164
 
154
165
  ```ts
155
166
  import '@fusion.dev/helix-headless'
156
167
  ```
157
168
 
158
- The headless tier is structure, accessibility, state and form participation with
159
- no CSS at all. You write the entire appearance. That is the honest answer when
160
- the design is genuinely not ours, and it is a supported tier rather than an
161
- escape hatch: `helix-headless-select` is a registered element, not an abstract
162
- class.
169
+ The headless tier provides structure, accessibility, state, and form
170
+ participation, with no CSS. You write the entire appearance. Use it when your
171
+ design is entirely different from ours. The headless tier is fully supported.
172
+ For example, `helix-headless-select` is a registered element that you can use
173
+ directly.