@dorsk/tsumikit 0.81.0 → 0.82.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/README.md +180 -2
- package/dist/breakpoints.d.ts +15 -0
- package/dist/breakpoints.js +14 -0
- package/dist/components/atoms/Badge.svelte +1 -0
- package/dist/components/atoms/Button.svelte +16 -5
- package/dist/components/atoms/Button.svelte.d.ts +2 -0
- package/dist/components/atoms/Input.svelte +1 -0
- package/dist/components/atoms/Slider.svelte +1 -1
- package/dist/components/atoms/Text.svelte +4 -4
- package/dist/components/atoms/Text.svelte.d.ts +1 -1
- package/dist/components/layouts/AppShell.svelte +14 -0
- package/dist/components/layouts/Backdrop.svelte +152 -0
- package/dist/components/layouts/Backdrop.svelte.d.ts +30 -0
- package/dist/components/layouts/NavSection.svelte +1 -1
- package/dist/components/molecules/AttachmentList.svelte +1 -1
- package/dist/components/molecules/CapBar.svelte +1 -1
- package/dist/components/molecules/Composer.svelte +1 -1
- package/dist/components/molecules/Dropzone.svelte +1 -1
- package/dist/components/molecules/EmptyState.svelte +13 -6
- package/dist/components/molecules/EmptyState.svelte.d.ts +3 -0
- package/dist/components/molecules/Field.svelte +1 -1
- package/dist/components/molecules/FilterInput.svelte +5 -3
- package/dist/components/molecules/Meter.svelte +1 -1
- package/dist/components/molecules/Metric.svelte +3 -1
- package/dist/components/molecules/Modal.svelte +4 -4
- package/dist/components/molecules/Pagination.svelte +26 -2
- package/dist/components/molecules/Pagination.svelte.d.ts +3 -1
- package/dist/components/molecules/Prose.svelte +1 -0
- package/dist/components/molecules/RadioGroup.svelte +2 -2
- package/dist/components/molecules/RichSelect.svelte.d.ts +2 -2
- package/dist/components/molecules/SectionHeader.svelte +1 -1
- package/dist/components/molecules/SegmentedControl.svelte +55 -22
- package/dist/components/molecules/SegmentedControl.svelte.d.ts +4 -0
- package/dist/components/molecules/SortableList.svelte +1 -1
- package/dist/components/molecules/StatusBar.svelte +2 -2
- package/dist/components/molecules/Tabs.svelte +32 -0
- package/dist/components/molecules/Tabs.svelte.d.ts +4 -0
- package/dist/components/molecules/ThemePicker.svelte +3 -3
- package/dist/components/molecules/VolumeSlider.svelte.d.ts +1 -1
- package/dist/components/organisms/DataTable.svelte +2 -2
- package/dist/dev-audit.d.ts +19 -0
- package/dist/dev-audit.js +259 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/media.svelte.d.ts +8 -0
- package/dist/media.svelte.js +26 -0
- package/dist/pending.svelte.d.ts +16 -0
- package/dist/pending.svelte.js +75 -0
- package/dist/stores/toast.svelte.d.ts +22 -0
- package/dist/stores/toast.svelte.js +54 -2
- package/dist/styles/app.css +2 -1
- package/dist/styles/font.css +19 -0
- package/dist/styles/fonts/LICENSE-Inter.txt +92 -0
- package/dist/styles/fonts/inter.woff2 +0 -0
- package/dist/styles/themes.css +58 -58
- package/dist/styles/tokens.css +15 -9
- package/dist/url-state.svelte.d.ts +22 -0
- package/dist/url-state.svelte.js +64 -0
- package/lint/breakpoints.mjs +33 -0
- package/lint/cli.mjs +99 -0
- package/lint/context.mjs +242 -0
- package/lint/index.mjs +97 -0
- package/lint/rules/breakpoints.mjs +25 -0
- package/lint/rules/img-alt.mjs +13 -0
- package/lint/rules/motion.mjs +85 -0
- package/lint/rules/no-raw-color.mjs +59 -0
- package/lint/rules/no-raw-control.mjs +23 -0
- package/lint/rules/no-raw-font.mjs +64 -0
- package/lint/rules/one-primary.mjs +74 -0
- package/lint/rules/spacing-scale.mjs +66 -0
- package/package.json +12 -2
- package/principles/01-visibility-of-status.md +32 -0
- package/principles/02-one-primary-action.md +31 -0
- package/principles/03-emphasise-by-de-emphasising.md +31 -0
- package/principles/04-labels-last-resort.md +30 -0
- package/principles/05-tint-the-greys.md +30 -0
- package/principles/06-depth-from-shade.md +30 -0
- package/principles/07-one-accent.md +31 -0
- package/principles/08-spacing-scale.md +30 -0
- package/principles/09-group-with-space.md +31 -0
- package/principles/10-real-typeface-fixed-scale.md +33 -0
- package/principles/11-mono-only-for-code.md +30 -0
- package/principles/12-measure.md +29 -0
- package/principles/13-imagery-sets-the-mood.md +32 -0
- package/principles/14-instant-calm-feedback.md +32 -0
- package/principles/15-undo-over-confirm.md +30 -0
- package/principles/16-empty-states.md +31 -0
- package/principles/17-errors-in-plain-words.md +31 -0
- package/principles/18-match-the-real-world.md +31 -0
- package/principles/19-consistency-and-standards.md +32 -0
- package/principles/20-error-prevention.md +31 -0
- package/principles/21-recognition-over-recall.md +31 -0
- package/principles/22-flexibility-and-shortcuts.md +31 -0
- package/principles/23-url-is-state.md +31 -0
- package/principles/24-links-are-links.md +31 -0
- package/principles/25-visible-focus.md +32 -0
- package/principles/26-generous-targets.md +31 -0
- package/principles/27-cheap-motion-respect-preferences.md +32 -0
- package/principles/28-less-but-better.md +31 -0
- package/principles/29-colour-is-not-the-only-cue.md +31 -0
- package/principles/30-help-where-needed.md +31 -0
- package/principles/README.md +62 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Feedback is instant and calm
|
|
2
|
+
|
|
3
|
+
Respond at once, show a spinner only after a short delay and keep it long enough not to flicker, and update optimistically when success is likely.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Doherty threshold: under about 400 ms people stay in flow. A spinner that flashes for 50 ms makes a fast response feel broken; one that never appears makes a slow response feel dead. Optimistic updates keep the interface at the user's pace, with rollback as the safety net.
|
|
8
|
+
Sources: https://lawsofux.com/doherty-threshold/ , https://vercel.com/design/guidelines
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Wait about 200 ms before showing a loading indicator, then keep it for at least 500 ms.
|
|
13
|
+
- Apply likely-to-succeed changes immediately and roll back with a message on failure.
|
|
14
|
+
- Keep layout stable while loading: reserve space with skeletons, never shift content.
|
|
15
|
+
- Prefer in-place indicators over page-level overlays.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Show a spinner on every click regardless of duration.
|
|
20
|
+
- Block the whole page for a single row's update.
|
|
21
|
+
- Animate the loading state so much that it draws more attention than the content.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `pending()` helper and `Button` `loadingDelay` / `loadingMin` implement the 200 ms / 500 ms rule; `loading="done"` shows the confirmation tick.
|
|
26
|
+
- `Skeleton`, `Spinner`, `Progress`, `LoadMore` for in-place loading.
|
|
27
|
+
- `toasts.error()` with an action for rollback messages; `toasts.undo()` for reversible changes.
|
|
28
|
+
- Motion tokens `--dur-fast`, `--dur-base`, `--ease-out` keep transitions short.
|
|
29
|
+
|
|
30
|
+
## Check
|
|
31
|
+
|
|
32
|
+
Throttle to a fast connection: no spinner should be visible at all. Throttle to slow 3G: a spinner appears once, stays at least half a second, and nothing on the page shifts when the data lands.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Prefer undo over confirmation
|
|
2
|
+
|
|
3
|
+
Make actions reversible and offer an undo instead of asking "Are you sure?" beforehand.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 3, user control and freedom: people need a clearly marked emergency exit. Confirmation dialogs are read once and then clicked through by habit, so they stop protecting anyone; undo protects every time.
|
|
8
|
+
Sources: https://www.nngroup.com/articles/ten-usability-heuristics/ , https://vercel.com/design/guidelines
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Perform the action, then show "Moved to trash. Undo" for a few seconds.
|
|
13
|
+
- Keep a confirm dialog only for truly irreversible or expensive actions (delete an account, send to many people), and name the consequence in the button ("Delete 12 files").
|
|
14
|
+
- Make Escape, Back and Cancel always available and always do what they say.
|
|
15
|
+
|
|
16
|
+
## Don't
|
|
17
|
+
|
|
18
|
+
- Ask "Are you sure?" with generic OK / Cancel buttons.
|
|
19
|
+
- Trap the user in a flow with no way out.
|
|
20
|
+
- Make Undo appear only in a hidden menu.
|
|
21
|
+
|
|
22
|
+
## In tsumikit
|
|
23
|
+
|
|
24
|
+
- `toasts.undo()` for the reversible pattern; `Toaster` renders the action and keeps it keyboard-reachable.
|
|
25
|
+
- `ConfirmModal` for the remaining irreversible cases, with a labelled destructive `Button` `variant="danger"`.
|
|
26
|
+
- `SwipeRow` exposes undoable row actions on touch.
|
|
27
|
+
|
|
28
|
+
## Check
|
|
29
|
+
|
|
30
|
+
Perform each destructive action on the screen. Either an undo appears for it, or a confirm names exactly what will be lost. "Are you sure?" with OK / Cancel is a failure.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Empty states are design too
|
|
2
|
+
|
|
3
|
+
An empty list explains what belongs there and offers the action that fills it.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 6, recognition over recall, and heuristic 10, help where it is needed: the first screen a new user sees is usually empty, and a blank area makes them guess whether something is broken or missing. The empty state is the onboarding.
|
|
8
|
+
Source: https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Say what goes here in one sentence, in the user's words.
|
|
13
|
+
- Offer the one action that fills it as the primary button.
|
|
14
|
+
- Distinguish "nothing yet", "nothing matches your filter" and "failed to load"; each gets its own copy and action.
|
|
15
|
+
- Keep the layout stable: the empty state occupies the same region the content will.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Render an empty table header with no rows and no message.
|
|
20
|
+
- Show "No data" with no action.
|
|
21
|
+
- Use the same empty state for a filtered-to-nothing result as for a fresh account.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `EmptyState` with `title`, `description`, `actionLabel` and `onAction` (or an `action` snippet for several).
|
|
26
|
+
- `Icon` for the illustration slot; `Callout` for the failed-to-load variant with a retry action.
|
|
27
|
+
- `FilterInput` and `FilterSearchBar` give a "clear filters" path for the filtered-empty case.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Reach every list and table on the screen in three states: new account, filter with no match, load failure. Each must show a message and an action that makes sense for that state.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Errors say what happened and what to do next
|
|
2
|
+
|
|
3
|
+
An error message names the problem in plain language, states the cause if known, and offers the next step.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 9: help users recognise, diagnose and recover from errors. "Request failed with status code 500" tells the user nothing they can act on; "Couldn't reach the indexer. It timed out after 30 s. Check that it's running, then retry." tells them everything.
|
|
8
|
+
Source: https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Lead with what the user wanted and could not get ("Couldn't save the profile").
|
|
13
|
+
- Add the cause in one short clause when you know it.
|
|
14
|
+
- Offer a concrete recovery: retry, edit the field, open settings.
|
|
15
|
+
- Place field errors next to the field and mark the field itself.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Surface raw exception text, status codes or stack traces.
|
|
20
|
+
- Blame the user ("Invalid input").
|
|
21
|
+
- Show the error in a place the user is no longer looking at.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Callout` `tone="error"` for inline errors with an action; `toasts.error()` with an action for transient ones (assertive live region).
|
|
26
|
+
- `Field` shows a field-level error next to its control and links it with `aria-describedby`.
|
|
27
|
+
- Status colour `--danger` always paired with the error icon and text.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Trigger each failure path on the screen (offline, bad input, server error). Every message must be understandable by someone who does not know what HTTP is, and must contain a next step.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Speak the user's language
|
|
2
|
+
|
|
3
|
+
Words, icons and order follow the user's world, not the system's internals.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 2: match between the system and the real world. Users should not have to translate "entity", "node" or "sync job" into the things they actually own. Natural ordering (recent first, alphabetical) and familiar formats (relative time, local units) reduce effort.
|
|
8
|
+
Source: https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Name things what users call them (a "film", not a "media item"; "folder", not "root path").
|
|
13
|
+
- Format dates and durations the way people say them ("2 hours ago", "1 h 42 min").
|
|
14
|
+
- Order lists the way users scan them, and say what the order is.
|
|
15
|
+
- Use icons whose meaning is already established; add a label where it is not.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Expose database field names, enum values or internal IDs as labels.
|
|
20
|
+
- Show ISO timestamps where a relative time would do.
|
|
21
|
+
- Invent a new icon for a familiar action.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Timestamp`, `relativeTime()` and `formatTimestamp()` for human time; `TimeDisplay`, `formatMediaTime()` for durations.
|
|
26
|
+
- `formatCount()` for counts with sensible rounding; `Truncate` keeps long paths readable.
|
|
27
|
+
- `Icon` ships a conventional icon set; `Tooltip` adds a label where meaning is not obvious.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Read every label and message aloud as if to a friend who does not know the codebase. Any word you would have to explain is a label to change.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Consistency beats local cleverness
|
|
2
|
+
|
|
3
|
+
The same thing looks and behaves the same everywhere, and works the way users expect from the apps they already use.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 4 and Jakob's law: users spend most of their time in other applications and bring those expectations with them. Every local deviation is something to learn, and every inconsistency is a moment of doubt.
|
|
8
|
+
Sources: https://www.nngroup.com/articles/ten-usability-heuristics/ , https://lawsofux.com/jakobs-law/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Reuse the kit component for a job rather than restyling a near copy.
|
|
13
|
+
- Keep one vocabulary: the same action has the same label and icon on every screen.
|
|
14
|
+
- Follow platform conventions for selection, navigation, keyboard shortcuts and dialogs.
|
|
15
|
+
- Put the same things in the same place on every page.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Build a one-off button, input or table because the kit's was "almost right".
|
|
20
|
+
- Use "Remove" here and "Delete" there for the same action.
|
|
21
|
+
- Change the primary button's position between pages.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- Atoms are the only place raw controls live; molecules compose them. Lint rule `no-raw-control` flags raw `button`, `input` and `select` in app code.
|
|
26
|
+
- Theme and look tokens (`--r-control`, `--control-height`, `--btn-fill`) make every control share shape and size.
|
|
27
|
+
- `canonicalTone()` and the `Tone` vocabulary unify status naming across components.
|
|
28
|
+
- Lint rule `breakpoints` keeps media queries on the shared 40rem / 48rem / 64rem steps.
|
|
29
|
+
|
|
30
|
+
## Check
|
|
31
|
+
|
|
32
|
+
Take screenshots of the same component type across three pages and lay them side by side. They must be indistinguishable in shape, size and behaviour.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Prevent errors before reporting them
|
|
2
|
+
|
|
3
|
+
Design so the mistake cannot happen: constrain input, show consequences before commit, and make the safe path the default.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 5: a careful design that prevents a problem is better than a good error message. Slips (wrong button) and mistakes (wrong mental model) have different cures: constraints and defaults for slips, previews and plain language for mistakes.
|
|
8
|
+
Source: https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Use the control that only allows valid values: a select, a slider, a date picker, a segmented choice.
|
|
13
|
+
- Validate as the user leaves a field, not after they submit the form.
|
|
14
|
+
- Separate destructive actions from frequent ones in space and style.
|
|
15
|
+
- Default to the safe choice; show the effect of a setting next to it.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Accept free text where the set of valid answers is known.
|
|
20
|
+
- Place "Delete" next to "Save" in the same style.
|
|
21
|
+
- Validate everything only on submit and then scroll the user to the top.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Select`, `RichSelect`, `Combobox`, `Slider`, `SegmentedControl`, `RadioGroup`, `Switch` constrain input.
|
|
26
|
+
- `Field` attaches help text and inline validation; `Fieldset` groups related choices.
|
|
27
|
+
- `Button` `variant="danger"` plus distance in a `Toolbar` keeps destructive actions apart; `ConfirmModal` for the truly irreversible.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
For each input on the screen, try to enter something invalid. The control should refuse or flag it before submit. For each destructive action, check it cannot be hit by a slip from a neighbouring frequent action.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Recognition over recall
|
|
2
|
+
|
|
3
|
+
Show options, state and context on screen instead of making people remember them.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 6 and Miller's law: working memory is small. Visible choices, remembered recent inputs and persistent context (where am I, what is selected) reduce the load; anything a user must remember from a previous screen is a defect.
|
|
8
|
+
Sources: https://www.nngroup.com/articles/ten-usability-heuristics/ , https://lawsofux.com/millers-law/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Show where the user is: breadcrumbs, active nav, page title.
|
|
13
|
+
- Offer suggestions and recent values in inputs instead of requiring exact syntax.
|
|
14
|
+
- Keep the selection visible while acting on it (toolbar shows "3 selected").
|
|
15
|
+
- Reveal keyboard shortcuts next to the actions they trigger.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Require typed identifiers the user saw on another page.
|
|
20
|
+
- Hide the current filter behind a closed panel with no summary.
|
|
21
|
+
- Show a modal that covers the data it asks about.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Breadcrumb`, `NavBar`, `TabBar` and `Tabs` show current location; `SectionHeader` names the block.
|
|
26
|
+
- `Combobox`, `FilterInput` and `suggest()` surface valid values while typing.
|
|
27
|
+
- `Kbd` and `Tooltip` show shortcuts in place; `Popover` and `Drawer` reveal detail without hiding context.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Open any page cold. Can you tell where you are, what is selected and what filters apply without clicking anything? Then try each input without reading docs: valid values must be discoverable from the control itself.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Shortcuts for the experienced, defaults for the rest
|
|
2
|
+
|
|
3
|
+
Frequent actions get accelerators (keyboard, bulk, saved filters) that newcomers can ignore.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 7, flexibility and efficiency of use: the same interface serves first-time and expert users. Experts want fewer steps; novices want fewer choices. Shortcuts layered on top of a simple default serve both.
|
|
8
|
+
Source: https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Add keyboard shortcuts for the top actions and show them in tooltips and menus.
|
|
13
|
+
- Allow bulk selection and bulk actions on lists.
|
|
14
|
+
- Let filters, sort and views be saved or shared via the URL.
|
|
15
|
+
- Support paste, drag and drop and type-ahead wherever they make sense.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Make shortcuts the only way to do something.
|
|
20
|
+
- Override browser or OS shortcuts.
|
|
21
|
+
- Hide bulk actions until a user discovers multi-select by accident.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Kbd` to display shortcuts; `Menu` items show them inline; `Tooltip` for hover hints.
|
|
26
|
+
- `FilterInput` with the `filters` query grammar for saved, shareable filters; `urlState()` keeps them in the URL.
|
|
27
|
+
- `Dropzone`, `FileButton` and `Composer` for drag, drop and paste; `SortableList` for keyboard-reorderable lists.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Do the three most frequent tasks on the screen without a mouse. Each should take fewer keystrokes than clicks. The shortcuts must be visible somewhere on the screen.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# The URL is state
|
|
2
|
+
|
|
3
|
+
Filters, tabs, sort and pagination live in the URL so they survive refresh, Back and sharing.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Vercel and Rauno Freiberg interface guidelines: a view a user cannot link to or return to is lost work. Back must restore both the view and its scroll position; a pasted link must open the same thing the sender saw.
|
|
8
|
+
Sources: https://vercel.com/design/guidelines , https://interfaces.rauno.me/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Encode the active tab, filter query, sort column and page in the query string or path.
|
|
13
|
+
- Restore scroll position on Back and Forward.
|
|
14
|
+
- Make the active state of tabs and segments come from the URL, not from component memory.
|
|
15
|
+
- Use `replaceState` for typed-ahead changes and `pushState` for navigational ones.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Keep tab or filter state only in a store that resets on refresh.
|
|
20
|
+
- Reset to page 1 and clear filters when the user presses Back.
|
|
21
|
+
- Encode state that is not shareable (a hover, an open menu).
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `urlState()` binds a value to a query parameter.
|
|
26
|
+
- `Tabs`, `SegmentedControl` and `Pagination` accept `href` per item so the active state is a real link.
|
|
27
|
+
- `FilterInput` with `serialize()` and `parse()` round-trips filter queries through the URL.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Set a filter, switch a tab, go to page 3, then refresh and press Back twice. Every step must restore exactly what was on screen, and the URL at each step must open the same view in a new window.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Links are links, buttons are buttons
|
|
2
|
+
|
|
3
|
+
Navigation uses an anchor so middle-click, Cmd-click and "open in new tab" work; actions use a button.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Vercel and Rauno Freiberg interface guidelines, and Nielsen heuristic 4: users expect anything that navigates to behave like a link. A `div` with an onclick breaks the context menu, accessibility and the ability to open several things at once.
|
|
8
|
+
Sources: https://vercel.com/design/guidelines , https://interfaces.rauno.me/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- `<a href>` for anything that changes the URL; `<button>` for anything that does not.
|
|
13
|
+
- Style a link as a button when needed, but keep the anchor element.
|
|
14
|
+
- Make whole cards clickable with a single anchor, not a click handler on the container.
|
|
15
|
+
- Mark external links and give them accessible names.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Attach `onclick` navigation to a `div`, `span` or card.
|
|
20
|
+
- Use a button for "View details" that only navigates.
|
|
21
|
+
- Nest interactive elements inside the link.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Link` for text links and button-styled links; `Card` renders an anchor when given `href`.
|
|
26
|
+
- `NavItem`, `TabBar`, `Breadcrumb`, `Pagination`, `Tabs`, `SegmentedControl` take `href` and render real anchors.
|
|
27
|
+
- `AnchorAttributes` type carries the anchor props through.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Middle-click every navigational element on the screen. Each must open in a new tab. Right-click them: "Copy link address" must be present.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Every control shows a visible focus ring
|
|
2
|
+
|
|
3
|
+
Keyboard focus is always visible, on every interactive element, with a ring that contrasts with its surroundings.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
WCAG 2.2 (2.4.7 focus visible, 2.4.11 focus not obscured) and the Vercel interface guidelines: keyboard users navigate by watching focus. Removing the outline without a replacement strands them. `:focus-visible` shows the ring only for keyboard focus, so pointer users are not bothered.
|
|
8
|
+
Sources: https://www.w3.org/WAI/WCAG22/quickref/ , https://vercel.com/design/guidelines
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Use `:focus-visible` with an accent ring at least 2px wide and an offset.
|
|
13
|
+
- Keep focus order the same as visual order.
|
|
14
|
+
- Move focus into dialogs on open and back to the trigger on close.
|
|
15
|
+
- Make the ring visible on dark and light surfaces and inside scroll containers.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Write `outline: none` without a replacement.
|
|
20
|
+
- Rely on a colour change alone to show focus.
|
|
21
|
+
- Let focus disappear behind a sticky header or an overlay.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `--focus-ring`, `--focus-ring-width`, `--focus-ring-offset`, `--focus-ring-offset-inset` tokens; every atom applies them under `:focus-visible`.
|
|
26
|
+
- `Modal`, `Drawer`, `Menu`, `Popover` manage focus trapping and return.
|
|
27
|
+
- Roving focus for `Toolbar`, `RadioGroup`, `Tabs`, `Menu`.
|
|
28
|
+
- Dev-audit warnings flag unlabelled controls (`warnUnlabelled()`).
|
|
29
|
+
|
|
30
|
+
## Check
|
|
31
|
+
|
|
32
|
+
Tab through the entire screen from the address bar. At every stop you must see where focus is, in the order you expected, and nothing must be skipped or trapped.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Generous targets
|
|
2
|
+
|
|
3
|
+
Interactive elements are at least 24px with a pointer and 44px on touch, with no dead zones between them.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Fitts's law: time to hit a target depends on its size and distance; frequent targets should be big and close. WCAG 2.2 (2.5.8) sets the 24px minimum, mobile platform guidance sets 44px. Gaps between adjacent targets prevent mis-taps.
|
|
8
|
+
Sources: https://lawsofux.com/fittss-law/ , https://www.w3.org/WAI/WCAG22/quickref/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Make the whole row or card the target, not just the text inside it.
|
|
13
|
+
- Pad icon buttons to the touch size even when the icon is small.
|
|
14
|
+
- Put frequent actions near where the pointer or thumb already is.
|
|
15
|
+
- Leave at least 8px between adjacent targets.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Ship 16px icon buttons with no padding.
|
|
20
|
+
- Make the hit area smaller than the visible shape.
|
|
21
|
+
- Place a destructive action within thumb reach of a frequent one on mobile.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `--touch-target` (44px) and `--touch-target-compact` (40px); `--control-height` variants for desktop density.
|
|
26
|
+
- `IconButton` and `Button` pad to the target size; `TabBar` and `SwipeRow` are built for thumbs.
|
|
27
|
+
- `AppShell` places primary navigation at the bottom on narrow screens.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Overlay 44px squares on a phone screenshot and 24px on a desktop one. Every interactive element must contain one. Tap between two neighbouring targets: one of them must respond, never neither.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Cheap motion, respected preferences
|
|
2
|
+
|
|
3
|
+
Animate only transform and opacity, keep durations short, and honour reduced-motion, zoom and paste.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Vercel and Rauno Freiberg interface guidelines: transform and opacity run on the compositor; animating layout properties janks. `transition: all` animates things you did not intend. Users who set reduced motion, zoom text or paste passwords are telling you something; blocking them is hostile.
|
|
8
|
+
Sources: https://vercel.com/design/guidelines , https://interfaces.rauno.me/ , https://www.w3.org/WAI/WCAG22/quickref/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Transition `transform` and `opacity`; keep enter around 180 ms and exit shorter.
|
|
13
|
+
- Wrap decorative motion in `prefers-reduced-motion: no-preference`, or reduce to a fade.
|
|
14
|
+
- Allow pinch and text zoom; never set `user-scalable=no`.
|
|
15
|
+
- Allow paste in every input, including password and code fields.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Write `transition: all`.
|
|
20
|
+
- Animate `height`, `width`, `top` or `margin`.
|
|
21
|
+
- Run looping animations the user cannot stop.
|
|
22
|
+
- Disable autocomplete or paste "for security".
|
|
23
|
+
|
|
24
|
+
## In tsumikit
|
|
25
|
+
|
|
26
|
+
- Duration tokens `--dur-fast`, `--dur-base`, `--dur-slow`, `--dur-exit`; easing `--ease-out`, `--ease-spring`; press and shift scales `--press-scale`, `--shift-sm`.
|
|
27
|
+
- The reset collapses motion under `prefers-reduced-motion: reduce`; `MotionSettings` lets users choose in-app.
|
|
28
|
+
- Lint rule `motion` flags `transition: all` and layout-property animations.
|
|
29
|
+
|
|
30
|
+
## Check
|
|
31
|
+
|
|
32
|
+
Enable "reduce motion" in the OS and reload: nothing should move except opacity. Record a performance profile while opening a menu: no layout thrash. Zoom to 200%: everything still usable.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Less, but better
|
|
2
|
+
|
|
3
|
+
Every element earns its place; if removing it loses nothing, remove it.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Dieter Rams's tenth principle, "good design is as little design as possible", and Nielsen heuristic 8, aesthetic and minimalist design: every extra element competes with the ones that matter. Restraint is what makes the remaining elements legible.
|
|
8
|
+
Sources: https://www.vitsoe.com/us/about/good-design , https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Remove decoration that carries no information: borders, icons next to every label, redundant titles.
|
|
13
|
+
- Say things once; a page title does not need to be repeated in a breadcrumb and a heading.
|
|
14
|
+
- Prefer the plain version of a component until the richer one is justified.
|
|
15
|
+
- Review each screen by deleting elements one at a time and asking what was lost.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Add an icon to every button and a badge to every row.
|
|
20
|
+
- Show every field the backend has.
|
|
21
|
+
- Keep a feature on screen because it was expensive to build.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- Components default to the quiet form (`Button` `variant="default"`, `Card` without border, `Text` body) and take opt-in props for emphasis.
|
|
26
|
+
- Looks and micro-interactions are opt-in via `parseLook()` and `LookPicker`, not applied by default.
|
|
27
|
+
- Dev-audit warnings surface redundancy such as duplicate primaries and unlabelled controls.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
For each element on the screen, hide it in dev tools. If the screen still answers "what is this, what state is it in, what can I do", the element was decoration.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Colour is never the only cue
|
|
2
|
+
|
|
3
|
+
State conveyed by colour is also conveyed by an icon, a label or a shape.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
WCAG 1.4.1 (use of colour): about one in twelve men cannot reliably distinguish red from green. A dot that is green or red is a dot. Pairing colour with text or an icon also helps everyone in bad light or on a poor display.
|
|
8
|
+
Source: https://www.w3.org/WAI/WCAG22/quickref/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Pair every status colour with a word or an icon ("Failed", a cross).
|
|
13
|
+
- Use shape or position as well as colour in charts and legends.
|
|
14
|
+
- Underline or otherwise mark links in running text.
|
|
15
|
+
- Test with a colour-blind-safe theme or a simulator.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Show a bare coloured dot as the only indicator of health.
|
|
20
|
+
- Distinguish chart series by hue alone.
|
|
21
|
+
- Mark required fields by colour only.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Badge`, `Dot`, `Callout`, `Gauge`, `Meter` take a `tone` and render an icon or label alongside the colour.
|
|
26
|
+
- `CapBar` and `SegmentedProgress` carry a legend; `Swatch` labels colours.
|
|
27
|
+
- A verified colour-blind-safe theme (Okabe-Ito) is available through `ThemePicker`.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Switch to the colour-blind-safe theme or apply a greyscale filter to a screenshot. Every state that was visible before must still be identifiable.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Help where it is needed
|
|
2
|
+
|
|
3
|
+
Explanation appears next to the thing it explains, at the moment it is needed, and is short.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Nielsen heuristic 10: the best interface needs no documentation, but when help is needed it should be in context, searchable, focused on the task and concise. A manual nobody opens is not help.
|
|
8
|
+
Source: https://www.nngroup.com/articles/ten-usability-heuristics/
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Put one line of help text under the field it concerns.
|
|
13
|
+
- Use tooltips for icon-only controls and for the meaning of a term.
|
|
14
|
+
- Explain a setting's effect next to the setting, in the user's words.
|
|
15
|
+
- Link to deeper documentation from the place where the question arises.
|
|
16
|
+
|
|
17
|
+
## Don't
|
|
18
|
+
|
|
19
|
+
- Replace clear labels with a help icon.
|
|
20
|
+
- Write paragraphs where a sentence will do.
|
|
21
|
+
- Hide the only explanation in a separate help section.
|
|
22
|
+
|
|
23
|
+
## In tsumikit
|
|
24
|
+
|
|
25
|
+
- `Field` renders help and error text under its control and links it with `aria-describedby`.
|
|
26
|
+
- `Tooltip` for terse in-place help; `Popover` and `Disclosure` for a little more.
|
|
27
|
+
- `Callout` `tone="info"` for a short contextual note; `Prose` for longer documentation rendered from markdown.
|
|
28
|
+
|
|
29
|
+
## Check
|
|
30
|
+
|
|
31
|
+
Find every setting, field and icon-only control on the screen. For each, can a new user learn what it does without leaving the screen? If not, add one line of help there.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Design principles
|
|
2
|
+
|
|
3
|
+
The principles tsumikit is built to serve, one file each. They distil Nielsen's usability heuristics, Laws of UX, Refactoring UI, Practical Typography, the Vercel and Rauno Freiberg web interface guidelines, WCAG 2.2 and Dieter Rams. Each file states the principle in one line, why it holds (with the source), do and don't lists, what the kit provides for it, and a check you can run on a screen.
|
|
4
|
+
|
|
5
|
+
They ship in the package: `node_modules/@dorsk/tsumikit/principles/`. Point a design review, or a coding agent, at them.
|
|
6
|
+
|
|
7
|
+
## Index
|
|
8
|
+
|
|
9
|
+
| # | Principle | In one line |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| 01 | [Visibility of system status](01-visibility-of-status.md) | Acknowledge every action within 100 ms and state the outcome. |
|
|
12
|
+
| 02 | [One primary action per view](02-one-primary-action.md) | One filled button; the rest outlined, plain or behind a menu. |
|
|
13
|
+
| 03 | [Emphasise by de-emphasising](03-emphasise-by-de-emphasising.md) | Quiet the surroundings instead of enlarging the subject; three text levels. |
|
|
14
|
+
| 04 | [Labels are a last resort](04-labels-last-resort.md) | Drop labels the value makes obvious; keep the rest small and muted. |
|
|
15
|
+
| 05 | [Tint your greys](05-tint-the-greys.md) | Every grey shares a slight hue; no neutral hexes. |
|
|
16
|
+
| 06 | [Depth from shade, not outlines](06-depth-from-shade.md) | Layers differ by background lightness; borders are faint or absent. |
|
|
17
|
+
| 07 | [One accent, used sparingly](07-one-accent.md) | Accent marks primary, active and focus; status colours mean status. |
|
|
18
|
+
| 08 | [A spacing scale, room to breathe](08-spacing-scale.md) | All spacing from the scale; start airy, then remove. |
|
|
19
|
+
| 09 | [Group with space, not boxes](09-group-with-space.md) | Proximity and shared background group; gaps between groups are larger. |
|
|
20
|
+
| 10 | [A real typeface, a fixed scale](10-real-typeface-fixed-scale.md) | Bundle a screen typeface; fixed type scale, few weights. |
|
|
21
|
+
| 11 | [Monospace only for code](11-mono-only-for-code.md) | Mono for paths, hashes, commands; sans for everything else. |
|
|
22
|
+
| 12 | [Keep lines 45 to 75 characters](12-measure.md) | Cap reading width independent of the viewport. |
|
|
23
|
+
| 13 | [Imagery sets the mood](13-imagery-sets-the-mood.md) | Backdrops with a fade, text on a scrim, designed placeholders. |
|
|
24
|
+
| 14 | [Feedback is instant and calm](14-instant-calm-feedback.md) | Spinner after 200 ms, kept 500 ms; optimistic updates with rollback. |
|
|
25
|
+
| 15 | [Prefer undo over confirmation](15-undo-over-confirm.md) | Reversible actions with undo; confirm only the irreversible. |
|
|
26
|
+
| 16 | [Empty states are design too](16-empty-states.md) | Say what belongs here and offer the action that fills it. |
|
|
27
|
+
| 17 | [Errors in plain words](17-errors-in-plain-words.md) | What happened, why, and what to do next. |
|
|
28
|
+
| 18 | [Speak the user's language](18-match-the-real-world.md) | Names, formats and order from the user's world, not the system's. |
|
|
29
|
+
| 19 | [Consistency beats local cleverness](19-consistency-and-standards.md) | Same thing, same look and behaviour, everywhere; follow conventions. |
|
|
30
|
+
| 20 | [Prevent errors](20-error-prevention.md) | Constrain input, validate early, keep destructive actions apart. |
|
|
31
|
+
| 21 | [Recognition over recall](21-recognition-over-recall.md) | Show location, selection, options and shortcuts on screen. |
|
|
32
|
+
| 22 | [Shortcuts for the experienced](22-flexibility-and-shortcuts.md) | Keyboard, bulk and saved views layered on a simple default. |
|
|
33
|
+
| 23 | [The URL is state](23-url-is-state.md) | Filters, tabs, sort and page survive refresh, Back and sharing. |
|
|
34
|
+
| 24 | [Links are links](24-links-are-links.md) | Anchors navigate, buttons act; middle-click always works. |
|
|
35
|
+
| 25 | [Visible focus](25-visible-focus.md) | Every control shows a focus ring under `:focus-visible`. |
|
|
36
|
+
| 26 | [Generous targets](26-generous-targets.md) | 24px with a pointer, 44px on touch, no dead zones. |
|
|
37
|
+
| 27 | [Cheap motion, respected preferences](27-cheap-motion-respect-preferences.md) | Transform and opacity only; honour reduced motion, zoom, paste. |
|
|
38
|
+
| 28 | [Less, but better](28-less-but-better.md) | Every element earns its place or goes. |
|
|
39
|
+
| 29 | [Colour is never the only cue](29-colour-is-not-the-only-cue.md) | Pair every status colour with an icon, label or shape. |
|
|
40
|
+
| 30 | [Help where it is needed](30-help-where-needed.md) | One line of help beside the thing it explains. |
|
|
41
|
+
|
|
42
|
+
## Designing a screen: the checklist
|
|
43
|
+
|
|
44
|
+
Run through this before calling a screen done. Each line points at the principle with the full check.
|
|
45
|
+
|
|
46
|
+
1. The first viewport answers "what is this, what state is it in, what can I do". (28, 21)
|
|
47
|
+
2. Exactly one filled button; no action bar wraps at 40rem. (02)
|
|
48
|
+
3. Three text levels at most; muted text passes 4.5:1. (03)
|
|
49
|
+
4. Every label left on screen is one the value could not do without. (04)
|
|
50
|
+
5. All greys tinted, layers told apart by shade, borders faint. (05, 06)
|
|
51
|
+
6. Accent only on primary, active, focus, selection; status colours paired with a cue. (07, 29)
|
|
52
|
+
7. Every gap and padding is a scale step; gaps between groups are larger than within. (08, 09)
|
|
53
|
+
8. Bundled typeface, scale sizes only, mono only for code-like text, lines 45 to 75 characters. (10, 11, 12)
|
|
54
|
+
9. Images fade into the page, text over them sits on a scrim, missing images get a placeholder. (13)
|
|
55
|
+
10. Every click changes something within 100 ms; spinners appear after 200 ms and stay 500 ms. (01, 14)
|
|
56
|
+
11. Destructive actions have undo, or a confirm that names the loss. (15, 20)
|
|
57
|
+
12. Empty, filtered-empty and failed states each have copy and an action; errors say what to do next. (16, 17)
|
|
58
|
+
13. Labels and messages read like a person talking; the same thing is named the same everywhere. (18, 19)
|
|
59
|
+
14. Filters, tabs, sort and page are in the URL; navigation is real anchors. (23, 24)
|
|
60
|
+
15. Tab through the screen: focus is visible everywhere; targets are 24px / 44px. (25, 26)
|
|
61
|
+
16. No `transition: all`; reduced motion, zoom and paste all respected. (27)
|
|
62
|
+
17. Help text sits beside the thing it explains; shortcuts are visible. (30, 22)
|