@dorsk/tsumikit 0.80.3 → 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.
Files changed (103) hide show
  1. package/README.md +180 -2
  2. package/dist/breakpoints.d.ts +15 -0
  3. package/dist/breakpoints.js +14 -0
  4. package/dist/components/atoms/Artwork.svelte +37 -10
  5. package/dist/components/atoms/Badge.svelte +2 -1
  6. package/dist/components/atoms/Button.svelte +17 -6
  7. package/dist/components/atoms/Button.svelte.d.ts +2 -0
  8. package/dist/components/atoms/Input.svelte +2 -1
  9. package/dist/components/atoms/Slider.svelte +1 -1
  10. package/dist/components/atoms/Text.svelte +4 -4
  11. package/dist/components/atoms/Text.svelte.d.ts +1 -1
  12. package/dist/components/layouts/AppShell.svelte +14 -0
  13. package/dist/components/layouts/Backdrop.svelte +152 -0
  14. package/dist/components/layouts/Backdrop.svelte.d.ts +30 -0
  15. package/dist/components/layouts/NavSection.svelte +2 -2
  16. package/dist/components/molecules/AttachmentList.svelte +1 -1
  17. package/dist/components/molecules/CapBar.svelte +1 -1
  18. package/dist/components/molecules/Composer.svelte +1 -1
  19. package/dist/components/molecules/Dropzone.svelte +1 -1
  20. package/dist/components/molecules/EmptyState.svelte +13 -6
  21. package/dist/components/molecules/EmptyState.svelte.d.ts +3 -0
  22. package/dist/components/molecules/Field.svelte +1 -1
  23. package/dist/components/molecules/FilterInput.svelte +8 -6
  24. package/dist/components/molecules/Meter.svelte +1 -1
  25. package/dist/components/molecules/Metric.svelte +6 -4
  26. package/dist/components/molecules/Modal.svelte +4 -4
  27. package/dist/components/molecules/Pagination.svelte +26 -2
  28. package/dist/components/molecules/Pagination.svelte.d.ts +3 -1
  29. package/dist/components/molecules/Prose.svelte +1 -0
  30. package/dist/components/molecules/RadioGroup.svelte +2 -2
  31. package/dist/components/molecules/RichSelect.svelte.d.ts +2 -2
  32. package/dist/components/molecules/SectionHeader.svelte +1 -1
  33. package/dist/components/molecules/SegmentedControl.svelte +59 -26
  34. package/dist/components/molecules/SegmentedControl.svelte.d.ts +4 -0
  35. package/dist/components/molecules/SortableList.svelte +1 -1
  36. package/dist/components/molecules/StatusBar.svelte +2 -2
  37. package/dist/components/molecules/Tabs.svelte +32 -0
  38. package/dist/components/molecules/Tabs.svelte.d.ts +4 -0
  39. package/dist/components/molecules/ThemePicker.svelte +3 -3
  40. package/dist/components/molecules/VolumeSlider.svelte.d.ts +1 -1
  41. package/dist/components/organisms/DataTable.svelte +2 -2
  42. package/dist/dev-audit.d.ts +19 -0
  43. package/dist/dev-audit.js +259 -0
  44. package/dist/index.d.ts +6 -1
  45. package/dist/index.js +6 -1
  46. package/dist/media.svelte.d.ts +8 -0
  47. package/dist/media.svelte.js +26 -0
  48. package/dist/pending.svelte.d.ts +16 -0
  49. package/dist/pending.svelte.js +75 -0
  50. package/dist/stores/toast.svelte.d.ts +22 -0
  51. package/dist/stores/toast.svelte.js +54 -2
  52. package/dist/styles/app.css +2 -1
  53. package/dist/styles/font.css +19 -0
  54. package/dist/styles/fonts/LICENSE-Inter.txt +92 -0
  55. package/dist/styles/fonts/inter.woff2 +0 -0
  56. package/dist/styles/themes.css +58 -58
  57. package/dist/styles/tokens.css +17 -5
  58. package/dist/url-state.svelte.d.ts +22 -0
  59. package/dist/url-state.svelte.js +64 -0
  60. package/lint/breakpoints.mjs +33 -0
  61. package/lint/cli.mjs +99 -0
  62. package/lint/context.mjs +242 -0
  63. package/lint/index.mjs +97 -0
  64. package/lint/rules/breakpoints.mjs +25 -0
  65. package/lint/rules/img-alt.mjs +13 -0
  66. package/lint/rules/motion.mjs +85 -0
  67. package/lint/rules/no-raw-color.mjs +59 -0
  68. package/lint/rules/no-raw-control.mjs +23 -0
  69. package/lint/rules/no-raw-font.mjs +64 -0
  70. package/lint/rules/one-primary.mjs +74 -0
  71. package/lint/rules/spacing-scale.mjs +66 -0
  72. package/package.json +12 -2
  73. package/principles/01-visibility-of-status.md +32 -0
  74. package/principles/02-one-primary-action.md +31 -0
  75. package/principles/03-emphasise-by-de-emphasising.md +31 -0
  76. package/principles/04-labels-last-resort.md +30 -0
  77. package/principles/05-tint-the-greys.md +30 -0
  78. package/principles/06-depth-from-shade.md +30 -0
  79. package/principles/07-one-accent.md +31 -0
  80. package/principles/08-spacing-scale.md +30 -0
  81. package/principles/09-group-with-space.md +31 -0
  82. package/principles/10-real-typeface-fixed-scale.md +33 -0
  83. package/principles/11-mono-only-for-code.md +30 -0
  84. package/principles/12-measure.md +29 -0
  85. package/principles/13-imagery-sets-the-mood.md +32 -0
  86. package/principles/14-instant-calm-feedback.md +32 -0
  87. package/principles/15-undo-over-confirm.md +30 -0
  88. package/principles/16-empty-states.md +31 -0
  89. package/principles/17-errors-in-plain-words.md +31 -0
  90. package/principles/18-match-the-real-world.md +31 -0
  91. package/principles/19-consistency-and-standards.md +32 -0
  92. package/principles/20-error-prevention.md +31 -0
  93. package/principles/21-recognition-over-recall.md +31 -0
  94. package/principles/22-flexibility-and-shortcuts.md +31 -0
  95. package/principles/23-url-is-state.md +31 -0
  96. package/principles/24-links-are-links.md +31 -0
  97. package/principles/25-visible-focus.md +32 -0
  98. package/principles/26-generous-targets.md +31 -0
  99. package/principles/27-cheap-motion-respect-preferences.md +32 -0
  100. package/principles/28-less-but-better.md +31 -0
  101. package/principles/29-colour-is-not-the-only-cue.md +31 -0
  102. package/principles/30-help-where-needed.md +31 -0
  103. package/principles/README.md +62 -0
@@ -0,0 +1,30 @@
1
+ # Monospace only for code
2
+
3
+ Use a monospace face only for things that are literally code-like: paths, hashes, identifiers, commands.
4
+
5
+ ## Why
6
+
7
+ Practical Typography: monospace is slower to read and takes more width per character. Set as prose or labels it makes an interface feel like a terminal and wastes space; set on genuine code it signals "copy this exactly".
8
+ Source: https://practicaltypography.com/summary-of-key-rules.html
9
+
10
+ ## Do
11
+
12
+ - Mono for file paths, commit hashes, IDs, shell commands and code blocks.
13
+ - Sans for labels, nav, buttons, descriptions, timestamps and numbers in running text.
14
+ - Tabular numerals (`font-variant-numeric: tabular-nums`) in tables and stats so columns line up without going mono.
15
+
16
+ ## Don't
17
+
18
+ - Set a whole sidebar or label system in mono.
19
+ - Use mono for numbers only to align them.
20
+ - Mix two mono faces.
21
+
22
+ ## In tsumikit
23
+
24
+ - `--font-mono` is only applied in code slots; `--font-numeric` and `--font-label` default to sans and can be switched to mono per app.
25
+ - `Text` `variant="code"`, `CodeBlock`, `Kbd`, `WorkingDir`, `GitRef` are the mono components.
26
+ - `Prose` and `renderMarkdown()` set inline code in mono and prose in sans.
27
+
28
+ ## Check
29
+
30
+ Select all monospace text on the screen. For each run ask: would a user copy this character by character? If not, it should be sans.
@@ -0,0 +1,29 @@
1
+ # Keep lines between 45 and 75 characters
2
+
3
+ Body text runs 45 to 75 characters per line so the eye finds the next line without effort.
4
+
5
+ ## Why
6
+
7
+ Practical Typography: lines shorter than about 45 characters break the rhythm; longer than about 75, the eye loses its place on the return sweep. Line length matters more than font size for reading comfort.
8
+ Source: https://practicaltypography.com/summary-of-key-rules.html
9
+
10
+ ## Do
11
+
12
+ - Cap reading columns with a max width in `ch` or `rem`, independent of the viewport.
13
+ - Raise line height slightly as lines get longer.
14
+ - On wide screens, add columns or margins rather than stretching the text.
15
+
16
+ ## Don't
17
+
18
+ - Let descriptions, prose or form help text run the full width of a 1600px window.
19
+ - Shrink the font to make long lines fit.
20
+
21
+ ## In tsumikit
22
+
23
+ - `--measure` sets the default reading width; `--content-max` caps page content.
24
+ - `Prose` and `EmptyState` descriptions are measure-limited; `Container` constrains layouts.
25
+ - Line-height tokens `--lh-normal`, `--lh-snug`.
26
+
27
+ ## Check
28
+
29
+ Count characters on a full line of body text at the widest supported viewport. Between 45 and 75.
@@ -0,0 +1,32 @@
1
+ # Let imagery set the mood, keep the chrome quiet
2
+
3
+ Content imagery carries atmosphere; UI chrome stays neutral, text over images sits on a scrim, and a missing image gets a designed placeholder.
4
+
5
+ ## Why
6
+
7
+ Aesthetic-usability effect: interfaces perceived as beautiful are perceived as easier to use and are forgiven more. A backdrop fading into the page does more than any amount of styled chrome. An empty grey box where an image should be breaks the spell instantly.
8
+ Sources: https://lawsofux.com/aesthetic-usability-effect/ , https://www.refactoringui.com/
9
+
10
+ ## Do
11
+
12
+ - Lead detail pages with a backdrop or hero image that fades into the page background.
13
+ - Place text over images only on a gradient scrim that guarantees contrast.
14
+ - Generate a placeholder from the item itself (initials, a hue derived from its name) when the image is missing or still loading.
15
+ - Give every meaningful image alt text; decorative images get an empty alt.
16
+
17
+ ## Don't
18
+
19
+ - Compete with the image using coloured chrome or heavy borders.
20
+ - Render white text directly on an unpredictable photo.
21
+ - Show an empty or broken-image box, even briefly.
22
+
23
+ ## In tsumikit
24
+
25
+ - `Backdrop` layout for the hero-with-fade; `Scrim` for readable text over images.
26
+ - `Artwork` shows a designed placeholder while loading or missing, via `artworkGradient()`, `artworkHue()` and `initials()`; `Avatar` does the same for people.
27
+ - `Skeleton` for placeholders that are shapes rather than pictures.
28
+ - Lint rule `img-alt` requires alt text on images.
29
+
30
+ ## Check
31
+
32
+ Load the page with images blocked. Every image slot must still show an intentional placeholder. Then re-enable images and verify text over them passes 4.5:1 at its lightest point.
@@ -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.