@fusion.dev/helix-templates 0.139.0 → 0.141.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 (62) hide show
  1. package/dist/index.js +6 -6
  2. package/package.json +1 -1
  3. package/templates/angular/src/app/data.ts +17 -17
  4. package/templates/angular/src/app/docs.ts +9 -9
  5. package/templates/angular/src/app/landing.html +39 -74
  6. package/templates/angular/src/app/landing.ts +0 -2
  7. package/templates/angular/src/app/overview.html +2 -2
  8. package/templates/angular/src/app/pathname.ts +1 -1
  9. package/templates/angular/src/app/signup-form.html +13 -14
  10. package/templates/angular/src/app/site-footer.html +3 -6
  11. package/templates/angular/src/app/site-footer.ts +2 -2
  12. package/templates/angular/src/app/site-header.html +3 -6
  13. package/templates/angular/src/app/site-header.ts +2 -3
  14. package/templates/angular/src/app.css +151 -158
  15. package/templates/react-nextjs/app/app.css +151 -158
  16. package/templates/react-nextjs/app/data.ts +17 -17
  17. package/templates/react-nextjs/app/docs.ts +9 -9
  18. package/templates/react-nextjs/app/landing.tsx +66 -128
  19. package/templates/react-nextjs/app/overview.tsx +24 -26
  20. package/templates/react-nextjs/app/signup-form.tsx +36 -40
  21. package/templates/react-nextjs/app/site-footer.tsx +9 -15
  22. package/templates/react-nextjs/app/site-header.tsx +18 -26
  23. package/templates/react-nextjs/helix-jsx.d.ts +5 -7
  24. package/templates/react-nextjs/tsconfig.json +4 -1
  25. package/templates/react-vite/helix-jsx.d.ts +5 -7
  26. package/templates/react-vite/src/app.css +151 -158
  27. package/templates/react-vite/src/data.ts +17 -17
  28. package/templates/react-vite/src/docs.ts +9 -9
  29. package/templates/react-vite/src/landing.tsx +66 -128
  30. package/templates/react-vite/src/overview.tsx +24 -26
  31. package/templates/react-vite/src/routes.tsx +11 -13
  32. package/templates/react-vite/src/signup-form.tsx +36 -40
  33. package/templates/react-vite/src/site-footer.tsx +9 -15
  34. package/templates/react-vite/src/site-header.tsx +16 -24
  35. package/templates/solid-vite/src/app.css +151 -158
  36. package/templates/solid-vite/src/data.ts +17 -17
  37. package/templates/solid-vite/src/docs.ts +9 -9
  38. package/templates/solid-vite/src/landing.tsx +54 -105
  39. package/templates/solid-vite/src/overview.tsx +2 -2
  40. package/templates/solid-vite/src/routes.ts +6 -6
  41. package/templates/solid-vite/src/signup-form.tsx +14 -14
  42. package/templates/solid-vite/src/site-footer.tsx +5 -8
  43. package/templates/solid-vite/src/site-header.tsx +5 -9
  44. package/templates/svelte-vite/src/Landing.svelte +39 -76
  45. package/templates/svelte-vite/src/Overview.svelte +2 -2
  46. package/templates/svelte-vite/src/SignupForm.svelte +13 -14
  47. package/templates/svelte-vite/src/SiteFooter.svelte +5 -8
  48. package/templates/svelte-vite/src/SiteHeader.svelte +5 -8
  49. package/templates/svelte-vite/src/app.css +151 -158
  50. package/templates/svelte-vite/src/data.ts +17 -17
  51. package/templates/svelte-vite/src/docs.ts +9 -9
  52. package/templates/svelte-vite/src/routes.svelte.ts +5 -5
  53. package/templates/vue-vite/src/LandingPage.vue +39 -76
  54. package/templates/vue-vite/src/OverviewPage.vue +2 -2
  55. package/templates/vue-vite/src/SignupForm.vue +14 -14
  56. package/templates/vue-vite/src/SiteFooter.vue +5 -8
  57. package/templates/vue-vite/src/SiteHeader.vue +5 -9
  58. package/templates/vue-vite/src/app.css +151 -158
  59. package/templates/vue-vite/src/data.ts +17 -17
  60. package/templates/vue-vite/src/docs.ts +9 -9
  61. package/templates/vue-vite/src/routes.ts +6 -6
  62. package/templates.json +4 -10
@@ -3,43 +3,43 @@ import { DOCS } from './docs.js'
3
3
  import { Link } from './routes.js'
4
4
 
5
5
  /**
6
- * **A locale named rather than left to the machine.** `toLocaleString()` with no
7
- * argument reads the environment's locale, which is the server's in one template
8
- * and the reader's in both, so the same number is grouped two ways and the
9
- * markup React hydrates does not match the markup it rendered.
6
+ * **A locale named, so the number reads the same everywhere.**
7
+ * `toLocaleString()` with no argument reads the machine's locale: the server's
8
+ * in the Next.js template, and the browser's in both. The same number is then
9
+ * grouped two ways, and the markup React hydrates does not match the markup it
10
+ * rendered.
10
11
  */
11
12
  const figure = new Intl.NumberFormat('en-US')
12
13
 
13
14
  /**
14
- * The first screen, and the only page here that is about the starter rather
15
- * than built out of it.
15
+ * The first screen, and the only page here about the starter itself.
16
16
  *
17
17
  * Delete it once you know your way around: nothing else imports it, and the
18
18
  * route it answers becomes yours.
19
19
  *
20
- * **The heading says what this is, and the project's name stays in the bar.** A
21
- * scaffold called `my-app` opening on an `<h1>my-app</h1>` tells its reader
22
- * nothing they did not type thirty seconds ago, and spends the loudest line on
23
- * the page doing it.
24
- *
25
- * **Anchors, not a `Button` with an `href`.** A button that navigates is a link
26
- * wearing a button's clothes, and the two are different things to a keyboard and
27
- * to a screen reader. The base stylesheet styles an anchor that asks with
28
- * `data-button`, so a link can look like one without being one.
20
+ * **The calls to action are anchors.** A button that navigates is a link
21
+ * dressed as a button, and the two are different things to a keyboard and to
22
+ * a screen reader. The base stylesheet styles an anchor that asks with
23
+ * `data-button`, so a link can look like one and stay a link.
29
24
  */
30
25
  export function Landing() {
31
26
  return (
32
27
  <div className="stack">
33
28
  <header className="first-screen">
29
+ {/* **The light behind the first screen**: a helix, drawn by the library
30
+ in the theme's colors, so a new brand color relights it. It shows
31
+ one still frame where motion is reduced, and a gradient where the
32
+ browser draws without a GPU. */}
33
+ <helix-aura className="first-light" data-layer speed={0.4} terminus={0.8} variant="braid" />
34
34
  <div className="hero">
35
- <helix-text emphasis="muted" variant="label">
36
- Helix starter
35
+ <helix-tag emphasis="subtle" tone="primary">
36
+ Starter
37
+ </helix-tag>
38
+ <helix-text as="h1" variant="display">
39
+ Helix design system
37
40
  </helix-text>
38
- <helix-text as="h1">Every tag on this page is Helix</helix-text>
39
- <helix-text as="p" className="lede" emphasis="muted" variant="body">
40
- Nothing here is a wrapper: the tags are the library's own elements, and every color,
41
- size and space inside them comes from one set of tokens. Three routes are laid down, and
42
- all of them are yours.
41
+ <helix-text as="p" className="lede" variant="body">
42
+ Web components in your brand's colors, drawn from one set of tokens.
43
43
  </helix-text>
44
44
  <div className="actions">
45
45
  <Link data-button="solid" data-button-tone="primary" href="/overview">
@@ -51,17 +51,13 @@ export function Landing() {
51
51
  </div>
52
52
  </div>
53
53
 
54
- {/* **What the first screen has to look at, and the honest thing to look
55
- at is the library working.** The figures are the roster the data view
56
- draws, read from the same module, so the panel is the app rather than
57
- a decoration of it. `helix-stat` sets the number on the display face
58
- at a size the type scale does not reach, which is the one place on a
59
- page where the number is the design. */}
60
- <helix-card className="specimen" separation="outlined">
61
- <helix-text as="h2" slot="header" variant="title">
62
- The roster, in three figures
63
- </helix-text>
64
- <helix-stat-group divided>
54
+ {/* **The library at work, on the first screen.** The figures are the
55
+ roster the data view draws, read from the same module, so the panel
56
+ is the app itself, on glass over the light. It has no link of its own:
57
+ the button beside it goes to the same page. */}
58
+ <helix-card className="specimen" material="glass" separation="outlined">
59
+ <helix-text as="h2" slot="header" variant="title">Roster</helix-text>
60
+ <helix-stat-group>
65
61
  <helix-stat>
66
62
  {roster.people}
67
63
  <span slot="label">People</span>
@@ -75,9 +71,6 @@ export function Landing() {
75
71
  <span slot="label">Reviews</span>
76
72
  </helix-stat>
77
73
  </helix-stat-group>
78
- <Link data-link href="/overview" slot="footer">
79
- Open the data view
80
- </Link>
81
74
  </helix-card>
82
75
  </header>
83
76
 
@@ -85,106 +78,51 @@ export function Landing() {
85
78
  <helix-text as="h2" variant="subheading">
86
79
  What you have
87
80
  </helix-text>
88
- {/* Three paragraphs about one installed thing, so they take a rule each
89
- rather than a box each. `app.css` says why at more length. */}
90
- <div className="facts">
91
- <article className="fact">
92
- <helix-text as="h3" variant="title">
93
- Elements, not wrappers
94
- </helix-text>
95
- <helix-text as="p" emphasis="muted" variant="body">
96
- They are custom elements, so you write the tags and there is no per-component import
97
- to keep in step. One line in <helix-code>src/main.tsx</helix-code> registers all of
98
- them, and a subpath like <helix-code>@fusion.dev/helix/tabs</helix-code> registers
99
- one.
100
- </helix-text>
101
- <a data-link href={DOCS.elements}>
102
- Using the elements
103
- </a>
104
- </article>
105
-
106
- <article className="fact">
107
- <helix-text as="h3" variant="title">
108
- One set of tokens
109
- </helix-text>
110
- <helix-text as="p" emphasis="muted" variant="body">
111
- Every color is written once as a light and dark pair, so the control in the bar moves
112
- the whole page by setting a single attribute on the root. Type, spacing, radii and
113
- control sizes come from the same place.
114
- </helix-text>
115
- <a data-link href={DOCS.tokens}>
116
- Tokens
117
- </a>
118
- </article>
119
-
120
- <article className="fact">
121
- <helix-text as="h3" variant="title">
122
- A short stylesheet
123
- </helix-text>
124
- <helix-text as="p" emphasis="muted" variant="body">
125
- <helix-code>src/app.css</helix-code> holds this page's measure, the rhythm between its
126
- blocks, and nothing a component already draws. Where a component leaves a decision
127
- open it names a part: the data view reaches one to paint the row you tick.
128
- </helix-text>
129
- <a data-link href={DOCS.parts}>
130
- Overriding a component
131
- </a>
132
- </article>
133
- </div>
134
- </section>
135
-
136
- <section className="section">
137
- <helix-text as="h2" variant="subheading">
138
- Where to go next
139
- </helix-text>
140
- {/* **The whole card is the link, which is what `data-card` is for.** A
141
- card with an anchor in its footer gives a reader a target the size of
142
- three words inside a box the size of a paragraph, and the box is what
143
- they aim at. The recipe is in the base layer: it draws the ring, moves
144
- the card's border to the brand on hover, and leaves focus alone. So
145
- nothing inside may be interactive, a link inside a link being neither
146
- valid nor reachable. */}
147
- <helix-grid columns={2} gap="md">
148
- <Link data-card href="/overview">
81
+ {/* **Each tile is a link, which is what `data-card` is for.** The base
82
+ layer draws the ring, moves the card's border to the brand on hover,
83
+ and leaves focus alone. Nothing inside may be interactive, since a
84
+ link inside a link is neither valid nor reachable. */}
85
+ <div className="features">
86
+ <a className="feature" data-card href={DOCS.elements}>
149
87
  <helix-card separation="outlined">
150
- {/* The route beside the name rather than under the text: slotted
151
- into the header the two sit on one baseline, and a row of
152
- cards stretched to a common height has nothing left floating
153
- in the middle of it. */}
154
- <helix-flex align-items="baseline" gap="sm" justify-content="between" slot="header">
155
- <helix-text as="h3" variant="title">
156
- The data view
157
- </helix-text>
158
- <helix-text emphasis="subtle" variant="code">
159
- /overview
160
- </helix-text>
161
- </helix-flex>
88
+ <span className="feature-mark">
89
+ <helix-icon name="cube" />
90
+ </span>
91
+ <helix-text as="h3" variant="title">
92
+ Custom elements
93
+ </helix-text>
162
94
  <helix-text as="p" emphasis="muted" variant="body">
163
- Twelve people from a local module, sorted, paged and selected with nothing wired up.
164
- The columns are markup you can read and the rows are one property, which is what
165
- keeps the same component working at ten thousand.
95
+ One import in <helix-code>src/main.tsx</helix-code> registers every tag.
166
96
  </helix-text>
167
97
  </helix-card>
168
- </Link>
169
-
170
- <Link data-card href="/join">
98
+ </a>
99
+ <a className="feature" data-card href={DOCS.tokens}>
171
100
  <helix-card separation="outlined">
172
- <helix-flex align-items="baseline" gap="sm" justify-content="between" slot="header">
173
- <helix-text as="h3" variant="title">
174
- The form
175
- </helix-text>
176
- <helix-text emphasis="subtle" variant="code">
177
- /join
178
- </helix-text>
179
- </helix-flex>
101
+ <span className="feature-mark">
102
+ <helix-icon name="palette" />
103
+ </span>
104
+ <helix-text as="h3" variant="title">
105
+ One set of tokens
106
+ </helix-text>
180
107
  <helix-text as="p" emphasis="muted" variant="body">
181
- Five controls a <helix-code>&lt;form&gt;</helix-code> reads with{' '}
182
- <helix-code>FormData</helix-code>, because every one of them is a real form
183
- participant. The email field's complaint is the browser's own.
108
+ Every color, size, and space, in light and dark.
184
109
  </helix-text>
185
110
  </helix-card>
186
- </Link>
187
- </helix-grid>
111
+ </a>
112
+ <a className="feature" data-card href={DOCS.parts}>
113
+ <helix-card separation="outlined">
114
+ <span className="feature-mark">
115
+ <helix-icon name="sliders-horizontal" />
116
+ </span>
117
+ <helix-text as="h3" variant="title">
118
+ Parts to style
119
+ </helix-text>
120
+ <helix-text as="p" emphasis="muted" variant="body">
121
+ Reach inside a component with <helix-code>::part()</helix-code>.
122
+ </helix-text>
123
+ </helix-card>
124
+ </a>
125
+ </div>
188
126
  </section>
189
127
  </div>
190
128
  )
@@ -6,13 +6,13 @@ import { DOCS } from './docs.js'
6
6
  *
7
7
  * **The schema is markup and the data is a property.** The columns are elements
8
8
  * you can read in the HTML, and `rows` is the whole result handed over at once,
9
- * because a table of ten thousand rows cannot be written out as elements. The
10
- * table sorts, pages and selects out of that array on its own: none of it is
11
- * wired up here, and none of it is a handler you have to write.
9
+ * since a table of ten thousand rows cannot be written out as elements. The
10
+ * table sorts, pages, and selects out of that array on its own, with no handler
11
+ * for you to write.
12
12
  *
13
13
  * **Selection is keyed on `row-key`, so it survives a sort and a page.** A row
14
- * is chosen by its identity rather than by where it happened to be sitting, and
15
- * `app.css` paints the chosen row through the parts the table names for it.
14
+ * is chosen by its identity, wherever it sits, and `app.css` paints the chosen
15
+ * row through the parts the table names for it.
16
16
  */
17
17
  export function Overview() {
18
18
  return (
@@ -39,18 +39,18 @@ export function Overview() {
39
39
  rows={members}
40
40
  selectable
41
41
  >
42
- {/* **Every column declares what it may give up, and that is the whole of
42
+ {/* **Every column declares what it may give up, which is the whole of
43
43
  the narrow-window story.** A column left to itself takes
44
- `minmax(max-content, 1fr)`, which refuses to go below its longest
45
- cell however little room there is, so the last column is pushed
46
- outside the box and the reader has to scroll the table sideways to
47
- read a value. A floor and a share instead: the floor is what the
48
- words need, the share is how the slack is divided. Measured 16 Sep
49
- 2026: at 390 the four columns and the checkboxes come to 358 in a box
50
- of 358, so nothing is cut and nothing wraps; the floors alone come to
51
- 344, so at a 376 pixel window they exactly fit and below it the table
52
- scrolls its own box, which is honest, and it fades whichever edge has
53
- more behind it. */}
44
+ `minmax(max-content, 1fr)`, which never goes below its longest
45
+ cell, so the last column is pushed outside the box and you have to
46
+ scroll the table sideways to read a value. Each column here has a
47
+ floor and a share: the floor is what the words need, and the share
48
+ is how the spare width is divided. Measured September 16, 2026: at
49
+ 390 the four columns and the checkboxes come to 358 in a box of
50
+ 358, so nothing is cut and nothing wraps. The floors alone come to
51
+ 344, so at a 376 pixel window they exactly fit, and below it the
52
+ table scrolls its own box and fades whichever edge has more behind
53
+ it. */}
54
54
  <helix-table-column field="name" sortable width="minmax(5.5rem, 1.6fr)">
55
55
  Name
56
56
  </helix-table-column>
@@ -60,26 +60,24 @@ export function Overview() {
60
60
  <helix-table-column field="role" width="minmax(4.5rem, 1fr)">
61
61
  Role
62
62
  </helix-table-column>
63
- {/* `end` lines the digits up under each other, which is the whole reason
64
- a number column is a different thing from a text one. `max-content`
65
- because a column of numerals has one right width and no reason to
66
- take a share of anything. */}
63
+ {/* `end` lines the digits up under each other, which is what makes a
64
+ number column different from a text one. `max-content`, since a
65
+ column of numerals has one right width. */}
67
66
  <helix-table-column field="reviews" sortable text-align="end" width="max-content">
68
67
  Reviews
69
68
  </helix-table-column>
70
69
 
71
- {/* The footer is a row across the table, not a corner: anything you slot
72
- goes at the start and the table's own pagination sits at the end. */}
70
+ {/* The footer is a row across the table: anything you slot goes at the
71
+ start, and the table's own pagination sits at the end. */}
73
72
  <helix-text emphasis="subtle" slot="footer" variant="caption">
74
73
  {members.length} people
75
74
  </helix-text>
76
75
  </helix-data-table>
77
76
 
78
- {/* A footnote to the table rather than the next block of the page, so it
79
- takes the section's gap and not the stack's. */}
77
+ {/* A footnote to the table, so it takes the section's gap. */}
80
78
  <helix-text as="p" emphasis="muted" variant="caption">
81
- Filtering, column reordering, resizing, virtualized rows and a server mode that asks you
82
- for a page are all on{' '}
79
+ Filtering, column reordering, resizing, virtualized rows, and a server mode for large
80
+ results are on{' '}
83
81
  <a data-link href={DOCS.table}>
84
82
  the component's own page
85
83
  </a>
@@ -2,26 +2,24 @@ import { type AnchorHTMLAttributes, useEffect, useState } from 'react'
2
2
 
3
3
  /* **The whole router, which is a hash and a listener.**
4
4
 
5
- A starter should not pick your router for you, and two pages need
6
- `location.hash` and one event. Swap this file for the router you want: the two
7
- names it exports are Next.js's, so the rest of the app reads the same in
8
- either template and nothing else here has to change.
5
+ Three pages need `location.hash` and one event, so the starter leaves the
6
+ router to you. Swap this file for the router you want: the two names it
7
+ exports are Next.js's, so the rest of the app reads the same in either
8
+ template, and nothing else has to change.
9
9
 
10
- A hash rather than the History API, because the History API wants a server
11
- that answers every path with the same document, and `vite preview` and
12
- whatever you deploy to each have to be told to. A hash asks nothing of
13
- either. */
10
+ A hash, because the History API wants a server that answers every path with
11
+ the same document, and `vite preview` and wherever you deploy each have to be
12
+ told to. A hash asks nothing of either. */
14
13
 
15
- /** Where the reader is, as a path, so the overview is `/`. */
14
+ /** The current path, read from the hash, so the first page is `/`. */
16
15
  const read = (): string => window.location.hash.slice(1) || '/'
17
16
 
18
17
  /**
19
18
  * A destination, addressed the way the Next.js template addresses one.
20
19
  *
21
- * Everything but `href` goes straight through to the anchor, which is what
22
- * `next/link` does and is why a `data-button` or a `slot` reads the same in both
23
- * templates. The path is written as a path: the hash is this component's
24
- * business and nobody else's.
20
+ * Everything but `href` goes straight through to the anchor, as `next/link`
21
+ * does, which is why a `data-button` or a `slot` reads the same in both
22
+ * templates. The path is written as a path, and this component adds the hash.
25
23
  */
26
24
  export function Link({ href, ...rest }: AnchorHTMLAttributes<HTMLAnchorElement>) {
27
25
  return <a {...rest} href={`#${href ?? '/'}`} />
@@ -12,21 +12,21 @@ interface Message {
12
12
  * The form.
13
13
  *
14
14
  * **Every control here is a form-associated custom element**, so a `<form>`
15
- * collects them, resets them and validates them the way it does an `<input>`.
16
- * That is what `ElementInternals` buys, and the submit below proves it: it reads
17
- * the answers with `new FormData(form)` and nothing gathers them by hand.
15
+ * collects them, resets them, and validates them the way it does an `<input>`.
16
+ * `ElementInternals` makes that possible, and the submit below proves it: it
17
+ * reads the answers with `new FormData(form)`, and nothing gathers them by hand.
18
18
  *
19
- * There is no wrapping `Field` component. A label in one shadow root cannot name
20
- * a control in another, so every control renders its own `label`, `hint` and
21
- * `feedback` instead.
19
+ * There is no wrapping `Field` component. A label in one shadow root cannot
20
+ * name a control in another, so every control renders its own `label`, `hint`,
21
+ * and `feedback`.
22
22
  */
23
23
  export function SignupForm() {
24
24
  const [handle, setHandle] = useState('')
25
25
  const [messages, setMessages] = useState<readonly Message[]>([])
26
26
 
27
27
  const join = (event: FormEvent<HTMLFormElement>) => {
28
- /* The page answers this, so nothing navigates. Remove the line and the form
29
- posts to the server like any other, with the same names. */
28
+ /* The page takes the submit, so nothing navigates. Remove the line and the
29
+ form posts to the server like any other, with the same names. */
30
30
  event.preventDefault()
31
31
  const answers = [...new FormData(event.currentTarget).entries()]
32
32
  .map(([field, value]) => `${field}=${value}`)
@@ -49,18 +49,16 @@ export function SignupForm() {
49
49
  </helix-text>
50
50
  </header>
51
51
 
52
- {/* The form takes a column of its own and the notes take what is left, so a
53
- wide window is a page rather than a form with a void beside it. At 390
54
- the two stack and the notes read as a postscript. */}
52
+ {/* The form takes a column of its own and the notes take what is left. At
53
+ 390 the two stack, and the notes read as a postscript. */}
55
54
  <div className="split">
56
55
  <form className="form" onSubmit={join}>
57
56
  <helix-input autocomplete="name" label="Name" name="name" required />
58
57
 
59
58
  {/* Validation is the platform's: `type` and `required` are set on the
60
- real input inside, and its own message is what shows once the reader
61
- has touched the field. Set `feedback` to say it in your own words
62
- instead, and it shows from the first paint rather than after a
63
- mistake. */}
59
+ real input inside, and its own message shows once the field has been
60
+ touched. Set `feedback` to say it in your own words, and it shows
61
+ from the first paint. */}
64
62
  <helix-input
65
63
  hint="Where we send the welcome note."
66
64
  label="Email"
@@ -72,11 +70,11 @@ export function SignupForm() {
72
70
  {/* **A controlled value, which is the one habit to unlearn.** The handler
73
71
  is a prop named for the event, so `onhelix-before-change` hears
74
72
  `helix-before-change`. A Helix element is not read-only: writing
75
- `value` seeds it and the element goes on applying its own changes, so
76
- taking the decision means refusing the change with `preventDefault()`
77
- and then setting the value you want. Declining to call `setHandle` is
78
- not a refusal, and leaves the element holding what you did not
79
- accept. */}
73
+ `value` seeds it, and the element goes on applying its own changes.
74
+ So taking the decision means refusing the change with
75
+ `preventDefault()`, then setting the value you want. Leaving
76
+ `setHandle` uncalled refuses nothing, and the element keeps the value
77
+ you meant to turn down. */}
80
78
  <helix-input
81
79
  hint="Lowercase and hyphens, whatever you type."
82
80
  label="Handle"
@@ -96,8 +94,8 @@ export function SignupForm() {
96
94
  ))}
97
95
  </helix-select>
98
96
 
99
- {/* A switch is a thing happening now rather than an answer a form asks
100
- for, which is why it says on and off. It still submits, as `on`. */}
97
+ {/* A switch is a thing happening now, which is why it says on and off.
98
+ It still submits, as `on`. */}
101
99
  <helix-switch label="Send me the weekly digest" name="digest" />
102
100
 
103
101
  <helix-button emphasis="solid" tone="primary" type="submit">
@@ -106,27 +104,25 @@ export function SignupForm() {
106
104
  </form>
107
105
 
108
106
  <aside className="notes">
109
- {/* The one fact a reader needs before typing their name into a page
110
- they have had for five minutes, which is why it is a callout and
111
- not the fourth line of a paragraph. `tone` is the meaning and the
112
- component decides what that looks like: a tint mixed from the
113
- status color and a bar on the inline start, so it survives being
114
- read in grayscale. */}
115
- <helix-callout tone="info">
116
- Nothing is posted anywhere. Submit it and the message shows exactly what a server would
117
- have been handed.
107
+ {/* The one fact you need before typing your name into a page you have
108
+ had for five minutes, so it is a callout. `neutral` is a note that
109
+ reports no status, drawn in the muted ink with a bar on the inline
110
+ start, so it stays in the palette, ink or brand. */}
111
+ <helix-callout tone="neutral">
112
+ The form posts nothing. Submit it, and a message shows exactly what a server would
113
+ receive.
118
114
  </helix-callout>
119
115
  <helix-text as="h2" variant="title">
120
- What this is showing
116
+ What the form shows
121
117
  </helix-text>
122
118
  <helix-text as="p" emphasis="muted" variant="body">
123
- Every control is a form-associated custom element, so the submit reads them with{' '}
124
- <helix-code>new FormData(form)</helix-code> and nothing gathers the answers by hand.
125
- Reset the form and they all go back, the way an <helix-code>input</helix-code> does.
119
+ Every control is a form-associated custom element, so the submit reads them all
120
+ with <helix-code>new FormData(form)</helix-code>. Reset the form and they all go back,
121
+ the way an <helix-code>input</helix-code> does.
126
122
  </helix-text>
127
123
  <helix-text as="p" emphasis="muted" variant="body">
128
- Leave the email wrong and the complaint is the browser's own, in the reader's language.
129
- Handle is the controlled one: it refuses the change and writes back what it will accept.
124
+ Leave the email wrong and the browser shows its own message, in your language. Handle is
125
+ the controlled field: the page refuses each change and writes back a cleaned value.
130
126
  </helix-text>
131
127
  <a data-link href={DOCS.forms}>
132
128
  Forms
@@ -136,9 +132,9 @@ export function SignupForm() {
136
132
 
137
133
  {/* **The region is rendered whether or not there is anything in it.**
138
134
  Assistive technology subscribes to a live region, and one that arrives
139
- already holding its message is frequently never announced. Nothing
140
- creates or destroys a message here either: the items are state, and a
141
- dismissed one asks to go through `helix-dismiss`. */}
135
+ already holding its message is often never announced. Messages are
136
+ state, so nothing creates or destroys one here, and a dismissed one
137
+ asks to go through `helix-dismiss`. */}
142
138
  <helix-toast>
143
139
  {messages.map((message) => (
144
140
  <helix-toast-item
@@ -3,32 +3,26 @@ import { DOCS } from './docs.js'
3
3
  /**
4
4
  * The bar's counterpart, on every route.
5
5
  *
6
- * **It is here so that a page ends rather than stops.** The overview is a
7
- * heading, a table and a caption, which on a tall window left the bottom third
8
- * of the screen empty and read as a layout that had run out. A footer is the
9
- * ordinary answer: it puts a floor under the content, and it is where the links
10
- * a reader wants once rather than constantly belong, which is why the landing
11
- * page no longer carries them itself.
6
+ * **It puts a floor under the content.** The overview is a heading, a table,
7
+ * and a caption, which on a tall window left the bottom third of the screen
8
+ * empty, as if the layout had run out. The footer is also where the links you
9
+ * want once belong, so the landing page no longer carries them.
12
10
  *
13
- * `data-link="subtle"` reads as body text until it is hovered, which is what a
14
- * row of links under a rule should do: they are a destination, not a sentence
15
- * asking to be read.
11
+ * `data-link="quiet"` sets a link in the muted ink with no underline, and it
12
+ * takes the foreground on hover, which suits a row of links under a rule.
16
13
  */
17
14
  export function SiteFooter() {
18
15
  return (
19
16
  <footer className="site-footer">
20
17
  <div className="footer-row">
21
- <helix-text as="p" emphasis="subtle" variant="caption">
22
- Every component in here, with its API, its parts and its examples, has a page.
23
- </helix-text>
24
18
  <div className="footer-links">
25
- <a data-link="subtle" href={DOCS.gallery}>
19
+ <a data-link="quiet" href={DOCS.gallery}>
26
20
  Components
27
21
  </a>
28
- <a data-link="subtle" href={DOCS.elements}>
22
+ <a data-link="quiet" href={DOCS.elements}>
29
23
  Documentation
30
24
  </a>
31
- <a data-link="subtle" href={DOCS.react}>
25
+ <a data-link="quiet" href={DOCS.react}>
32
26
  React
33
27
  </a>
34
28
  </div>
@@ -18,18 +18,16 @@ export function SiteHeader() {
18
18
  control draws the same glyph on the server and on the first client paint. */
19
19
  const [mode, setMode] = useState<Mode>('system')
20
20
 
21
- /**
21
+ /**
22
22
  * One attribute, two modes, and no second stylesheet.
23
23
  *
24
- * Every Helix token is written once as a `light-dark()` pair, so which half
25
- * resolves is decided by `color-scheme` and nothing else. `data-color-mode`
26
- * sets it, and because `color-scheme` inherits the attribute works on any
27
- * element, not only on the root: put it on a panel for a light preview inside
28
- * a dark page.
24
+ * Every Helix token is written once as a `light-dark()` pair, so
25
+ * `color-scheme` decides which half resolves. `data-color-mode` sets it, and
26
+ * since `color-scheme` inherits, the attribute works on any element: put it
27
+ * on a panel for a light preview inside a dark page.
29
28
  *
30
- * `system` removes the attribute rather than writing out whichever mode is
31
- * current, which would freeze the page there the moment the reader's machine
32
- * changed.
29
+ * `system` removes the attribute. Writing out whichever mode is current would
30
+ * hold the page there when the system's own setting changed.
33
31
  */
34
32
  const cycle = () => {
35
33
  const next = MODES[(MODES.indexOf(mode) + 1) % MODES.length]
@@ -43,22 +41,17 @@ export function SiteHeader() {
43
41
 
44
42
  return (
45
43
  <helix-app-header material="glass" sticky>
46
- {/* The wordmark is the way back to the first page, which is what a reader
47
- expects of one, so there is no third destination in the bar saying the
48
- same thing. It is also the only name of the project on the screen: the
49
- landing page leads with what this is rather than with what you called
50
- it.
51
-
52
- The mark is a placeholder for your own, and it is drawn in
53
- `--helix-color-brand-mark`, the token for the brand as a graphic. */}
44
+ {/* The wordmark is the way back to the first page, as a wordmark usually
45
+ is, so there is no third destination in the bar saying the same thing.
46
+ It names the library the starter is built on, and it is the place for
47
+ your own name or logo. */}
54
48
  <Link className="wordmark" data-link="quiet" href="/" slot="start">
55
- <helix-icon name="cube" />
56
- <helix-text variant="body-strong">__PROJECT_NAME__</helix-text>
49
+ <helix-text variant="body-strong">Helix</helix-text>
57
50
  </Link>
58
51
 
59
52
  {/* The links are yours. Navigation owns the marker that travels between
60
- them and renders no anchors of its own, because a router renders its
61
- own and could not render them inside this shadow root. The base layer
53
+ them and renders no anchors of its own, since a router renders its
54
+ own, which could not sit inside this shadow root. The base layer
62
55
  styles them, `aria-current="page"` included. */}
63
56
  <helix-navigation label="Pages">
64
57
  <Link aria-current={path === '/overview' ? 'page' : undefined} href="/overview">
@@ -69,9 +62,8 @@ export function SiteHeader() {
69
62
  </Link>
70
63
  </helix-navigation>
71
64
 
72
- {/* An icon with no words has no accessible name unless it is given one,
73
- which is the whole reason this is its own element rather than a button
74
- with a glyph in it. */}
65
+ {/* An icon with no words has no accessible name until it is given one,
66
+ which is why this is its own element. */}
75
67
  <helix-icon-button
76
68
  label={`Color mode: ${mode}`}
77
69
  onClick={cycle}