@jasonsdevspace/design-kit 0.1.0-next.1 → 0.1.0-next.2
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/CHANGELOG.md +53 -0
- package/README.md +511 -23
- package/dist/base.css +38 -10
- package/dist/components.css +643 -0
- package/dist/react.d.ts +117 -0
- package/dist/react.js +120 -0
- package/dist/tokens.css +20 -14
- package/package.json +13 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,59 @@ keeping a copy. Each version from `0.1.0-next.1` on is staged on npm from a tag
|
|
|
7
7
|
workflow, with provenance, goes live when it is promoted, and has a release on GitHub with the
|
|
8
8
|
entry below as its notes.
|
|
9
9
|
|
|
10
|
+
## 0.1.0-next.2
|
|
11
|
+
|
|
12
|
+
The page chrome every demo draws, and buttons, text fields, selects and callouts, styled by
|
|
13
|
+
`components.css` and drawn by the components in `./react`, with the state of each control carried
|
|
14
|
+
by a native or ARIA attribute. The explainer page, with its steps, panels, tables and code blocks,
|
|
15
|
+
is styled too, as markup a page writes by hand.
|
|
16
|
+
|
|
17
|
+
- **React.** `./react` is new, and takes React `^19.3.0` as an optional peer, which a page that
|
|
18
|
+
writes the markup itself never needs.
|
|
19
|
+
- **Page chrome.** `SkipLink`, `PortfolioStrip` and `DemoFooter` draw the skip link, the strip
|
|
20
|
+
above the page that says whose demo it is, and the footer below it, and `PORTFOLIO_URL`,
|
|
21
|
+
`PORTFOLIO_WORK_URL` and `GITHUB_PROFILE_URL` are the links they share. The components draw
|
|
22
|
+
markup alone, so a page loads `components.css` itself.
|
|
23
|
+
- **Buttons.** `Button` draws `.dk-button`, a plain `type="button"` unless a page gives another
|
|
24
|
+
type, with `.dk-button-accent` for the one action a view leads with. A toggle carries
|
|
25
|
+
`aria-pressed`, and a button whose panel is open `aria-expanded`, drawn alike. `off` marks a
|
|
26
|
+
button `aria-disabled` rather than `disabled`, so it keeps its place in the tab order, and a press
|
|
27
|
+
of it runs no click handler of the page's and submits no form. A button and a field's control
|
|
28
|
+
stand 2.25rem tall, and 2.75rem at 48rem wide or less.
|
|
29
|
+
- **Fields.** `InputField` and `SelectField` draw `.dk-field`, its label, a hint and an error when
|
|
30
|
+
given, and the control, `.dk-input` or a native `.dk-select`. The error sits above the control,
|
|
31
|
+
starts with a hidden "Error: " and marks the control `aria-invalid`. A control's text is never
|
|
32
|
+
under 16px.
|
|
33
|
+
- **Callouts.** `Callout` draws `.dk-callout` in the info tone on the highlight, or
|
|
34
|
+
`.dk-callout-success` or `.dk-callout-danger`, which start with a hidden word that says the tone.
|
|
35
|
+
It takes a role only when the page passes `live`.
|
|
36
|
+
- **Explainer pages.** `.dk-explainer-header` and `.dk-explainer` lay out the page that says how a
|
|
37
|
+
demo works, its text starting where the strip's links start and held to the measure, in plain
|
|
38
|
+
sections that open on a rule, a number and a heading a jump leaves in sight. `.dk-steps` draws
|
|
39
|
+
steps one after another. `./react` draws none of it.
|
|
40
|
+
- **Panels, tables and code.** `.dk-panel` sets a part of a page apart on the card ground, with no
|
|
41
|
+
tone and no role. A table in `.dk-table-scroll` and a block of `.dk-code` scroll inside
|
|
42
|
+
themselves, each a region named by the title above it that the keyboard reaches at every width,
|
|
43
|
+
so the page never scrolls sideways.
|
|
44
|
+
- **Code in running text.** `base.css` sets `code` at `--dk-text-code`, a step under the text
|
|
45
|
+
around it, and breaks a long name where it must. In a block it takes the block's size.
|
|
46
|
+
- **Base.** The border box now reaches a page's `::before` and `::after`, which it missed before,
|
|
47
|
+
since a pseudo-element listed inside a `:where()` matches nothing. The cut to motion reaches
|
|
48
|
+
those too, and a dialog's `::backdrop` and each part of a view transition. A `::before` or
|
|
49
|
+
`::after` that sets a width or height beside padding or a border now draws that size including
|
|
50
|
+
them, so one sized for the content box draws smaller.
|
|
51
|
+
- **Weight.** Every rule for these pieces, the resting look and the explainer's layout included,
|
|
52
|
+
sits in one `:where()`, so a state wins over the resting look by its order in the sheet, and a
|
|
53
|
+
page's own rule outside a layer wins over both.
|
|
54
|
+
- **Tokens.** `--dk-measure` changes from 42rem to 34rem, for every page that reads it. At 42rem a
|
|
55
|
+
line of body text ran past 80 characters, the most WCAG 1.4.8 suggests, and at 34rem the browser
|
|
56
|
+
tests hold a line of the lead and of body text to 80 or fewer. `--dk-text-code` is new, at 0.9em,
|
|
57
|
+
so code sizes to the text around it. The dark value of `--dk-control` is `#7d7d7d`, from
|
|
58
|
+
`#7a7a7a`. A button or field inside an info callout drew its edge at 2.99 to 1 on the highlight in
|
|
59
|
+
the dark theme, under the 3 to 1 a control's edge needs. The accent on the success and danger
|
|
60
|
+
grounds, and a control's edge on the highlight and on each callout's ground, are now measured as
|
|
61
|
+
well.
|
|
62
|
+
|
|
10
63
|
## 0.1.0-next.1
|
|
11
64
|
|
|
12
65
|
Nothing in the code, the styles or the types changed. This is the first version the release workflow
|
package/README.md
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
# @jasonsdevspace/design-kit
|
|
2
2
|
|
|
3
|
-
Design tokens, base styles
|
|
4
|
-
[jasonsdevspace.com](https://jasonsdevspace.com). Every rule sits
|
|
5
|
-
own, so a page's own rule outside a layer wins over every rule of
|
|
6
|
-
without any extra weight. A page with layers of its own has to
|
|
7
|
-
in the order the section on layers below gives. The script
|
|
8
|
-
portfolio before the page paints.
|
|
3
|
+
Design tokens, base styles, a theme script, the page chrome, buttons, fields and callouts, and the
|
|
4
|
+
explainer page, for the demos on [jasonsdevspace.com](https://jasonsdevspace.com). Every rule sits
|
|
5
|
+
in a cascade layer of the kit's own, so a page's own rule outside a layer wins over every rule of
|
|
6
|
+
the kit's but the cut to motion, without any extra weight. A page with layers of its own has to
|
|
7
|
+
name where the kit's go among them, in the order the section on layers below gives. The script
|
|
8
|
+
sets the theme a visitor chose on the portfolio before the page paints. The components are the
|
|
9
|
+
page chrome a demo draws around its page, a skip link, a strip and a footer, and the buttons,
|
|
10
|
+
text fields, selects and callouts on the page itself, as markup a sheet styles and as React
|
|
11
|
+
components that draw it. The explainer that says how a demo works, with its steps, panels, tables
|
|
12
|
+
and code blocks, is markup a page writes by hand, with no component.
|
|
9
13
|
|
|
10
14
|
```sh
|
|
11
15
|
npm install @jasonsdevspace/design-kit
|
|
12
16
|
```
|
|
13
17
|
|
|
14
|
-
The package loads on Node 22.12 or later, the lowest Node 22 that Vite 8 runs on
|
|
15
|
-
or later as an optional peer for its plugin.
|
|
16
|
-
`@jasonsdevspace/design-kit@next`. What
|
|
18
|
+
The package loads on Node 22.12 or later, the lowest Node 22 that Vite 8 runs on. It takes Vite 8
|
|
19
|
+
or later as an optional peer for its plugin, and React `^19.3.0` as an optional peer for its
|
|
20
|
+
components. Prereleases are under npm's `next` tag, as `@jasonsdevspace/design-kit@next`. What
|
|
21
|
+
changed in each version is in
|
|
17
22
|
[CHANGELOG.md](https://github.com/jasonsdevspace/design-kit/blob/main/packages/design-kit/CHANGELOG.md).
|
|
18
23
|
|
|
19
|
-
A page loads
|
|
24
|
+
A page loads the sheets, tokens first, through a bundler that resolves package exports, such as
|
|
20
25
|
Vite.
|
|
21
26
|
|
|
22
27
|
```css
|
|
23
28
|
@import '@jasonsdevspace/design-kit/tokens.css';
|
|
24
29
|
@import '@jasonsdevspace/design-kit/base.css';
|
|
30
|
+
@import '@jasonsdevspace/design-kit/components.css';
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
A page built with Vite takes the theme script from the plugin, and marks where it goes in its head.
|
|
@@ -46,16 +52,29 @@ export default defineConfig({ plugins: [themeScriptPlugin()] });
|
|
|
46
52
|
`--dk-surface`, `--dk-raised`, `--dk-fg`, `--dk-muted`, `--dk-line`, `--dk-control`,
|
|
47
53
|
`--dk-accent`, `--dk-on-accent`, `--dk-highlight`, `--dk-success`, `--dk-success-bg`,
|
|
48
54
|
`--dk-danger`, `--dk-danger-bg` and the scrim, `--dk-scrim`. Beside them are steps of space, text
|
|
49
|
-
size and corners,
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
size and corners, a code size in em that follows the text around it, two line heights, three
|
|
56
|
+
weights, a sans and a mono font stack, the focus ring's width and offset, a field's height and
|
|
57
|
+
inline padding, the size of a touch target, the height of the portfolio's strip, the measure
|
|
58
|
+
and column widths, a shadow and a short duration.
|
|
52
59
|
- `@jasonsdevspace/design-kit/base.css` gives the page its type and colors, one focus ring in the
|
|
53
|
-
accent for every link and control the keyboard reaches,
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
accent for every link and control the keyboard reaches, code in running text a step under the
|
|
61
|
+
text around it, the `.dk-visually-hidden` class for a label a screen reader reads and the eye
|
|
62
|
+
does not, and less motion for a visitor who asks for it. It loads after the tokens, since the
|
|
63
|
+
tokens' first statement declares the kit's layer order.
|
|
64
|
+
- `@jasonsdevspace/design-kit/components.css` styles the page chrome, the skip link, the strip and
|
|
65
|
+
the footer, the buttons, fields and callouts, and the explainer page with its steps, panels,
|
|
66
|
+
tables and code blocks, by the classes in the markup the sections below give. It loads after
|
|
67
|
+
the tokens too.
|
|
56
68
|
- `@jasonsdevspace/design-kit/theme` exports `THEME_KEY`, the storage key the portfolio saves a
|
|
57
69
|
visitor's theme under, `THEMES`, the two themes the script sets, their type `Theme`, and
|
|
58
70
|
`themeScript`, the script itself as a string to put inline.
|
|
71
|
+
- `@jasonsdevspace/design-kit/react` exports `SkipLink`, `PortfolioStrip` and `DemoFooter`, which
|
|
72
|
+
draw the chrome's markup, and `Button`, `InputField`, `SelectField` and `Callout`, which draw the
|
|
73
|
+
markup of the buttons, fields and callouts, with the types `ButtonProps`, `InputFieldProps`,
|
|
74
|
+
`SelectFieldProps` and `CalloutTone`. It also exports `PORTFOLIO_URL`, `PORTFOLIO_WORK_URL` and
|
|
75
|
+
`GITHUB_PROFILE_URL`, the portfolio, its list of work and the GitHub profile the chrome links to.
|
|
76
|
+
It has no component for the explainer or the pieces in it, which a page writes by hand. It takes
|
|
77
|
+
React `^19.3.0` as an optional peer, which a page that writes the markup itself never needs.
|
|
59
78
|
- `@jasonsdevspace/design-kit/vite` exports `themeScriptPlugin()`, the Vite plugin that puts the
|
|
60
79
|
script inline in place of `THEME_PLACEHOLDER`, the comment `<!-- theme-script -->`, and
|
|
61
80
|
`themeScriptHash`, the script's hash as a Content Security Policy source. It runs in Node, and
|
|
@@ -71,12 +90,479 @@ import { themeScript } from '@jasonsdevspace/design-kit/theme';
|
|
|
71
90
|
const head = `<script>${themeScript}</script>`;
|
|
72
91
|
```
|
|
73
92
|
|
|
93
|
+
## Page chrome
|
|
94
|
+
|
|
95
|
+
The page chrome a demo draws around its page is a skip link, a strip above the page that says whose
|
|
96
|
+
demo it is, and a footer below it. `components.css` styles the markup below, which a page without
|
|
97
|
+
React writes by hand and the components in `./react` draw. The components ship no stylesheet and
|
|
98
|
+
set no `style` attribute, so a page loads `components.css` itself, and a page whose policy allows
|
|
99
|
+
styles only from its own origin, `style-src 'self'`, still draws them.
|
|
100
|
+
|
|
101
|
+
The skip link is the keyboard's first stop, out of sight until it takes focus, and then at the
|
|
102
|
+
column's inset, where the strip's links start. Following it moves focus to the page's main content,
|
|
103
|
+
which has a tabindex of -1 so it can take focus.
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<a class="dk-skip" href="#main-content">Skip to content</a>
|
|
107
|
+
<main id="main-content" tabindex="-1">...</main>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The strip holds a navigation landmark named Portfolio, with three links in this order: whose demo
|
|
111
|
+
it is, how it works and the source. The strip on the demo's explainer marks the explainer's link
|
|
112
|
+
with `aria-current="page"`. A demo with switches of its own puts them in a section after the links,
|
|
113
|
+
named for them, and a demo with none leaves the section out.
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<div class="dk-strip">
|
|
117
|
+
<nav class="dk-strip-links" aria-label="Portfolio">
|
|
118
|
+
<a href="https://jasonsdevspace.com/#work">A demo by Jason Poteet</a>
|
|
119
|
+
<a href="./about/">How it works</a>
|
|
120
|
+
<a href="https://github.com/jasonsdevspace/example">Source on GitHub</a>
|
|
121
|
+
</nav>
|
|
122
|
+
<section class="dk-strip-tools" aria-label="Demo">
|
|
123
|
+
<button type="button">Tour</button>
|
|
124
|
+
</section>
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
On a wide screen the strip is one 2.5rem row over a 1px rule, in the small text, with its links
|
|
129
|
+
1.5rem apart and the switches on the right. At 48rem wide or less the text steps down to the
|
|
130
|
+
caption size, the links close to 1rem apart and wrap where they must, and the switches take a row
|
|
131
|
+
of their own below them.
|
|
132
|
+
|
|
133
|
+
The footer is one paragraph that says whose project the demo is, with the author's name linking the
|
|
134
|
+
portfolio, and links how it works, the source, the rest of the work and the license. Anything the
|
|
135
|
+
demo adds follows the license, as sentences of its own. Every link in the chrome keeps its
|
|
136
|
+
underline, and in the footer's sentence the underline is what tells a link from the text around it.
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<footer class="dk-footer">
|
|
140
|
+
<p>
|
|
141
|
+
Example is a portfolio project by <a href="https://jasonsdevspace.com/">Jason Poteet</a>.
|
|
142
|
+
<a href="./about/">How it works</a>, in plain words. The
|
|
143
|
+
<a href="https://github.com/jasonsdevspace/example">source</a> and
|
|
144
|
+
<a href="https://github.com/jasonsdevspace">more work</a> are on GitHub, under the
|
|
145
|
+
<a href="https://github.com/jasonsdevspace/example/blob/main/LICENSE">MIT license</a>.
|
|
146
|
+
</p>
|
|
147
|
+
</footer>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The strip and the footer run their rules the width of the page and line their content up with the
|
|
151
|
+
page's column, `--dk-column`, 72rem wide by default and centered, inset by 1rem. A page whose column
|
|
152
|
+
is another width sets `--dk-column` to it, as it sets a color role.
|
|
153
|
+
|
|
154
|
+
A page with React draws the same markup from the components.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
import { DemoFooter, PortfolioStrip, SkipLink } from '@jasonsdevspace/design-kit/react';
|
|
158
|
+
|
|
159
|
+
const source = 'https://github.com/jasonsdevspace/example';
|
|
160
|
+
|
|
161
|
+
export const Page = () => (
|
|
162
|
+
<>
|
|
163
|
+
<SkipLink />
|
|
164
|
+
<PortfolioStrip
|
|
165
|
+
about="./about/"
|
|
166
|
+
source={source}
|
|
167
|
+
tools={<button type="button">Tour</button>}
|
|
168
|
+
/>
|
|
169
|
+
<main id="main-content" tabIndex={-1}>
|
|
170
|
+
<h1>Example</h1>
|
|
171
|
+
</main>
|
|
172
|
+
<DemoFooter name="Example" about="./about/" source={source} />
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- `SkipLink` leads to `main-content` unless `target` names another id, and its children replace
|
|
178
|
+
the words "Skip to content".
|
|
179
|
+
- `PortfolioStrip` takes `about`, the demo's explainer, and `source`, its repository. `current`
|
|
180
|
+
set to `about` marks the explainer's link as the current page. `tools` holds the demo's own
|
|
181
|
+
switches, and `toolsLabel` names their landmark, `Demo` unless given. With `tools` undefined,
|
|
182
|
+
null or false the strip draws no section for them, so a demo can pass `hasTour && <Tour />`.
|
|
183
|
+
- `DemoFooter` takes `name`, the demo's name as the sentence opens with it, `about` and `source`.
|
|
184
|
+
`license` is the link to the license, `LICENSE` on the main branch of `source` unless given, with
|
|
185
|
+
any trailing slash on `source` dropped first. `licenseName` is the license's name in the
|
|
186
|
+
sentence, `MIT license` unless given. Its children follow the license, and children that are
|
|
187
|
+
undefined, null or false add nothing.
|
|
188
|
+
|
|
189
|
+
## Buttons, fields and callouts
|
|
190
|
+
|
|
191
|
+
`components.css` styles a button, a text field, a select and a callout by the markup below, which a
|
|
192
|
+
page without React writes by hand and the components in `./react` draw. A control's state is the
|
|
193
|
+
native or ARIA attribute a screen reader already reads, such as `aria-pressed` or `aria-invalid`,
|
|
194
|
+
never a class of the kit's, so what a visitor sees and what a screen reader says come from one
|
|
195
|
+
attribute. Every rule for these pieces sits in one `:where()`, the resting look included, so each
|
|
196
|
+
weighs nothing and a state wins over the resting look by coming after it in the sheet. A resting
|
|
197
|
+
look written as a bare class would outweigh every state. A page's own rule outside a layer still
|
|
198
|
+
wins over all of them.
|
|
199
|
+
|
|
200
|
+
### Buttons
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<button type="button" class="dk-button">Filters</button>
|
|
204
|
+
<button type="button" class="dk-button dk-button-accent">Save rule</button>
|
|
205
|
+
<button type="button" class="dk-button" aria-pressed="true">Members only</button>
|
|
206
|
+
<button type="button" class="dk-button" aria-disabled="true">Previous</button>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
A button is `type="button"` unless the page gives it another type, so it never submits a form by
|
|
210
|
+
surprise. `dk-button-accent` fills the one action a view leads with in the accent. It is a
|
|
211
|
+
class, since it is a kind of button rather than a state, and an accent button is never a toggle. A
|
|
212
|
+
toggle that stands alone carries `aria-pressed`, `"true"` or `"false"`, and keeps the same label in
|
|
213
|
+
both states, since the attribute already says which it is. An action carries no `aria-pressed` at
|
|
214
|
+
all. A button with `aria-expanded="true"`, whose panel is open, is drawn as a pressed one.
|
|
215
|
+
|
|
216
|
+
A button that is off carries `aria-disabled="true"` rather than `disabled`. A disabled button that
|
|
217
|
+
has focus drops it, and leaves the tab order, while an aria-disabled one stays where the keyboard
|
|
218
|
+
can reach it and find out why. The attribute does nothing by itself, so a page that writes this
|
|
219
|
+
markup makes its own click handler check `aria-disabled` and return early, calling
|
|
220
|
+
`preventDefault()` so a submit button does not submit. The sheet draws a `disabled` button, and one
|
|
221
|
+
inside a disabled fieldset, the same as an off one, and an accent button that is off keeps its
|
|
222
|
+
class and is drawn as any off button is. A page says why a button is off in text beside it. A
|
|
223
|
+
form's submit button is better left on while the form is incomplete, so a press shows the errors
|
|
224
|
+
rather than doing nothing.
|
|
225
|
+
|
|
226
|
+
A button is 2.25rem tall, `--dk-field-height`, as tall as a field, and 2.75rem, `--dk-target`, at
|
|
227
|
+
48rem wide or less, and it is never narrower than it is tall. It has no ground of its own, so it
|
|
228
|
+
takes the ground of whatever it sits on, a callout included. A link with the class looks the same.
|
|
229
|
+
Where a pointer can hover, the edge turns to the accent under it, and the accent button underlines
|
|
230
|
+
its words instead. Pressed is the highlight with an edge twice as thick in the accent, and its words
|
|
231
|
+
in the page's text, an accent button's included. Off is the muted text on no ground with an edge in
|
|
232
|
+
the line color, rather than a fade, so its words keep a ratio the palette measures, and an off
|
|
233
|
+
toggle that is on keeps the thicker edge, in the line color. In forced colors a pressed button
|
|
234
|
+
takes the system's `Highlight`, an off one `GrayText` on the page's ground, accent or not, and an
|
|
235
|
+
off toggle that is on an edge in `Highlight`.
|
|
236
|
+
|
|
237
|
+
### Fields
|
|
238
|
+
|
|
239
|
+
```html
|
|
240
|
+
<div class="dk-field">
|
|
241
|
+
<label class="dk-field-label" for="email">Email</label>
|
|
242
|
+
<p class="dk-field-hint" id="email-hint">Where the receipt goes</p>
|
|
243
|
+
<p class="dk-field-error" id="email-error">
|
|
244
|
+
<span class="dk-visually-hidden">Error: </span>Enter an email address
|
|
245
|
+
</p>
|
|
246
|
+
<input
|
|
247
|
+
class="dk-input"
|
|
248
|
+
id="email"
|
|
249
|
+
type="email"
|
|
250
|
+
aria-describedby="email-hint email-error"
|
|
251
|
+
aria-invalid="true"
|
|
252
|
+
/>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="dk-field">
|
|
256
|
+
<label class="dk-field-label" for="sort">Sort by</label>
|
|
257
|
+
<select class="dk-select" id="sort">
|
|
258
|
+
<option value="">Choose an order</option>
|
|
259
|
+
<option value="price">Price</option>
|
|
260
|
+
</select>
|
|
261
|
+
</div>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
A field is its label, a hint when it has one, an error when it has one, and then the control,
|
|
265
|
+
`.dk-input` or `.dk-select`. The error sits above the control, so a phone's keyboard or an open list
|
|
266
|
+
never covers it, and the hint stays while the error shows, since it often says how to put the error
|
|
267
|
+
right. The error starts with a visually hidden "Error: ", so it is told from the hint by more than
|
|
268
|
+
its color. `aria-describedby` lists only what is drawn, the hint and then the error, followed by any
|
|
269
|
+
description of the page's own, and is left off when there is none. `aria-invalid="true"` is there
|
|
270
|
+
only while an error is drawn.
|
|
271
|
+
|
|
272
|
+
The error is not live, since a form that fails in several fields at once would speak over itself.
|
|
273
|
+
The page says when errors appear, and on a submit that fails it moves focus to the first invalid
|
|
274
|
+
control or to an error summary. The kit does no validation, so the page decides when to show an
|
|
275
|
+
error, best after a submit is tried rather than while someone is typing.
|
|
276
|
+
|
|
277
|
+
A control is as tall as a button, at both widths, and its text is never under 16px, so a phone does
|
|
278
|
+
not zoom the page when the field is tapped. It sits on the page's own ground, so it reads as a well
|
|
279
|
+
on a card. The hint and the error are in the caption size, the error in the danger color, and an
|
|
280
|
+
invalid control's edge in the danger color too. A disabled control is drawn on the surface ground
|
|
281
|
+
in the muted text. The select is a native `<select>`, its caret drawn as a background, and in forced
|
|
282
|
+
colors the browser's own arrow comes back. A select that shows its options as a list, with
|
|
283
|
+
`multiple` or a `size`, is left as the browser draws it, with no caret.
|
|
284
|
+
|
|
285
|
+
### Callouts
|
|
286
|
+
|
|
287
|
+
```html
|
|
288
|
+
<div class="dk-callout dk-callout-danger">
|
|
289
|
+
<div class="dk-callout-body">
|
|
290
|
+
<p class="dk-callout-title">
|
|
291
|
+
<span class="dk-visually-hidden">Error: </span>The products could not be loaded
|
|
292
|
+
</p>
|
|
293
|
+
<p>Check the file, then load it again.</p>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="dk-callout-actions">
|
|
296
|
+
<button type="button" class="dk-button">Try again</button>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
A callout is `dk-callout` alone for news, on the highlight, or adds `dk-callout-success` or
|
|
302
|
+
`dk-callout-danger` for something that went right or wrong, on that color's own ground. The words
|
|
303
|
+
stay in the page's text color in every tone. A success or danger callout starts its title, or its
|
|
304
|
+
message when it has no title, with a visually hidden "Success: " or "Error: ", so the tone is not
|
|
305
|
+
told by color alone. The title is a paragraph, or a heading from `h2` to `h6` where the callout is a
|
|
306
|
+
section a visitor would jump to, and opens `.dk-callout-body`, the message. The actions, when there
|
|
307
|
+
are some, follow the message, outside it.
|
|
308
|
+
|
|
309
|
+
A callout has no role unless the page gives it one, whatever its tone. A message on the page when it
|
|
310
|
+
loads gets none, since a live region created with its text already in it is read unreliably across
|
|
311
|
+
screen readers and interrupts the reading of the page's title. A message that arrives after the page
|
|
312
|
+
has loaded takes `role="status"`, read when the screen reader is next idle, or `role="alert"`, read
|
|
313
|
+
at once, on `.dk-callout-body`, so the title and the tone's word are read with the message and the
|
|
314
|
+
actions are not. The page puts a live callout on the page empty first and fills it after, which is
|
|
315
|
+
when a screen reader reads it. A page filling a success or danger callout puts the hidden word in
|
|
316
|
+
with the message, as `<span class="dk-visually-hidden">Success: </span>Saved.`, since the word is
|
|
317
|
+
part of what is read. An empty live callout still draws its ground and padding, so a page places it
|
|
318
|
+
where an empty band reads as space, or leaves it out of the layout's flow until it is filled.
|
|
319
|
+
|
|
320
|
+
A callout's edge is transparent, and shows only in forced colors, where the ground is dropped and
|
|
321
|
+
the edge marks where the callout starts and ends.
|
|
322
|
+
|
|
323
|
+
### From React
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { useState } from 'react';
|
|
327
|
+
|
|
328
|
+
import { Button, Callout, InputField, SelectField } from '@jasonsdevspace/design-kit/react';
|
|
329
|
+
|
|
330
|
+
type Props = { readonly error?: string; readonly saved?: string };
|
|
331
|
+
|
|
332
|
+
export const Checkout = ({ error, saved }: Props) => {
|
|
333
|
+
const [gift, setGift] = useState(false);
|
|
334
|
+
return (
|
|
335
|
+
<form>
|
|
336
|
+
<InputField label="Email" hint="Where the receipt goes" error={error} type="email" />
|
|
337
|
+
<SelectField label="Delivery">
|
|
338
|
+
<option value="standard">Standard</option>
|
|
339
|
+
<option value="express">Express</option>
|
|
340
|
+
</SelectField>
|
|
341
|
+
<Button pressed={gift} onClick={() => setGift(!gift)}>
|
|
342
|
+
Gift wrap
|
|
343
|
+
</Button>
|
|
344
|
+
<Button type="submit" accent>
|
|
345
|
+
Place order
|
|
346
|
+
</Button>
|
|
347
|
+
<Callout tone="success" live="status">
|
|
348
|
+
{saved}
|
|
349
|
+
</Callout>
|
|
350
|
+
</form>
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- `Button` takes the props of a `<button>` but `style`, `disabled`, `aria-disabled` and
|
|
356
|
+
`aria-pressed`, and is `type="button"` unless `type` says otherwise. `accent` adds
|
|
357
|
+
`dk-button-accent`. `pressed` writes `aria-pressed`, `false` included, and left undefined makes
|
|
358
|
+
the button an action. The types refuse `accent` and `pressed` together. `off` writes
|
|
359
|
+
`aria-disabled="true"` and puts a click handler that calls `preventDefault()` in place of
|
|
360
|
+
`onClick`, so the page's click handler does not run and a submit button does not submit. Any
|
|
361
|
+
other handler the page passes, for keys or a pointer, still runs, and checks `off` itself. A
|
|
362
|
+
`className` follows the kit's classes.
|
|
363
|
+
- `InputField` takes `label`, and `hint` and `error`, each drawn only when given, an empty string,
|
|
364
|
+
null or false drawing nothing. `id` is the control's, made with React's `useId` unless given, and
|
|
365
|
+
a page gives one when something links to the field, such as an error summary. Every other prop
|
|
366
|
+
goes to the `<input>` but `style` and `aria-invalid`. A `className` follows `dk-input`, and an
|
|
367
|
+
`aria-describedby` of the page's own is listed after the hint and the error.
|
|
368
|
+
- `SelectField` takes the same props, with its children as the options of the `<select>`.
|
|
369
|
+
- `Callout` takes `tone`, `info` unless given, `success` or `danger`, and `title`, drawn at the
|
|
370
|
+
start of the message as a paragraph or, with `headingLevel` from 2 to 6, as that heading, and as
|
|
371
|
+
a paragraph for any other level. `actions` are drawn after the message. `prefix` is the word
|
|
372
|
+
read before the title or the message, the tone's own unless given, and an empty string drops it.
|
|
373
|
+
`live`, `status` or `alert`, puts that role on the message and draws the message even when it is
|
|
374
|
+
empty. A callout takes no other props, so a page that needs a class
|
|
375
|
+
or an id on one puts it on an element around it.
|
|
376
|
+
|
|
377
|
+
## Explainer pages
|
|
378
|
+
|
|
379
|
+
An explainer is the page that says how a demo works, the one the strip's "How it works" link
|
|
380
|
+
leads to. `components.css` styles its layout and the steps, panels, tables and code blocks it
|
|
381
|
+
holds, by the markup below, which every page writes by hand. `./react` draws none of it, since
|
|
382
|
+
none of these pieces has logic to hide. Every rule for them sits in one `:where()`, the layout
|
|
383
|
+
included, so where the layout and a piece set inside it both set something, the later rule in
|
|
384
|
+
the sheet wins, and a page's own rule outside a layer wins over both. A layout written as a bare
|
|
385
|
+
class would outweigh every piece set inside it.
|
|
386
|
+
|
|
387
|
+
### The page
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<a class="dk-skip" href="#main-content">Skip to content</a>
|
|
391
|
+
<div class="dk-strip">...</div>
|
|
392
|
+
<header class="dk-explainer-header"><a href="../">Example</a></header>
|
|
393
|
+
<main id="main-content" tabindex="-1" class="dk-explainer">
|
|
394
|
+
<h1>How Example works</h1>
|
|
395
|
+
<p class="dk-explainer-lead">What the demo does, in a sentence or two.</p>
|
|
396
|
+
<p><a class="dk-button dk-button-accent" href="../">Open the demo</a></p>
|
|
397
|
+
<section>
|
|
398
|
+
<p class="dk-explainer-number" aria-hidden="true">01</p>
|
|
399
|
+
<h2 id="order">What happens when an order comes in</h2>
|
|
400
|
+
<p>...</p>
|
|
401
|
+
<h3>What usually goes wrong</h3>
|
|
402
|
+
<p>...</p>
|
|
403
|
+
</section>
|
|
404
|
+
</main>
|
|
405
|
+
<footer class="dk-footer">...</footer>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The header holds a link back to the demo, outside `main`, so the skip link passes it and reading
|
|
409
|
+
starts at the h1. `main` holds the h1, the lead, the one action as an accent button, and then
|
|
410
|
+
the sections. Each section holds its number, hidden from a screen reader since its heading says
|
|
411
|
+
what the section is, then an h2 with an id, then its text, with an h3 for each part of it.
|
|
412
|
+
|
|
413
|
+
A section is a plain `<section>`, with no `aria-labelledby` or `aria-label`. A named section becomes
|
|
414
|
+
a region landmark, and the page's landmarks stay the strip's navigation, the demo's switches when it
|
|
415
|
+
has some, the header, the main content, the footer and the frames that scroll. A reader moves
|
|
416
|
+
between sections by their headings. The h2 ids are a contract other pages link to, so a page keeps
|
|
417
|
+
an id when it rewords its heading. An in-page link needs no tabindex on the heading, since a jump to
|
|
418
|
+
a fragment moves where the keyboard goes next. Each h2 has a scroll margin, so a jump stops short of
|
|
419
|
+
it and leaves the section's number and rule in sight above it. The kit draws no table of contents,
|
|
420
|
+
since an explainer is short enough to move through by its headings.
|
|
421
|
+
|
|
422
|
+
The header and the explainer start their text at the column's inset, where the strip's links start,
|
|
423
|
+
and every part of the explainer holds to `--dk-measure`, 34rem, no more than 80 characters a line in
|
|
424
|
+
the body size. Each part is spaced from the one before it, so a list, a table, a panel and a code
|
|
425
|
+
block space alike. The h1 is the display size and the lead a step over the body. A section opens on
|
|
426
|
+
a rule in the line color, with 2rem of space above and below it, then its number in the muted
|
|
427
|
+
caption size, its heading in the title size, and its text, with an h3 at the body size.
|
|
428
|
+
|
|
429
|
+
### Steps
|
|
430
|
+
|
|
431
|
+
```html
|
|
432
|
+
<ol class="dk-steps" role="list">
|
|
433
|
+
<li>
|
|
434
|
+
<strong class="dk-steps-title">The order names a product.</strong>
|
|
435
|
+
<span class="dk-steps-note">And how many of it.</span>
|
|
436
|
+
</li>
|
|
437
|
+
<li>
|
|
438
|
+
<span class="dk-steps-arrow" aria-hidden="true">↓</span>
|
|
439
|
+
<strong class="dk-steps-title">It waits its turn.</strong>
|
|
440
|
+
<span class="dk-steps-note">Behind any order for the same product.</span>
|
|
441
|
+
</li>
|
|
442
|
+
</ol>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Steps a demo takes one after another, each in a box of its own with an edge in the line color and
|
|
446
|
+
large corners, in the small text. A step's title is at medium weight, with its note in the muted
|
|
447
|
+
text on a line below. Every step after the first opens with an arrow, a span holding ↓ that the
|
|
448
|
+
sheet sets in the gap above the step, hidden from a screen reader. It is a span because a
|
|
449
|
+
component's selectors allow nothing after a `:where()` but a focus pseudo-class, and a
|
|
450
|
+
pseudo-element cannot sit inside one. The list keeps `role="list"`, since WebKit with VoiceOver
|
|
451
|
+
drops a list's meaning when its markers are gone.
|
|
452
|
+
|
|
453
|
+
### Panels
|
|
454
|
+
|
|
455
|
+
```html
|
|
456
|
+
<div class="dk-panel">
|
|
457
|
+
<p class="dk-panel-title">How the numbers were taken</p>
|
|
458
|
+
<p>...</p>
|
|
459
|
+
</div>
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
A panel sets a part of a page apart on the card ground, `--dk-surface`, with an edge in the line
|
|
463
|
+
color and large corners. The edge is what draws it, since the card ground is close to the page's,
|
|
464
|
+
and it stays in forced colors. A panel is padded 1.5rem, and 1rem at 48rem wide or less, and what it
|
|
465
|
+
holds is spaced 0.75rem from the one before it, a step closer than an explainer's parts. Its title
|
|
466
|
+
is a paragraph, or a heading one level under its section's where a reader would jump to it, and
|
|
467
|
+
inside an explainer it steps down to the body size.
|
|
468
|
+
|
|
469
|
+
A panel differs from a callout in having no tone, no hidden word and no live role, and in being
|
|
470
|
+
at the body size rather than the small text. It has no role at all. It is not an `aside`, since
|
|
471
|
+
inside `main` an aside becomes a complementary landmark, and a box in the reading flow is not
|
|
472
|
+
complementary to it. Nor is it a named `section`, which would be a region landmark, or a
|
|
473
|
+
`figure`.
|
|
474
|
+
|
|
475
|
+
### Tables
|
|
476
|
+
|
|
477
|
+
```html
|
|
478
|
+
<p class="dk-table-title" id="bench-title">Orders a second under each strategy</p>
|
|
479
|
+
<div class="dk-table-scroll" role="region" aria-labelledby="bench-title" tabindex="0">
|
|
480
|
+
<table class="dk-table" aria-labelledby="bench-title">
|
|
481
|
+
<thead>
|
|
482
|
+
<tr>
|
|
483
|
+
<th scope="col">Scenario</th>
|
|
484
|
+
<th scope="col" class="dk-table-number">Orders a second</th>
|
|
485
|
+
<th scope="col" class="dk-table-number">Slowest 1%, ms</th>
|
|
486
|
+
</tr>
|
|
487
|
+
</thead>
|
|
488
|
+
<tbody>
|
|
489
|
+
<tr>
|
|
490
|
+
<th scope="row">Hot product, row lock</th>
|
|
491
|
+
<td class="dk-table-number">700</td>
|
|
492
|
+
<td class="dk-table-number">48.5</td>
|
|
493
|
+
</tr>
|
|
494
|
+
</tbody>
|
|
495
|
+
</table>
|
|
496
|
+
</div>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The title is a paragraph above the frame, never a `<caption>`, since a caption takes the table's
|
|
500
|
+
width and scrolls off a phone with it. The one title names both the frame and the table. Its id is
|
|
501
|
+
unique on the page, and both `aria-labelledby` attributes point to it. The frame is a region with a
|
|
502
|
+
tabindex of 0 at every width, so a keyboard can reach it and scroll it with the arrow keys. A page
|
|
503
|
+
written by hand cannot tell when its table overflows, which changes with zoom and text size, so the
|
|
504
|
+
frame is a region whether or not it scrolls, which costs one tab stop on a wide screen where it does
|
|
505
|
+
not. A frame holds to the measure as the prose does, so a table wider than 34rem scrolls on a wide
|
|
506
|
+
screen too.
|
|
507
|
+
|
|
508
|
+
A column's header is `th scope="col"`, the cell that names a row is `th scope="row"`, and every
|
|
509
|
+
cell of a numeric column, its header included, is `.dk-table-number`. A table is never stacked
|
|
510
|
+
into cards on a phone, since `display: block` on a table's parts drops its table semantics in
|
|
511
|
+
WebKit. It scrolls in its frame instead.
|
|
512
|
+
|
|
513
|
+
A table is in the small text, with each row ruled in the line color. Its column headers are the
|
|
514
|
+
muted text at medium weight, and a row's header is at medium weight too. The outer cells keep no
|
|
515
|
+
padding on their outer side, so the table's text lines up with the prose around it. Figures sit
|
|
516
|
+
to the right and never wrap, while text in a cell wraps and keeps its first line level with the
|
|
517
|
+
rest of its row. A header gives its unit in words a screen reader says well, "Slowest 1%, ms"
|
|
518
|
+
rather than "p99 ms".
|
|
519
|
+
|
|
520
|
+
### Code blocks
|
|
521
|
+
|
|
522
|
+
```html
|
|
523
|
+
<p class="dk-code-title" id="lock-title">The statement that holds the stock</p>
|
|
524
|
+
<pre
|
|
525
|
+
class="dk-code"
|
|
526
|
+
role="region"
|
|
527
|
+
aria-labelledby="lock-title"
|
|
528
|
+
tabindex="0"
|
|
529
|
+
translate="no"
|
|
530
|
+
><code>UPDATE stock SET held = held + 1
|
|
531
|
+
WHERE product_id = $1 AND held < on_hand;</code></pre>
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
A block of code keeps its lines as written and scrolls inside itself, since wrapping would break its
|
|
535
|
+
indentation and show line breaks the code does not have. It is a named region with a tabindex of 0,
|
|
536
|
+
for the same reasons as a table's frame. Its title says what the code is for, such as "Run it
|
|
537
|
+
yourself", rather than naming its language. `translate="no"` keeps a browser's translation of the
|
|
538
|
+
page off the identifiers. A block never takes `lang` for its programming language, since `lang`
|
|
539
|
+
names a human language. The code starts right after `<code>`, since a line break there shows as a
|
|
540
|
+
blank first line, and a `<` or `&` in it is written `<` or `&`.
|
|
541
|
+
|
|
542
|
+
A block sits on the card ground, or on the page's own ground inside a panel, so it reads as set
|
|
543
|
+
into what holds it. Its edge is transparent, and shows only in forced colors. The kit ships no
|
|
544
|
+
script, so a block has no copy button.
|
|
545
|
+
|
|
546
|
+
### Code in running text
|
|
547
|
+
|
|
548
|
+
`base.css` sets code in running text on every page, not only an explainer, at `--dk-text-code`,
|
|
549
|
+
0.9em, so it follows the size of the text around it, a step under it since a mono face looks
|
|
550
|
+
larger at the same size. It breaks anywhere it must, with `overflow-wrap: anywhere`, since a long
|
|
551
|
+
name has no spaces to break at and would push a narrow column wider. Inside a `pre` it takes the
|
|
552
|
+
block's size and keeps its lines whole. It has no ground of its own, and in a link it keeps the
|
|
553
|
+
link's underline.
|
|
554
|
+
|
|
74
555
|
## Layers and palettes
|
|
75
556
|
|
|
76
557
|
The tokens declare `dk.tokens`, `dk.base` and `dk.components`, in that order, and each sheet fills
|
|
77
558
|
its own. A page's own rule outside a layer wins over every rule of the kit's but the cut to motion,
|
|
78
|
-
however little it weighs and wherever its sheet loads. Inside the layers, every selector
|
|
79
|
-
|
|
559
|
+
however little it weighs and wherever its sheet loads. Inside the layers, every selector is either
|
|
560
|
+
one of the kit's own classes, such as `.dk-strip` or `.dk-visually-hidden`, or one `:where()`,
|
|
561
|
+
followed at most by a focus pseudo-class. The base alone follows a `:where()` with a pseudo-element,
|
|
562
|
+
so the border box reaches a page's `::before` and `::after`, and the cut to motion reaches those, a
|
|
563
|
+
dialog's `::backdrop` and each part of a view transition, since a pseudo-element inside a `:where()`
|
|
564
|
+
matches nothing. That adds the weight of one element, which counts for nothing against a page's own
|
|
565
|
+
rule outside a layer.
|
|
80
566
|
|
|
81
567
|
A page with layers of its own names where `dk` goes among them. A layer takes its place where its
|
|
82
568
|
name first appears, so a page whose layers load before the kit's sheets, without naming `dk`, puts
|
|
@@ -103,11 +589,13 @@ color in both themes.
|
|
|
103
589
|
|
|
104
590
|
The default palette is measured in both themes, against the pairings it is meant for. Text, muted
|
|
105
591
|
text, the accent, and the success and danger colors pass 4.5 to 1 on the page, a card and anything
|
|
106
|
-
raised, and a control's edge passes 3 to 1 on each of those. Text on the accent passes 4.5 to 1
|
|
107
|
-
|
|
108
|
-
and
|
|
109
|
-
|
|
110
|
-
|
|
592
|
+
raised, and a control's edge passes 3 to 1 on each of those. Text on the accent passes 4.5 to 1.
|
|
593
|
+
Text, muted text and the accent pass 4.5 to 1 on the highlight and on the success and danger grounds
|
|
594
|
+
as well, and a control's edge passes 3 to 1 on them, so a button or a field keeps its edge inside a
|
|
595
|
+
callout of any tone. The success and danger colors pass 4.5 to 1 on their own grounds. The focus
|
|
596
|
+
ring is drawn in the accent, so it is held as a link is. A pairing outside that list, such as a line
|
|
597
|
+
against its ground, carries no ratio, and a page that sets its own palette answers for its own
|
|
598
|
+
pairings.
|
|
111
599
|
|
|
112
600
|
## The theme contract
|
|
113
601
|
|