@fusion.dev/helix-docs 0.0.0-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/content/accessibility.mdx +117 -0
  3. package/content/angular.mdx +60 -0
  4. package/content/browser-support.mdx +83 -0
  5. package/content/building-on-the-headless-tier.mdx +143 -0
  6. package/content/charting.mdx +93 -0
  7. package/content/coding-agents.mdx +139 -0
  8. package/content/color-modes.mdx +87 -0
  9. package/content/color.mdx +92 -0
  10. package/content/composition.mdx +171 -0
  11. package/content/elevation-and-material.mdx +93 -0
  12. package/content/events.mdx +125 -0
  13. package/content/forms.mdx +170 -0
  14. package/content/iconography.mdx +80 -0
  15. package/content/installation.mdx +125 -0
  16. package/content/intent-reference.mdx +107 -0
  17. package/content/introduction.mdx +69 -0
  18. package/content/localization.mdx +95 -0
  19. package/content/metrics.mdx +399 -0
  20. package/content/motion.mdx +91 -0
  21. package/content/other-frameworks.mdx +70 -0
  22. package/content/overlays.mdx +102 -0
  23. package/content/overriding-a-component.mdx +160 -0
  24. package/content/react.mdx +144 -0
  25. package/content/recipes.mdx +131 -0
  26. package/content/routing.mdx +208 -0
  27. package/content/sandbox.mdx +76 -0
  28. package/content/server-rendering.mdx +103 -0
  29. package/content/shape-and-borders.mdx +80 -0
  30. package/content/size-and-density.mdx +86 -0
  31. package/content/space-and-size.mdx +89 -0
  32. package/content/state-and-control.mdx +132 -0
  33. package/content/surfaces-and-separation.mdx +99 -0
  34. package/content/svelte.mdx +45 -0
  35. package/content/theme-builder.mdx +66 -0
  36. package/content/theming.mdx +111 -0
  37. package/content/tiers.mdx +159 -0
  38. package/content/tokens.mdx +88 -0
  39. package/content/typography.mdx +117 -0
  40. package/content/using-helix-with-an-agent.mdx +231 -0
  41. package/content/using-the-elements.mdx +125 -0
  42. package/content/versioning.mdx +72 -0
  43. package/content/visual-axes.mdx +75 -0
  44. package/content/vue.mdx +80 -0
  45. package/content/without-a-bundler.mdx +119 -0
  46. package/dist/catalog.d.ts +186 -0
  47. package/dist/catalog.js +296 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +6 -0
  50. package/dist/naming.d.ts +14 -0
  51. package/dist/naming.js +28 -0
  52. package/dist/pages.generated.d.ts +7 -0
  53. package/dist/pages.generated.js +284 -0
  54. package/dist/reference.d.ts +105 -0
  55. package/dist/reference.js +181 -0
  56. package/dist/registry.d.ts +89 -0
  57. package/dist/registry.js +70 -0
  58. package/dist/sections.d.ts +44 -0
  59. package/dist/sections.js +36 -0
  60. package/dist/sources.generated.d.ts +6 -0
  61. package/dist/sources.generated.js +5158 -0
  62. package/package.json +46 -0
@@ -0,0 +1,170 @@
1
+ ---
2
+ description: Real form participation, labels that resolve across a shadow root, and the disabled trap.
3
+ section: Essentials
4
+ title: Forms and validation
5
+ ---
6
+
7
+ # Forms and validation
8
+
9
+ Twenty-two families are form-associated custom elements, so a `<form>` collects
10
+ them, resets them, restores them on back-navigation and validates them the way it
11
+ does a native control. Nothing about that is simulated.
12
+
13
+ ```html
14
+ <form>
15
+ <helix-input
16
+ label="Name"
17
+ name="name"
18
+ required
19
+ ></helix-input>
20
+ <helix-select
21
+ label="Plan"
22
+ name="plan"
23
+ >
24
+ <helix-option value="free">Free</helix-option>
25
+ <helix-option value="pro">Pro</helix-option>
26
+ </helix-select>
27
+ <helix-button type="submit">Save</helix-button>
28
+ </form>
29
+ ```
30
+
31
+ `new FormData(form)` has `name` and `plan` in it. So does a server reading the
32
+ POST body.
33
+
34
+ ## Every control carries its own label
35
+
36
+ ```html
37
+ <helix-input
38
+ hint="We only use this to sign you in."
39
+ label="Email"
40
+ name="email"
41
+ required
42
+ ></helix-input>
43
+ ```
44
+
45
+ There is no wrapping `Field` component, and that is not a style preference. **A
46
+ wrapper cannot name what it wraps.** Its label would live in one shadow root and
47
+ the control in another, and measured in Chrome 148: an `aria-labelledby` IDREF
48
+ cannot see across a shadow boundary, and `control.ariaLabelledByElements =
49
+ [labelInTheWrapper]` reads back as zero elements, the reference silently
50
+ dropped. The same assignment with a label in the control's own tree is kept.
51
+
52
+ That pattern works in React component libraries because they have no shadow
53
+ boundary. Bringing it here brings the ergonomics and none of the conditions that
54
+ made it work.
55
+
56
+ So every control that wants a label has the same three properties and renders
57
+ its own nodes:
58
+
59
+ | Property | For |
60
+ |---|---|
61
+ | `label` | The accessible name, and the fallback for the `label` slot |
62
+ | `hint` | Help under the control, while there is nothing wrong |
63
+ | `feedback` | What is wrong, in your words rather than the browser's |
64
+
65
+ Each is a slot fallback, so anything richer than a string replaces it:
66
+
67
+ ```html
68
+ <helix-input name="email">
69
+ <span slot="label">Email <helix-badge>required</helix-badge></span>
70
+ </helix-input>
71
+ ```
72
+
73
+ **A whitespace-only text node defeats a default slot's fallback**, which costs a
74
+ label and gives no warning. Measured in Chrome 148: a newline between the tags
75
+ is enough, so `<helix-checkbox label="Accept">` closed on the next line renders
76
+ no label at all. Named slots do not have this problem, because `slot="label"` is
77
+ an attribute and a text node has none. It is the platform's behavior rather
78
+ than ours, and it is true of every slot fallback in the library.
79
+
80
+ ## Validation
81
+
82
+ The platform's own validation works, because these are real form participants:
83
+
84
+ ```html
85
+ <helix-input
86
+ label="Email"
87
+ name="email"
88
+ required
89
+ type="email"
90
+ ></helix-input>
91
+ ```
92
+
93
+ Submitting an empty form blocks and points at the control. `checkValidity()`,
94
+ `reportValidity()` and `:invalid` behave.
95
+
96
+ **`feedback` overrides the message.** Left empty, the browser's own is shown,
97
+ which is localised and familiar; set, it says what you want in the words your
98
+ product uses:
99
+
100
+ ```html
101
+ <helix-input
102
+ feedback="We need this to send your receipt."
103
+ label="Email"
104
+ name="email"
105
+ required
106
+ ></helix-input>
107
+ ```
108
+
109
+ **A group is the participant, not its members.** `Radio.Group` and
110
+ `Toggle.Group` own the `name`, the `value` and the validity: a lone radio is not
111
+ a control at all, it is an answer to one. `Checkbox.Group` is the exception in
112
+ the other direction, since each box in it submits itself already, so the group
113
+ deliberately reports no value of its own.
114
+
115
+ ## The disabled trap
116
+
117
+ ```html
118
+ <fieldset disabled>
119
+ <helix-input
120
+ label="Name"
121
+ name="name"
122
+ ></helix-input>
123
+ </fieldset>
124
+ ```
125
+
126
+ That works, and it did not always. The platform tells a form-associated element
127
+ through `formDisabledCallback`, which nothing implemented, so a control inside a
128
+ disabled fieldset stayed fully typeable and announced itself as enabled.
129
+ Measured: a `helix-input` in one accepted text, reported `disabled` as false,
130
+ and carried no `aria-disabled`, while the form's entry list correctly left it
131
+ out. Interactive and unsubmittable is the worst of both.
132
+
133
+ **For your own CSS this means one rule.** Style disabled controls with
134
+ `:disabled`, never `[disabled]`:
135
+
136
+ ```css
137
+ /* Right: matches the attribute and the ancestor fieldset. */
138
+ helix-input:disabled { … }
139
+
140
+ /* Wrong: misses every control inside a disabled fieldset. */
141
+ helix-input[disabled] { … }
142
+ ```
143
+
144
+ The pseudo-class is true for a control carrying the attribute *and* for one
145
+ inside a disabled fieldset, which sets no attribute on anything. A component
146
+ styled with the attribute selector refuses interaction from the fieldset,
147
+ because the platform disables it either way, and paints itself as live. That
148
+ reads as a control that is simply broken, and it is invisible in every example
149
+ that does not happen to use a fieldset. The library holds itself to this, and
150
+ the same reasoning applies to yours.
151
+
152
+ `disabled` is mirrored to `aria-disabled` rather than removing the control from
153
+ the accessibility tree, so a screen reader can still announce that the control
154
+ exists and is currently off.
155
+
156
+ ## Seeding a form
157
+
158
+ A declared `value` or `checked` is a seed and the control goes on managing
159
+ itself, so a form needs nothing wired to work. `default-value` is what a reset
160
+ returns to, which is usually what a seed in a form means:
161
+
162
+ ```html
163
+ <helix-input
164
+ default-value="Ada"
165
+ label="Name"
166
+ name="name"
167
+ ></helix-input>
168
+ ```
169
+
170
+ See [State and control](./state-and-control).
@@ -0,0 +1,80 @@
1
+ ---
2
+ description: 103 glyphs, one geometry, and how to draw one that is not in the set.
3
+ section: Foundations
4
+ title: Iconography
5
+ ---
6
+
7
+ # Iconography
8
+
9
+ ```html
10
+ <helix-icon name="trash"></helix-icon>
11
+ ```
12
+
13
+ 103 glyphs, from Phosphor, drawn on a 256 box at stroke-width 16. That is
14
+ Phosphor's own geometry and it is kept rather than rescaled: normalizing to a 24
15
+ box would change the optical weight of every glyph and stop the set matching
16
+ anything upstream.
17
+
18
+ ## Size and color come from the context
19
+
20
+ An icon has no `size` and no `color` property. It is `1em` square and
21
+ `currentColor`, so it takes the size and color of the text it sits in:
22
+
23
+ ```html
24
+ <span data-tone="danger">
25
+ <helix-icon name="warning"></helix-icon> Careful
26
+ </span>
27
+ ```
28
+
29
+ Inside a control, `--helix-size-icon` is what decides, and it follows the
30
+ region's density along with everything else. That is why an icon in a small
31
+ button is smaller without anybody saying so.
32
+
33
+ ## Decorative or named
34
+
35
+ ```html
36
+ <!-- Decorative: the text beside it already says what this is. -->
37
+ <helix-button>
38
+ <helix-icon name="trash"></helix-icon>
39
+ Delete
40
+ </helix-button>
41
+
42
+ <!-- Named: there is no text, so the icon has to carry the name. -->
43
+ <helix-icon-button
44
+ label="Delete"
45
+ name="trash"
46
+ ></helix-icon-button>
47
+ ```
48
+
49
+ An icon with no `label` is hidden from assistive technology, which is right when
50
+ the words are beside it and wrong when they are not. `Icon.Button` takes a
51
+ `label` because it never has words beside it.
52
+
53
+ ## Importing a glyph directly
54
+
55
+ ```ts
56
+ import { trash } from '@fusion.dev/helix-icons'
57
+ ```
58
+
59
+ Each glyph is an exported string, so you can put one in your own markup, in a
60
+ canvas, or anywhere an element is not what you want. Every `name` the library
61
+ uses is held to the set of glyphs that actually exist, so a typo is a build
62
+ failure rather than an empty box.
63
+
64
+ ## Adding one
65
+
66
+ Drop the SVG into `helix/icons/raw` and run `bun run icons`. The set is
67
+ generated from those files, so adding a glyph is adding a file rather than
68
+ hand-transcribing a path into a TypeScript object.
69
+
70
+ Keep to the same geometry: 256 box, stroke-width 16, no fill. A glyph drawn at a
71
+ different weight is visible immediately when it sits beside one that was not.
72
+
73
+ ## Icons this set does not have
74
+
75
+ Product marks. Helix and Cortex have marks and they live in the icon package,
76
+ but a mark is not an icon: it identifies a product and does not mean an action.
77
+ Three earlier attempts at product marks were removed for being pictograms of a
78
+ metaphor standing beside a parent that is a geometric monogram, and the rule
79
+ that replaced them is that **a mark is one rounded body with material removed**.
80
+ If you are reaching for the icon set to draw a logo, that is the wrong drawer.
@@ -0,0 +1,125 @@
1
+ ---
2
+ description: What to install, the order the stylesheets go in, and how to check it worked.
3
+ order: 3
4
+ section: Getting started
5
+ title: Installation
6
+ ---
7
+
8
+ # Installation
9
+
10
+ ```sh
11
+ bun add @fusion.dev/helix
12
+ ```
13
+
14
+ `@fusion.dev/helix-styles` is not optional and not a peer dependency by accident. The
15
+ components reference semantic tokens and paint nothing without them: a Helix
16
+ page with the elements installed and the tokens missing renders unstyled boxes
17
+ rather than an unthemed version of itself.
18
+
19
+ ## The stylesheets go in this order
20
+
21
+ ```ts
22
+ import '@fusion.dev/helix/helix.css'
23
+ ```
24
+
25
+ One import, and it is four files in the order they have to be in:
26
+ `primitives.css`, `tokens.css`, `base.css`, `elements.css`. The order is
27
+ load-bearing and the failure is silent, which these instructions demonstrated
28
+ for longer than anyone would like, so it is stated once inside the package
29
+ rather than by every consumer.
30
+
31
+ It resolves `@fusion.dev/helix-styles` through this package's own dependency, so
32
+ installing `@fusion.dev/helix` is enough. A bare specifier in an `@import` is
33
+ resolved by a bundler and never by a browser, so a page serving CSS through a
34
+ `<link>` takes the four by path instead.
35
+
36
+ ### The four, for anyone substituting a layer
37
+
38
+ Each file keeps its own export, so a consumer bringing their own reset or their
39
+ own ramp imports the two they want and skips the bundle.
40
+
41
+ - **`primitives.css`** is the raw ramp the semantic layer is derived from, and
42
+ it is where the seven `--helix-seed-*` knobs are read. Nothing in your own
43
+ code should name a primitive, and leaving the file out is not a way to
44
+ enforce that: `tokens.css` refers to one 235 times, so without it every
45
+ semantic token resolves to nothing at all. Measured on a page missing it,
46
+ `--helix-color-background` computes to `rgba(0, 0, 0, 0)`, and so does every
47
+ other color. It is also the file [theming](theming.mdx) depends on, since a
48
+ `--helix-seed-brand` nobody reads moves nothing. **This is the one that was
49
+ missing from this page**, which is why the bundle exists.
50
+ - **`tokens.css`** declares the semantic layer, 177 custom properties in 15
51
+ categories, plus `color-scheme: light dark` on `:root`. Everything below reads
52
+ from it, and it reads primitives.
53
+ - **`base.css`** is the element reset, the type scale on bare `h1` through
54
+ `h6`, and the recipes for markup this library does not render. It reads
55
+ tokens.
56
+
57
+ **The last one is the one people leave out**, because everything works without
58
+ it right up until the page is server-rendered: an element that has not upgraded
59
+ yet has no shadow root and no styles of its own, so the markup flashes unstyled
60
+ and then snaps into place. `elements.css` is what makes that interval look like
61
+ the finished page.
62
+
63
+ Put them in a stylesheet you control rather than deep inside a component, so the
64
+ order is visible in one place and stays that way.
65
+
66
+ ## Register the elements
67
+
68
+ ```ts
69
+ import '@fusion.dev/helix'
70
+ ```
71
+
72
+ A side-effect import. It calls `customElements.define` for every element, once,
73
+ and is safe to run more than once: the helper behind it checks the registry
74
+ first, because a second `define` for the same tag throws and takes the rest of
75
+ the module with it.
76
+
77
+ You can register a subset instead, from a subpath:
78
+
79
+ ```ts
80
+ import '@fusion.dev/helix/button'
81
+ import '@fusion.dev/helix/dialog'
82
+ ```
83
+
84
+ Worth doing when you use a handful of components. Not worth doing in the belief
85
+ that the full import ships everything to the browser: the package is
86
+ side-effectful by design, so a bundler keeps what you import either way, and the
87
+ subpaths are about being explicit rather than about size.
88
+
89
+ ## Check it worked
90
+
91
+ Render one element and ask the browser whether it upgraded:
92
+
93
+ ```ts
94
+ await customElements.whenDefined('helix-button')
95
+ console.log(document.querySelector('helix-button')?.shadowRoot !== null)
96
+ ```
97
+
98
+ `true` means the definition arrived and the element built its shadow root.
99
+ `false`, or a promise that never settles, means the registration import did not
100
+ run: usually a `'use client'` boundary the import ended up outside of, or a
101
+ subpath import for a component you did not end up using.
102
+
103
+ **A styling problem looks nothing like that.** If the element upgraded and the
104
+ page still looks wrong, the stylesheets are the thing to check, in the order
105
+ above. The tell is which layer is missing: no color at all is `tokens.css`,
106
+ unstyled prose and bare headings is `base.css`, and components that look right
107
+ once JavaScript runs but flash unstyled before it is `elements.css`.
108
+
109
+ ## Your framework
110
+
111
+ The elements are custom elements and every framework can render one. What
112
+ differs is how a framework passes a non-string value, how it binds an event, and
113
+ whether it needs telling that an unknown tag is deliberate.
114
+
115
+ | | |
116
+ |---|---|
117
+ | [React](./react) | A runtime bridge ships for it, plus `jsx.d.ts` |
118
+ | [Vue](./vue) | Ships `vue.d.ts`, needs `compilerOptions.isCustomElement` |
119
+ | [Svelte](./svelte) | Ships `svelte.d.ts`, and needs nothing else |
120
+ | [Angular](./angular) | Needs `CUSTOM_ELEMENTS_SCHEMA` |
121
+ | [Other frameworks](./other-frameworks) | Solid, Qwik, Lit, and the three rules they all reduce to |
122
+ | [Without a bundler](./without-a-bundler) | An import map, or the stylesheets alone |
123
+
124
+ A framework gets its own page when Helix ships typings for it, or when it
125
+ refuses to compile without configuration. Everything else works with no setup.
@@ -0,0 +1,107 @@
1
+ ---
2
+ description: Every input the theme engine takes, and what each one decides.
3
+ section: Theming
4
+ title: Intent reference
5
+ ---
6
+
7
+ # Intent reference
8
+
9
+ [Deriving a theme](./theming) is the argument. This is the surface: everything
10
+ `ThemeIntent` accepts, and what changes when you set it.
11
+
12
+ ```ts
13
+ import { derive } from '@fusion.dev/helix-theme'
14
+
15
+ const theme = derive({ brand: '#2563eb' })
16
+ ```
17
+
18
+ **`brand` is the only required input.** Everything else has a default that is
19
+ derived from it or from the design language, and a theme that sets one field is
20
+ a valid theme.
21
+
22
+ ## Color
23
+
24
+ | Field | Decides |
25
+ |---|---|
26
+ | `brand` | The one color most consumers care about. Required |
27
+ | `accent` | The second emphasis. Defaults to the complement, `{ from: 'brand', rotate: 180 }` |
28
+ | `neutral` | The gray ramp, which can be tinted toward the brand |
29
+ | `status` | `info`, `success`, `warning`, `danger`. A color pins one; an object nudges its hue within the meaning it already carries |
30
+ | `chroma` | How saturated the palette is, independent of hue |
31
+ | `scheme` | `'light'` or `'dark'`, for a theme that is only one |
32
+ | `tokens` | Literal overrides, by token name, for the cases nothing else reaches |
33
+
34
+ **`accent` may be a relation and `status` may not**, and the asymmetry is
35
+ deliberate: a `danger` that rotates with the brand is a warning a reader has to
36
+ re-learn per product.
37
+
38
+ **`chroma` is the axis a brand's feel lives on.** Monotone, muted, restrained
39
+ and vivid are all chroma statements and none of them is a hue. `ceiling` clamps,
40
+ so 0 is monotone and roughly 0.10 is muted; `scale` multiplies what the brand
41
+ supplies. Both apply to every derived color including the brand itself, because
42
+ a monotone theme is one where the brand is gray too. Never applied to a value
43
+ you pinned: a pinned color is a fact.
44
+
45
+ ## The guarantee
46
+
47
+ | Field | Decides |
48
+ |---|---|
49
+ | `contrastTarget` | Target contrast for body text. 4.5 is WCAG AA, 7 is AAA |
50
+ | `pairings` | Extra foreground and background pairs to solve for, beyond the model's own |
51
+
52
+ 4.5 is the default because the European Accessibility Act points at WCAG 2.1 AA,
53
+ so it is the figure with legal weight.
54
+
55
+ ## Shape and space
56
+
57
+ | Field | Decides |
58
+ |---|---|
59
+ | `radius` | Corner rounding across the system |
60
+ | `density` | Scales spacing and control heights. 1 is default, 0.875 is compact |
61
+ | `separation` | Whether a surface is told apart by an edge or by a lift |
62
+
63
+ `separation` is the policy pair described in [Surfaces and
64
+ separation](./surfaces-and-separation).
65
+
66
+ ## Type and motion
67
+
68
+ | Field | Decides |
69
+ |---|---|
70
+ | `font` | The families, and the half of the type system that is a scale |
71
+ | `motion` | `character` and `scale` |
72
+
73
+ **`motion.character` sets the curve every component reaches for and carries a
74
+ duration multiplier with it**, because the two are one decision: a spring over
75
+ 120ms is a twitch, and a soft curve over 120ms is a fade. `scale` multiplies on
76
+ top for a theme that wants the character and not the pace.
77
+
78
+ `instant` is not scaled. It is 0 and means "do not animate", which is a
79
+ statement rather than a duration, and it emits no curves at all: three easings
80
+ for transitions of zero length is a contradiction.
81
+
82
+ `precise` is the one character that is not physical. The other three accelerate,
83
+ settle or overshoot; linear reads as moved by a mechanism, and it is the only
84
+ curve legible on a short travel, because constancy shows in the timing where an
85
+ overshoot shows in the distance.
86
+
87
+ ## Material
88
+
89
+ | Field | Decides |
90
+ |---|---|
91
+ | `material` | `blur` in pixels and `opacity` from 0 to 1 |
92
+
93
+ **This is the one choice that can invalidate the contrast guarantee.** Contrast
94
+ is measured against a known ground, and a translucent surface does not have one,
95
+ so a pair over it comes back `unverifiable` rather than measured. That is
96
+ reported rather than hidden: a theme with heavy material is making a trade and
97
+ should be able to see it.
98
+
99
+ ## Output
100
+
101
+ | Field | Decides |
102
+ |---|---|
103
+ | `namespace` | The token prefix. Defaults to `helix` |
104
+
105
+ `namespace` is for a product shipping its own token layer built on this engine.
106
+ Everything else in the library reads `--helix-*`, so changing it is a decision
107
+ about your own surface rather than about Helix's.
@@ -0,0 +1,69 @@
1
+ ---
2
+ description: What Helix is, and the four packages it stacks into.
3
+ order: 1
4
+ section: Getting started
5
+ title: Introduction
6
+ ---
7
+
8
+ # Helix
9
+
10
+ A design system of web components: tokens, a headless component tier, a styled
11
+ tier, and the framework typings generated from both. It works in any framework,
12
+ or none, because the components are custom elements.
13
+
14
+ Two things make it different from a component library.
15
+
16
+ **Theming is derivation, not override.** You supply a handful of intent inputs
17
+ and the system computes a complete token set whose every foreground and
18
+ background pair meets its contrast target by construction. See
19
+ [Theming](./theming).
20
+
21
+ **AI is a first-class consumer.** Components are described by a custom elements
22
+ manifest with typed enums, documented parts, and generated React prop names. An
23
+ agent can write correct Helix code by reading types alone. That is why
24
+ conventions like inline literal unions and documented CSS parts are enforced at
25
+ analysis time rather than left to review.
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ bun add @fusion.dev/helix @fusion.dev/helix-styles
31
+ ```
32
+
33
+ Three imports at your app root, in this order, then the elements are usable
34
+ anywhere:
35
+
36
+ ```ts
37
+ import '@fusion.dev/helix/helix.css'
38
+ import '@fusion.dev/helix'
39
+ ```
40
+
41
+ ```html
42
+ <helix-accordion separation="outlined">
43
+ <helix-accordion-item default-open>
44
+ <span slot="summary">What is Helix?</span>
45
+ <p>A design system that derives its own accessible palette.</p>
46
+ </helix-accordion-item>
47
+ </helix-accordion>
48
+ ```
49
+
50
+ [Installation](./installation) has the order those imports are in and why, and a
51
+ page for each framework.
52
+
53
+ ## Packages
54
+
55
+ | Package | Use when |
56
+ | --- | --- |
57
+ | `@fusion.dev/helix-styles` | Always. Tokens and the base layer |
58
+ | `@fusion.dev/helix` | You want Helix components as-is |
59
+ | `@fusion.dev/helix-headless` | You want the behavior and none of the styling |
60
+ | `@fusion.dev/helix-theme` | You are generating or validating a theme |
61
+ | `@fusion.dev/helix-icons` | You are drawing an icon outside a component |
62
+ | `@fusion.dev/helix-core` | You are building a new Helix component |
63
+
64
+ **There is no React package.** The elements are custom elements and React
65
+ renders them directly; what ships for React is a `jsx.d.ts` that types every tag
66
+ and a runtime bridge in `@fusion.dev/helix-core` that repairs React's hydration of
67
+ custom events. [React](./react) has both.
68
+
69
+ See [Tiers](./tiers) for how the packages relate and which one to reach for.
@@ -0,0 +1,95 @@
1
+ ---
2
+ description: Locale-aware controls, translatable strings, and a layout that already flips.
3
+ section: Essentials
4
+ title: Localization and RTL
5
+ ---
6
+
7
+ # Localization and RTL
8
+
9
+ ## Right-to-left already works
10
+
11
+ Set `dir` and the layout flips:
12
+
13
+ ```html
14
+ <html
15
+ dir="rtl"
16
+ lang="ar"
17
+ >
18
+ ```
19
+
20
+ Every component is written in logical properties, `inline-start` rather than
21
+ `left`, `margin-block` rather than `margin-top`. Sixty-seven stylesheets name a
22
+ direction, and **not one of them names a physical side**: there is no
23
+ `margin-left`, no `padding-right` and no `inset-top` anywhere in the library.
24
+ Nothing infers the direction for you and nothing needs to: the document says it
25
+ and the components follow.
26
+
27
+ The same is true of anything you write on top. Use logical properties and your
28
+ layout flips with ours; use `left` and `right` and it will not.
29
+
30
+ ## Locale-aware controls
31
+
32
+ Six families format numbers or dates and take a `locale`:
33
+
34
+ | | |
35
+ |---|---|
36
+ | `Date Input`, `Date Picker` | Date formats and first day of week |
37
+ | `Time Input`, `Time Picker` | 12 or 24 hour, and separators |
38
+ | `Pagination` | Grouped thousands in page numbers |
39
+ | `File Input` | File sizes |
40
+
41
+ ```html
42
+ <helix-pagination
43
+ locale="de-DE"
44
+ pages="1200"
45
+ ></helix-pagination>
46
+ ```
47
+
48
+ Left unset, they use the browser's own locale, which is usually right. Set it
49
+ when your application's language is a choice the reader made rather than the one
50
+ their operating system is in.
51
+
52
+ Formatting goes through `Intl`, so there is no locale data bundled and nothing
53
+ to keep up to date.
54
+
55
+ ## Translatable strings
56
+
57
+ Seventy properties across the headless tier are strings a reader sees, and every
58
+ one is a property rather than a hardcoded word. The ones that combine
59
+ values are format templates:
60
+
61
+ ```html
62
+ <helix-pagination-summary
63
+ format="{from} bis {to} von {total}"
64
+ locale="de-DE"
65
+ page="2"
66
+ page-size="10"
67
+ total="1500"
68
+ ></helix-pagination-summary>
69
+ ```
70
+
71
+ The placeholders are named rather than positional, so a translation can reorder
72
+ them, which some languages require and a positional format cannot express.
73
+
74
+ **Pass these from your own translation system.** Helix ships English defaults
75
+ and no translation machinery: a design system that owned the string catalog
76
+ would be a second one to keep in step with yours.
77
+
78
+ ## What to check when adding a language
79
+
80
+ - **Long strings.** German and Finnish will find every control that assumed a
81
+ short label. Controls take their width from the layout rather than carrying
82
+ one, which helps, but a toolbar that fitted six buttons may now fit four.
83
+ - **The `lang` attribute, not just the strings.** Hyphenation, quotation marks
84
+ and font fallback all key off it.
85
+ - **Numerals.** `Intl` handles the digits; a hardcoded `%` or a currency symbol
86
+ in your own markup will not move to the right side.
87
+ - **Icons that point.** A chevron meaning "forward" is mirrored by the logical
88
+ properties; one baked into an SVG path is not.
89
+
90
+ ## What is not covered
91
+
92
+ There is no message catalog, no pluralization helper and no date-range
93
+ formatter beyond what the controls do for themselves. Those belong to your
94
+ application's i18n layer, and every one of these properties is designed to be
95
+ fed from it.