@fusion.dev/helix-docs 0.104.0 → 0.105.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.
- package/content/accessibility.mdx +92 -71
- package/content/angular.mdx +26 -24
- package/content/browser-support.mdx +61 -58
- package/content/building-on-the-headless-tier.mdx +100 -74
- package/content/charting.mdx +98 -79
- package/content/coding-agents.mdx +87 -69
- package/content/color-modes.mdx +40 -37
- package/content/color.mdx +67 -54
- package/content/composition.mdx +85 -83
- package/content/elevation-and-material.mdx +32 -29
- package/content/events.mdx +77 -65
- package/content/forms.mdx +69 -58
- package/content/iconography.mdx +75 -71
- package/content/installation.mdx +77 -68
- package/content/intent-reference.mdx +59 -54
- package/content/introduction.mdx +25 -23
- package/content/localization.mdx +122 -82
- package/content/maps.mdx +222 -0
- package/content/metrics.mdx +294 -297
- package/content/motion.mdx +44 -36
- package/content/other-frameworks.mdx +33 -31
- package/content/overlays.mdx +72 -54
- package/content/overriding-a-component.mdx +85 -74
- package/content/react.mdx +72 -64
- package/content/recipes.mdx +64 -47
- package/content/routing.mdx +100 -93
- package/content/sandbox.mdx +39 -39
- package/content/server-rendering.mdx +95 -84
- package/content/shape-and-borders.mdx +44 -37
- package/content/size-and-density.mdx +51 -46
- package/content/space-and-size.mdx +41 -34
- package/content/state-and-control.mdx +63 -51
- package/content/surfaces-and-separation.mdx +55 -53
- package/content/svelte.mdx +20 -19
- package/content/theme-builder.mdx +42 -31
- package/content/theming.mdx +58 -51
- package/content/tiers.mdx +69 -61
- package/content/tokens.mdx +72 -53
- package/content/typography.mdx +47 -47
- package/content/using-helix-with-an-agent.mdx +147 -115
- package/content/using-the-elements.mdx +55 -50
- package/content/versioning.mdx +42 -35
- package/content/visual-axes.mdx +168 -146
- package/content/vue.mdx +24 -24
- package/content/without-a-bundler.mdx +40 -23
- package/dist/pages.generated.js +49 -43
- package/dist/sources.generated.js +3337 -2680
- package/package.json +3 -3
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: How components render on a server with no DOM, and what waits for the browser.
|
|
3
3
|
section: Essentials
|
|
4
4
|
title: Server rendering
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Server rendering
|
|
8
8
|
|
|
9
|
-
Every component renders on a server with no DOM. If you
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
rules below are the whole contract.
|
|
9
|
+
Every component renders on a server with no DOM. If you only use Helix
|
|
10
|
+
components, they work in your server components with no configuration. If you
|
|
11
|
+
extend a component, follow the rules below. They are the complete set.
|
|
13
12
|
|
|
14
|
-
##
|
|
13
|
+
## The server has no DOM
|
|
15
14
|
|
|
16
|
-
Lit's element shim
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
Lit's element shim has attributes and no element tree. `querySelector` and
|
|
16
|
+
`this.children` are undefined, and any code that walks the DOM throws during
|
|
17
|
+
rendering.
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
We found these two examples in Helix the first time each component got a server
|
|
20
|
+
spec:
|
|
22
21
|
|
|
23
22
|
```ts
|
|
24
23
|
// Select. `display` is read during render and reached the render root.
|
|
@@ -32,70 +31,71 @@ get hasSubmenu(): boolean {
|
|
|
32
31
|
}
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
Before those two guards, neither component could
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
Before we added those two guards, neither component could render on a server.
|
|
35
|
+
Both worked in every browser, because a browser always has a render root and an
|
|
36
|
+
element tree.
|
|
38
37
|
|
|
39
|
-
**
|
|
40
|
-
|
|
41
|
-
none
|
|
42
|
-
render already has the
|
|
43
|
-
|
|
38
|
+
**Read the element tree through `this.knownChildren`**, in `willUpdate` or in a
|
|
39
|
+
getter that the render calls. On a client it holds every child, and on a server
|
|
40
|
+
it holds none. The server renders the "not known yet" state, and the client's
|
|
41
|
+
first render already has the children. If you read the tree after the render,
|
|
42
|
+
every client renders twice, and Lit's dev build warns about that.
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
`this.childrenKnown` is false on a server, where the children cannot be read. A
|
|
45
|
+
list on a server therefore does not render its "Nothing to show" message over
|
|
46
|
+
items it cannot see.
|
|
47
47
|
|
|
48
48
|
## Declarative shadow DOM
|
|
49
49
|
|
|
50
|
-
If you render with Lit SSR,
|
|
51
|
-
hydration support before the components:
|
|
50
|
+
If you render with Lit SSR, the declarative shadow DOM it writes hydrates in the
|
|
51
|
+
browser. Load Lit's hydration support before the components:
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
54
|
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
55
55
|
import '@fusion.dev/helix'
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Hydration assumes the markup already
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
server
|
|
62
|
-
|
|
58
|
+
Hydration assumes the markup already matches the client's first render. If a
|
|
59
|
+
value differs, hydration does not write it, and the page keeps the server's
|
|
60
|
+
value. If a template differs, hydration throws. While hydrating, a component
|
|
61
|
+
renders exactly what the server rendered. Anything it can read only in a browser
|
|
62
|
+
waits for the next update:
|
|
63
63
|
|
|
64
64
|
- `this.hydrating` is true for the render that hydrates.
|
|
65
65
|
- `this.knownChildren` is empty during it, as on the server.
|
|
66
|
-
- `this.finishHydration()`, called from `firstUpdated`,
|
|
67
|
-
reads what was skipped.
|
|
66
|
+
- `this.finishHydration()`, called from `firstUpdated`, requests the update
|
|
67
|
+
that reads what was skipped.
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
The browser does two things before any script can listen. Components handle
|
|
70
|
+
them this way:
|
|
71
71
|
|
|
72
72
|
- **A declarative shadow root reports its slots as it is parsed.** A component
|
|
73
|
-
|
|
74
|
-
first and uses `slotchange` for later changes.
|
|
75
|
-
- **The parser fetches images.** A `load` or `error` can fire before
|
|
76
|
-
|
|
77
|
-
the event a task later.
|
|
73
|
+
does not rely on `slotchange` alone to find what is slotted. It reads its
|
|
74
|
+
children first and uses `slotchange` for later changes.
|
|
75
|
+
- **The parser fetches images.** A `load` or `error` event can fire before
|
|
76
|
+
hydration. A component checks the state of the image (`this.serverRendered`)
|
|
77
|
+
and dispatches the event again a task later.
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
(`this.assignsByName`)
|
|
82
|
-
|
|
79
|
+
HTML cannot declare manual slot assignment. A component that assigns its slots
|
|
80
|
+
by hand therefore gets a root that can assign only by name. On such a root
|
|
81
|
+
(`this.assignsByName`), and only there, the component writes `slot` onto its
|
|
82
|
+
children with `this.route()`.
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
Run your page's own scripts after the definitions load. Hydration expects that
|
|
85
|
+
order. A property you set before hydration is one the server did not render
|
|
86
|
+
with.
|
|
87
87
|
|
|
88
88
|
`bun run check:spec:dsd` renders every example with Lit SSR, hydrates it in
|
|
89
89
|
Chromium, and compares it with the same example rendered on a client.
|
|
90
90
|
|
|
91
|
-
React server components
|
|
92
|
-
DOM APIs, and
|
|
93
|
-
[React](./react).
|
|
91
|
+
React server components do not use declarative shadow DOM. Their output is
|
|
92
|
+
replayed through DOM APIs, and declarative shadow DOM does not activate for
|
|
93
|
+
nodes that a script inserts. See [React](./react).
|
|
94
94
|
|
|
95
95
|
## Never initialize state in `connectedCallback`
|
|
96
96
|
|
|
97
|
-
SSR does not call
|
|
98
|
-
|
|
97
|
+
SSR does not call `connectedCallback`. Derive state from properties, or set
|
|
98
|
+
initial state in `willUpdate`, which SSR calls.
|
|
99
99
|
|
|
100
100
|
## Guard generated ids
|
|
101
101
|
|
|
@@ -105,50 +105,61 @@ if (!this.id) {
|
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
The server writes the id into the HTML
|
|
109
|
-
|
|
110
|
-
association breaks on hydration.
|
|
108
|
+
The server writes the id into the HTML. On the client, the id is already
|
|
109
|
+
present, so the component does not generate a new one, and both ids match.
|
|
110
|
+
Without the guard, the ids differ, and the association breaks on hydration.
|
|
111
111
|
|
|
112
|
-
##
|
|
112
|
+
## Put semantics on the host and presentation in the shadow root
|
|
113
113
|
|
|
114
114
|
IDREFs do not cross shadow boundaries, so `aria-labelledby` inside a shadow root
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
115
|
+
cannot resolve to a light-DOM id. Use `ElementInternals`. It writes no
|
|
116
|
+
attributes, so the host stays exactly as you wrote it, and hydration finds no
|
|
117
|
+
difference.
|
|
118
|
+
|
|
119
|
+
Labels have the same problem. `<label for>` cannot reach into a shadow root
|
|
120
|
+
either. For that reason:
|
|
121
|
+
|
|
122
|
+
- A field's label is rendered *inside* the control, and no wrapping element
|
|
123
|
+
renders it.
|
|
124
|
+
- A wrapping "form field" component can own the label only by passing it down
|
|
125
|
+
as a property.
|
|
126
|
+
- `aria-label` on a host has to be forwarded from where you wrote it.
|
|
127
|
+
|
|
128
|
+
See [Composition](./composition).
|
|
129
|
+
|
|
130
|
+
Where an id cannot reach, use an element reference. Element references resolve
|
|
131
|
+
outward, so an element in a shadow root can point at an element in the light
|
|
132
|
+
DOM. `Listbox` uses one for `aria-activedescendant`, because its options are
|
|
133
|
+
your elements and the element that takes focus is in its shadow root. Test for
|
|
134
|
+
support before you use element references. Assigning to a property that an
|
|
135
|
+
engine does not implement does nothing and throws no error.
|
|
136
|
+
|
|
137
|
+
## A child dispatches a ready event to its parent
|
|
134
138
|
|
|
135
139
|
A parent cannot poll for a child that has not upgraded yet. The child dispatches
|
|
136
|
-
a bubbling ready event and the parent listens.
|
|
140
|
+
a bubbling ready event, and the parent listens for it.
|
|
137
141
|
|
|
138
|
-
##
|
|
142
|
+
## Write a server spec
|
|
139
143
|
|
|
140
|
-
Every component has a `*.ssr.spec.ts
|
|
141
|
-
|
|
144
|
+
Every component has a `*.ssr.spec.ts` file. Breaking one rule for these specs
|
|
145
|
+
has produced a false pass twice:
|
|
142
146
|
|
|
143
147
|
**Never assert structure against the raw SSR string.** Declarative shadow DOM
|
|
144
|
-
inlines the component's stylesheet into the
|
|
145
|
-
stylesheet names every part, host attribute and token the component uses.
|
|
146
|
-
`toContain('part="media"')`
|
|
147
|
-
`not.toContain('data-scrolled')`
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Use
|
|
151
|
-
|
|
148
|
+
inlines the component's stylesheet into the markup you assert against. That
|
|
149
|
+
stylesheet names every part, host attribute, and token the component uses.
|
|
150
|
+
`toContain('part="media"')` can pass because of a *selector*, and
|
|
151
|
+
`not.toContain('data-scrolled')` can fail because of a `:host([data-scrolled])`
|
|
152
|
+
*rule*. Neither result tells you what rendered.
|
|
153
|
+
|
|
154
|
+
Use these helpers:
|
|
155
|
+
|
|
156
|
+
- `withoutStyles`
|
|
157
|
+
- `hasPart`
|
|
158
|
+
- `partTag`
|
|
159
|
+
- `hostTag`
|
|
160
|
+
|
|
161
|
+
Match attribute bindings with a pattern, because they render across several
|
|
162
|
+
lines:
|
|
152
163
|
|
|
153
164
|
```ts
|
|
154
165
|
expect(markup).toMatch(/<a\s+part="button"/) // ✅
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: The corner radius and border width tokens, and how a theme changes them.
|
|
3
3
|
section: Foundations
|
|
4
4
|
title: Shape and borders
|
|
5
5
|
---
|
|
@@ -20,12 +20,11 @@ title: Shape and borders
|
|
|
20
20
|
|
|
21
21
|
[//]: # (/tokens)
|
|
22
22
|
|
|
23
|
-
`full` is `9999px
|
|
24
|
-
|
|
25
|
-
what you wanted.
|
|
23
|
+
`full` is `9999px`, which gives a pill shape on an element that is not square.
|
|
24
|
+
A percentage radius such as `50%` gives an ellipse on a wide element.
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
Changing the rounding is the quickest way to change how your interface looks.
|
|
27
|
+
You change it with one token, the seed:
|
|
29
28
|
|
|
30
29
|
```css
|
|
31
30
|
:root {
|
|
@@ -33,25 +32,32 @@ token, but it is the **seed** rather than one of the four above:
|
|
|
33
32
|
}
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
`md
|
|
38
|
-
engine
|
|
39
|
-
theme](./theming).
|
|
35
|
+
This makes every card, dialog, input, and button squarer at once, because `sm`,
|
|
36
|
+
`md`, and `lg` are all computed from the seed. With this seed, they become 1px,
|
|
37
|
+
2px, and 3px. The theme engine has a `radius` intent input for the same purpose.
|
|
38
|
+
See [Deriving a theme](./theming).
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
system.
|
|
40
|
+
Setting `--helix-radii-md` changes only what uses `md`. Buttons, inputs, and
|
|
41
|
+
dialogs use `sm`, and cards use `lg`, so none of them change. Each step is
|
|
42
|
+
computed from the seed on its own, so overriding one step changes only that
|
|
43
|
+
step. To change the rounding everywhere, set the seed.
|
|
46
44
|
|
|
47
|
-
**Every floating panel
|
|
48
|
-
tooltip, a toast, and the panel any field opens under itself: all the same
|
|
49
|
-
corner, whatever the panel's size. A radius is read against the corner beside it
|
|
50
|
-
rather than against the area behind it.
|
|
45
|
+
**Every floating panel uses `sm`.** This includes:
|
|
51
46
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
- dialogs
|
|
48
|
+
- drawers
|
|
49
|
+
- menus
|
|
50
|
+
- popovers
|
|
51
|
+
- tooltips
|
|
52
|
+
- toasts
|
|
53
|
+
- the panel a field opens below itself
|
|
54
|
+
|
|
55
|
+
They all have the same corner, whatever their size. You compare a radius with
|
|
56
|
+
the corners next to it, so the size of the panel does not matter.
|
|
57
|
+
|
|
58
|
+
Circles do not use these tokens. An avatar, a loader, and a radio are circles by
|
|
59
|
+
design, so they set `50%` directly. A theme that squares off everything else
|
|
60
|
+
does not change them.
|
|
55
61
|
|
|
56
62
|
## Border widths
|
|
57
63
|
|
|
@@ -66,26 +72,27 @@ directly and are unaffected by a theme that squares everything else off.
|
|
|
66
72
|
|
|
67
73
|
[//]: # (/tokens)
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
You pick from the first two. The last two are policy tokens, described in
|
|
76
|
+
[Surfaces and separation](./surfaces-and-separation). A theme sets them to zero
|
|
77
|
+
to go shadow-first, or to a width to go border-first.
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
other.
|
|
79
|
+
For this reason, each component reads its border width from its own hook. A
|
|
80
|
+
theme can recolor a width written inline but cannot remove it. A theme also
|
|
81
|
+
cannot add a border back to a component that sets `border: none`. In both
|
|
82
|
+
cases, the surface can be themed in one direction only.
|
|
78
83
|
|
|
79
|
-
##
|
|
84
|
+
## Corners in a group of controls
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
Controls next to each other in a group have flat corners where they meet, so a
|
|
87
|
+
button group looks like one shape:
|
|
83
88
|
|
|
84
89
|
```css
|
|
85
90
|
[data-button]:not(:first-child) { border-start-start-radius: 0; }
|
|
86
91
|
```
|
|
87
92
|
|
|
88
|
-
The base layer does this for the recipes, and
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
The base layer does this for the recipes, and each component does it for
|
|
94
|
+
itself. If you build your own group:
|
|
95
|
+
|
|
96
|
+
- Flatten the inner corners, and keep the outer corners rounded.
|
|
97
|
+
- Use `border-start-start-radius`, which follows the writing direction.
|
|
98
|
+
`border-top-left-radius` does not.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Controls take their height from the density of the region they are in. Set the density once on the region.
|
|
3
3
|
section: Theming
|
|
4
4
|
title: Size and density
|
|
5
5
|
---
|
|
@@ -17,47 +17,46 @@ title: Size and density
|
|
|
17
17
|
</helix-flex>
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
looks like a restriction and is not one.
|
|
20
|
+
Buttons, inputs, selects, and checkboxes have no `size` prop.
|
|
22
21
|
|
|
23
|
-
##
|
|
22
|
+
## What size is used for
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
matters" far louder than eight more pixels ever will.
|
|
24
|
+
Size does three jobs:
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
- Emphasis shows which control matters most. The `emphasis` axis sets it. A
|
|
27
|
+
filled primary button marks the important action much more clearly than eight
|
|
28
|
+
more pixels of height.
|
|
29
|
+
- Fit depends on the region. A toolbar is tight, a hero is spacious, and a table
|
|
30
|
+
row is tighter still. The region sets the fit for every control in it.
|
|
31
|
+
- Optical role applies only to things that are not controls. An avatar at 24 in
|
|
32
|
+
a list and at 96 on a profile are two different pictures. That is why
|
|
33
|
+
`Avatar`, `Loader`, and `Orb` keep a `size`.
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
24 in a list and 96 on a profile is a different graphic, not a taller control.
|
|
34
|
-
That is why `size` survives on `Avatar`, `Loader` and `Orb`: those are
|
|
35
|
-
pictures.
|
|
35
|
+
## Can two controls differ in height?
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Ask whether two controls placed together can ever differ in height:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
- Save and Cancel
|
|
40
|
+
- An icon button beside a text button
|
|
41
|
+
- An input and its submit button
|
|
42
|
+
- The two halves of a split button
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
The two halves of a split button. In every case the answer is no.
|
|
44
|
+
In every case the answer is no.
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
unwritable.
|
|
46
|
+
A size on each control describes the **region**, one element at a time, and
|
|
47
|
+
that is how a row ends up with one control at the wrong height. When you set the
|
|
48
|
+
density once on the region, you cannot write a row of mismatched heights.
|
|
48
49
|
|
|
49
|
-
`Dialog` keeps a `size` by the same test
|
|
50
|
-
|
|
51
|
-
region rather than a control in one.
|
|
50
|
+
`Dialog` keeps a `size` by the same test. Two dialogs never sit together. One is
|
|
51
|
+
on screen at a time, in the top layer, so the dialog is itself the region.
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
region instead of one per element.
|
|
53
|
+
This also helps when you or a model generate code. There is no size to get wrong
|
|
54
|
+
on each element, and you make one choice for each region.
|
|
56
55
|
|
|
57
|
-
##
|
|
56
|
+
## Set the density of a region
|
|
58
57
|
|
|
59
|
-
Put `data-density` on any container
|
|
60
|
-
is never
|
|
58
|
+
Put `data-density` on any container, with a value of `sm`, `md`, or `lg`. `md`
|
|
59
|
+
is the default, so you never write it.
|
|
61
60
|
|
|
62
61
|
```html
|
|
63
62
|
<helix-toolbar>
|
|
@@ -67,24 +66,30 @@ is never written.
|
|
|
67
66
|
</helix-toolbar>
|
|
68
67
|
```
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
right height
|
|
69
|
+
Containers that are dense by design set their own density. The app header, the
|
|
70
|
+
toolbar, and the announcement all do, so a control inside one of them is already
|
|
71
|
+
the right height.
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
`--helix-size-checkbox`, `--helix-size-switch`, `--helix-size-tag`), which is
|
|
76
|
-
why it costs no component any API and crosses every shadow boundary on its own.
|
|
73
|
+
Density works through these custom properties:
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
- `--helix-size-control`
|
|
76
|
+
- `--helix-size-checkbox`
|
|
77
|
+
- `--helix-size-switch`
|
|
78
|
+
- `--helix-size-tag`
|
|
80
79
|
|
|
81
|
-
|
|
80
|
+
Because of this, density adds no API to any component, and it passes through
|
|
81
|
+
every shadow boundary.
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
The `-sm`, `-md`, and `-lg` tokens are the scale, and they never change. The
|
|
84
|
+
unsuffixed tokens hold the current step, and `data-density` changes them.
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
## Exceptions
|
|
87
|
+
|
|
88
|
+
`data-link-size` still exists. A text link has no control height. Its size comes
|
|
89
|
+
from typography, and it follows the text around it.
|
|
90
|
+
|
|
91
|
+
A component can still set its own step on the scale. `--helix-size-switch` is
|
|
92
|
+
one step above `--helix-size-checkbox` at every density, because a switch is a
|
|
93
|
+
track with a thumb inside it and a checkbox is a box with a mark in it. The
|
|
94
|
+
component sets this once, and it applies to every instance. You do not set a
|
|
95
|
+
height on each instance.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Use the space scale for gaps and the size tokens for the height of controls.
|
|
3
3
|
section: Foundations
|
|
4
4
|
title: Space and size
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Space and size
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
up
|
|
9
|
+
Helix has two scales. The space scale sets the gaps between things. The size
|
|
10
|
+
scale sets how big a control is. If you use one for the other, a button can end
|
|
11
|
+
up one step of padding taller than the input beside it.
|
|
12
12
|
|
|
13
13
|
## Space
|
|
14
14
|
|
|
@@ -29,15 +29,16 @@ up with a button that is one step of padding taller than the input beside it.
|
|
|
29
29
|
|
|
30
30
|
[//]: # (/tokens)
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
arithmetic
|
|
34
|
-
scale is tuned, and
|
|
32
|
+
The scale has nine steps, named like t-shirt sizes. Numbered steps suggest
|
|
33
|
+
arithmetic, such as `space-4` being twice `space-2`. That stops being true as
|
|
34
|
+
soon as the scale is tuned, and it leads to a `space-4.5` the first time
|
|
35
|
+
something does not fit.
|
|
35
36
|
|
|
36
|
-
`--helix-space-control` is
|
|
37
|
-
control uses, and it follows the region's density
|
|
37
|
+
`--helix-space-control` is a tenth token of a different kind. It is the inset a
|
|
38
|
+
control uses, and it follows the region's density. It is not a step on the
|
|
38
39
|
scale. See [Size and density](./size-and-density).
|
|
39
40
|
|
|
40
|
-
Use
|
|
41
|
+
Use the space tokens for gap, padding, and margin:
|
|
41
42
|
|
|
42
43
|
```css
|
|
43
44
|
.toolbar {
|
|
@@ -49,24 +50,31 @@ Use them for gap, padding and margin:
|
|
|
49
50
|
|
|
50
51
|
## Size
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`--helix-size-avatar`, `--helix-size-icon`, `--helix-size-tag`,
|
|
55
|
-
`--helix-size-header`, `--helix-size-progress`, `--helix-size-meter`.
|
|
53
|
+
The size scale has forty-one tokens, and almost all of them name a specific
|
|
54
|
+
thing:
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
- `--helix-size-control`
|
|
57
|
+
- `--helix-size-checkbox`
|
|
58
|
+
- `--helix-size-switch`
|
|
59
|
+
- `--helix-size-avatar`
|
|
60
|
+
- `--helix-size-icon`
|
|
61
|
+
- `--helix-size-tag`
|
|
62
|
+
- `--helix-size-header`
|
|
63
|
+
- `--helix-size-progress`
|
|
64
|
+
- `--helix-size-meter`
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
Most come in a `-sm`, `-md`, and `-lg` set beside the unsuffixed token. **The
|
|
67
|
+
unsuffixed token is the live one.** The suffixed tokens are the values the
|
|
68
|
+
density axis picks between. A component reads `--helix-size-control`, and the
|
|
69
|
+
region's `[data-density]` sets whether that resolves to `-sm`, `-md`, or `-lg`.
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
The loader is the exception. It has `-sm`, `-md`, and `-lg` and no unsuffixed
|
|
72
|
+
token, because nothing reads a loader's size from the density.
|
|
73
|
+
|
|
74
|
+
This is why a component has no `size` property. The region sets the size once,
|
|
75
|
+
so you cannot write a row of controls with different heights.
|
|
76
|
+
|
|
77
|
+
## Focus ring tokens
|
|
70
78
|
|
|
71
79
|
```
|
|
72
80
|
--helix-size-focus-ring the ring's own thickness
|
|
@@ -74,16 +82,15 @@ unwritable.
|
|
|
74
82
|
--helix-size-focus-reach ring plus offset, for reserving room
|
|
75
83
|
```
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
component that draws
|
|
79
|
-
|
|
85
|
+
A ring drawn outside a control needs room. `--helix-size-focus-reach` holds that
|
|
86
|
+
sum, so each component that draws a ring does not compute it again. Use `reach`
|
|
87
|
+
to reserve the room, and the other two to draw the ring.
|
|
80
88
|
|
|
81
89
|
## What not to do
|
|
82
90
|
|
|
83
|
-
**Do not use a size token for a gap.** `--helix-size-control` is a height
|
|
84
|
-
|
|
91
|
+
**Do not use a size token for a gap.** `--helix-size-control` is a height. A gap
|
|
92
|
+
that uses it changes when the region's density changes.
|
|
85
93
|
|
|
86
|
-
**Do not use a raw value
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
layout.
|
|
94
|
+
**Do not use a raw value when the scale has no step for it.** If a gap really
|
|
95
|
+
falls between `sm` and `md`, note it and do not write `11px`. It usually means
|
|
96
|
+
the two things it separates belong at different levels of the layout.
|