@apliteni/apliteni-ui 0.38.0 → 0.39.1

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 CHANGED
@@ -31,8 +31,11 @@ factories below. Yes → the [React components](#react-components-stateful-surfa
31
31
  Either layer follows the same UI rules — what one page may hold, which component to reach
32
32
  for, the states it owes, how colour and wording work. They live in the **Guidelines** section
33
33
  of Storybook, which opens on
34
- [an overview of every page](https://ui.apli.tech/storybook/?path=/story/guidelines-overview--overview)
35
- and what the kit does and does not yet meet.
34
+ [links to every guideline page](https://ui.apli.tech/storybook/?path=/story/guidelines-overview--overview).
35
+ The same guidance ships as plain Markdown in
36
+ `node_modules/@apliteni/apliteni-ui/guidelines/`. Start with
37
+ [guidelines/overview.md](guidelines/overview.md); Storybook reads these documents too.
38
+
36
39
  [The page](https://ui.apli.tech/storybook/?path=/story/guidelines-the-page--the-page) is the one
37
40
  to read before you design a screen: the limits one page keeps, whatever it is about.
38
41
 
@@ -0,0 +1,105 @@
1
+ # Accessibility minimums
2
+
3
+ ## Minimum target size
4
+
5
+ <!-- rule: target-size -->
6
+
7
+ **Rule:** Give every pointer target at least 24x24 CSS px.
8
+
9
+ **Why:** Larger targets are easier to activate accurately.
10
+
11
+ **Do:** Use a centred 24x24 `::before` hit area for a 19x19 checkbox.
12
+
13
+ **Don't:** Leave a 19x19 target unchanged.
14
+
15
+ **Except:** WCAG 2.5.8 permits spacing, equivalent controls, inline text, user-agent sizes and essential presentation. Overlays must not reach neighbours.
16
+
17
+ ## Focus ring contrast
18
+
19
+ <!-- rule: ring-contrast -->
20
+
21
+ **Rule:** Keep a focus indicator at least 3:1 against every ground it reaches.
22
+
23
+ **Why:** A solid band remains visible where glow alone may disappear.
24
+
25
+ **Do:** Use G2’s solid band with a surface-coloured gap around accent-filled controls.
26
+
27
+ **Don't:** Use the halo alone as the focus indicator.
28
+
29
+ **Except:** The band must reach 4.22:1 on flat ground and 3:1 against actual gap and halo pixels.
30
+
31
+ ## Disabled control legibility
32
+
33
+ <!-- rule: disabled-legibility -->
34
+
35
+ **Rule:** Paint disabled controls with a different ink/surface pair from enabled controls.
36
+
37
+ **Why:** Opaque colours keep disabled text and surfaces readable.
38
+
39
+ **Do:** Use opaque `--disabled-ink`, `--disabled-surface` and `--disabled-border`.
40
+
41
+ **Don't:** Fade the label and box together.
42
+
43
+ **Except:** Labels must reach 3:1. Boxless ghosts use `--disabled-ink-bare`, floored at 4.89:1. Disabled controls must look weaker by changing the pair and removing accent. Only the label-free switch track may fade.
44
+
45
+ ## Touch field text
46
+
47
+ <!-- rule: touch-field-size -->
48
+
49
+ **Rule:** Set coarse-pointer field text to 16px without scaling it down.
50
+
51
+ **Why:** iOS Safari zooms fields below 16px and does not zoom out.
52
+
53
+ **Do:** Apply real 16px text to `input`, `select` and `textarea`, including host fields.
54
+
55
+ **Don't:** Scale 16px down with transforms or smaller computed text.
56
+
57
+ **Except:** Preserve host fields above 16px with `!important` on a more specific selector. Checkbox, radio, range, colour, file and button types do not zoom. Do not remove pinch-zoom with `user-scalable=no` or `maximum-scale=1`. The host owns the viewport tag.
58
+
59
+ ## Body contrast
60
+
61
+ <!-- rule: body-contrast -->
62
+
63
+ **Rule:** Aim for body text contrast close to 7:1, not merely 4.5:1.
64
+
65
+ **Why:** Stronger contrast supports comfortable reading at every size.
66
+
67
+ **Do:** Use body ink at every size, then vary size, weight or spacing for hierarchy.
68
+
69
+ **Don't:** Use pale body ink and rely on larger text for hierarchy.
70
+
71
+ ## Status labels
72
+
73
+ <!-- rule: status-label -->
74
+
75
+ **Rule:** Show every status with both a mark and a word.
76
+
77
+ **Why:** Colour alone is not reliably distinguishable.
78
+
79
+ **Do:** Pair a status icon with text such as “Paused.”
80
+
81
+ **Don't:** Show success using green colour alone.
82
+
83
+ ## Measurable pairs
84
+
85
+ <!-- rule: measurable-pair -->
86
+
87
+ **Rule:** Prefer opaque grounds behind text so contrast can be measured.
88
+
89
+ **Why:** Gradients, filters and translucent layers make contrast uncertain.
90
+
91
+ **Do:** Place text on an opaque surface with a measurable ink pair.
92
+
93
+ **Don't:** Place text over a gradient, filter or translucent layer.
94
+
95
+ ## Keyboard first
96
+
97
+ <!-- rule: keyboard-first -->
98
+
99
+ **Rule:** Implement keyboard behaviour before pointer behaviour and styling.
100
+
101
+ **Why:** Keyboard access defines the interaction before visual polish.
102
+
103
+ **Do:** Make focus, order and keyboard actions work before styling controls.
104
+
105
+ **Don't:** Style pointer interactions before implementing keyboard behaviour.
@@ -0,0 +1,56 @@
1
+ # Colour and theming
2
+
3
+ ## Semantic tokens
4
+
5
+ **Rule:** Add and use a semantic colour token before writing a literal.
6
+
7
+ **Why:** Literals cannot follow the theme or accent.
8
+
9
+ **Do:** Use tokens for ground and ink.
10
+
11
+ **Don't:** Freeze colours at dark-theme values.
12
+
13
+ **Except:** Token definitions set the colours; `#000` inside a mask controls alpha, not colour.
14
+
15
+ <!-- rule: tokens -->
16
+
17
+ ## Stable signals
18
+
19
+ **Rule:** Let the accent change with a sub-theme, but keep `--pink` and `--green` unchanged.
20
+
21
+ **Why:** If danger followed the accent, revoke and go would look identical.
22
+
23
+ **Do:** Keep error as `--pink` under every accent.
24
+
25
+ **Don't:** Point `--pink` at the accent.
26
+
27
+ **Except:** Across `data-theme`, signals change: light deepens `--pink` from `#e97ca5` to `#b63361` so it reads as ink on white.
28
+
29
+ <!-- rule: signals -->
30
+
31
+ ## Accent contrast
32
+
33
+ **Rule:** Use `--accent-strong` for an accent ground and `--accent` for accent ink.
34
+
35
+ **Why:** `--accent` is text on the canvas; its darker sibling, `--accent-strong`, lets white clear AA.
36
+
37
+ **Do:** Use `--accent-strong` behind white text.
38
+
39
+ **Don't:** Use `--accent` as that ground.
40
+
41
+ **Except:** In Phoenix, Ocean and Emerald, both are one token; the difference matters only in Nebula.
42
+
43
+ <!-- rule: accent-strong -->
44
+
45
+ ## Theme checks
46
+
47
+ **Rule:** Check dark and light themes and at least two accents before opening a PR.
48
+
49
+ **Why:** A pair that clears AA in dark can fail in light over a card’s wash.
50
+
51
+ **Do:** Test both themes and two accents.
52
+
53
+ **Don't:** Check only dark mode and one accent.
54
+
55
+
56
+ <!-- rule: both-themes -->
@@ -0,0 +1,79 @@
1
+ # The command palette
2
+
3
+ ## Named things
4
+
5
+ <!-- rule: named-things -->
6
+
7
+ **Rule:** Put something in the palette only if a reader can name it.
8
+
9
+ **Why:** Named items are easier to find and understand.
10
+
11
+ **Do:** Include recognisable commands and numbered records that readers can also reach by clicking through the product.
12
+
13
+ **Don't:** Include “Advanced…”, “More”, a frequency option, or a row that opens the current page.
14
+
15
+ **Except:** One-step settings are commands, such as “Switch to the light theme”; settings needing a choice open a named page.
16
+
17
+ ## Product-owned groups
18
+
19
+ <!-- rule: groups -->
20
+
21
+ **Rule:** Group results by what they are, using the product’s own word for each group.
22
+
23
+ **Why:** Groups tell readers whether a result opens a record, changes location or runs a command.
24
+
25
+ **Do:** Use three sentence-case headings at the 13px label rank for actions, destinations and recent items.
26
+
27
+ **Don't:** Show nine ungrouped rows that hide whether Enter runs a command, opens an invoice or goes to a page.
28
+
29
+ ## Query-based ranking
30
+
31
+ <!-- rule: ranking -->
32
+
33
+ **Rule:** Rank results by how the query matches the name, and let the caller break every tie.
34
+
35
+ **Why:** Caller order keeps preferred commands first, and the kit stores no usage history.
36
+
37
+ **Do:** For “inv”, rank whole-word matches above fragments and names above notes; put the best row’s group first.
38
+
39
+ **Don't:** Put INV-4809 first because of its digits while leaving the matching name “New invoice” last.
40
+
41
+ **Except:** For server results, pass `rank: false` and answer `ui-command-query`; the server ranks the full result, and the kit keeps that order.
42
+
43
+ ## Six keyboard keys
44
+
45
+ <!-- rule: keyboard -->
46
+
47
+ **Rule:** Handle Cmd/Ctrl+K, Up, Down, Enter, Escape and Tab, and leave every other key to the text box.
48
+
49
+ **Why:** These keys open, navigate, run, close and contain the palette without changing text-box behavior.
50
+
51
+ **Do:** Wrap arrow navigation, run the active row with Enter, close the top overlay with Escape, and keep focus inside with Tab.
52
+
53
+ **Don't:** Handle Home, End, Ctrl+N/P/J/K or Alt+Arrow; Home and End belong to the caret under ARIA’s combobox pattern.
54
+
55
+ **Except:** Leave Ctrl+K alone inside another text box, where it means kill-to-end-of-line.
56
+
57
+ ## Focus and announcements
58
+
59
+ <!-- rule: say-it -->
60
+
61
+ **Rule:** Focus the text box, announce the result count, and return focus afterward.
62
+
63
+ **Why:** `aria-activedescendant` identifies the active row while letters and arrows stay in the text box.
64
+
65
+ **Do:** Announce only the count in a polite live region and restore focus on close, even if the command removed the opener.
66
+
67
+ **Don't:** Move focus to each row or reread every row after each keystroke.
68
+
69
+ ## Confirm destructive actions
70
+
71
+ <!-- rule: ask-first -->
72
+
73
+ **Rule:** Never let the palette run a delete by itself.
74
+
75
+ **Why:** A destructive row must be confirmed because readers watch the text box on this fast surface.
76
+
77
+ **Do:** Show an ellipsis and danger ink, and let Enter open a confirm above the palette that answers the first Escape.
78
+
79
+ **Don't:** Omit confirmation; the row becomes disabled and arrow navigation skips it.
@@ -0,0 +1,71 @@
1
+ # Component choice
2
+
3
+ ## Interrupt with a question
4
+
5
+ <!-- rule: interrupt -->
6
+
7
+ **Rule:** Stop the page for a question; leave a statement on the page.
8
+
9
+ **Why:** A confirm keeps keyboard focus until answered; a callout does not interrupt.
10
+
11
+ **Except:** Use a drawer when the answer needs a form instead of two buttons.
12
+
13
+ **Do:** The question stops the page.
14
+
15
+ **Don't:** Show the same question with nothing to answer.
16
+
17
+ ## Show lasting conditions
18
+
19
+ <!-- rule: transient -->
20
+
21
+ **Rule:** Use a toast for what happened and a callout for what is still true.
22
+
23
+ **Why:** A toast dismisses itself, so an active condition disappears too soon.
24
+
25
+ **Except:** A toast with an action may remain because something happened and can still be answered.
26
+
27
+ **Do:** Keep a standing warning on the page.
28
+
29
+ **Don't:** Let the same warning dismiss itself.
30
+
31
+ ## Match controls to panels
32
+
33
+ <!-- rule: panels -->
34
+
35
+ **Rule:** Use tabs() when the control owns a panel and segmented() when it owns nothing.
36
+
37
+ **Why:** A tablist promises a panel and arrow-key support; a strip without either fails the reader.
38
+
39
+ **Except:** Links between locations are neither; the kit’s nav uses aria-current, not a tablist.
40
+
41
+ **Do:** Use a filter strip that owns no panel.
42
+
43
+ **Don't:** Use a tablist that owns no panels.
44
+
45
+ ## Match confirmation scale
46
+
47
+ <!-- rule: scale -->
48
+
49
+ **Rule:** Choose the confirmation by how much of the screen it owns.
50
+
51
+ **Why:** successPanel() is a page block; success() is the page and tells the reader where to go next.
52
+
53
+ **Except:** A block that needs somewhere to go next still uses success(); successPanel() takes two strings.
54
+
55
+ **Do:** Use a block confirmation inside the page.
56
+
57
+ **Don't:** Force a page-sized confirmation into a card.
58
+
59
+ ## Add dropdown search
60
+
61
+ <!-- rule: dropdown-search -->
62
+
63
+ **Rule:** Give a dropdown a search field at ten options, or whenever its options come from data.
64
+
65
+ **Why:** Ten options require scrolling, and data-fed lists need search at any count; matching anywhere in a label also finds later words.
66
+
67
+ **Except:** Below six options, search adds an unnecessary stop; at six to nine, the author decides, adding search for described rows that scroll from the sixth.
68
+
69
+ **Do:** For twenty-nine currencies, type “dollar” instead of scrolling.
70
+
71
+ **Don't:** Show only seven of twenty-nine currencies without search when dollars are scattered through the list.
@@ -0,0 +1,87 @@
1
+ # Dense tables
2
+
3
+ Keep numbers comparable when the dataset needs many rows and columns.
4
+
5
+ ## Use the right surface
6
+
7
+ <!-- rule: surface -->
8
+
9
+ **Why:** Grey or tinted table backgrounds make dense rows harder to separate from the surrounding interface.
10
+
11
+ **Rule:** Put tables on a white surface in light mode and the base canvas in dark mode.
12
+
13
+ **Do:** Use a white surface in light mode and the base canvas in dark mode; let the row edge mark hover.
14
+
15
+ **Don't:** Add a grey zebra or hover fill.
16
+
17
+ ## Choose density without shrinking type
18
+
19
+ <!-- rule: density -->
20
+
21
+ **Why:** Compact rows save space without making values harder to read.
22
+
23
+ **Rule:** Choose compact spacing without changing the type size.
24
+
25
+ **Do:** Keep readable values at the small text rank and use compact vertical spacing only when needed.
26
+
27
+ **Don't:** Shrink the type to fit more rows.
28
+
29
+ ## Align numeric values
30
+
31
+ <!-- rule: numbers -->
32
+
33
+ **Why:** A shared edge lets readers compare magnitudes quickly.
34
+
35
+ **Rule:** Right-align numeric headers and values.
36
+
37
+ **Do:** Right-align numeric headers and values, use tabular numerals, and keep text left-aligned.
38
+
39
+ **Don't:** Align numbers to their labels or sort formatted strings.
40
+
41
+ ## Name the comparison
42
+
43
+ <!-- rule: delta -->
44
+
45
+ **Why:** A sign carries direction without making colour do all the work.
46
+
47
+ **Rule:** Print the sign and name the comparison.
48
+
49
+ **Do:** Print the sign, name the comparison, and let the caller choose whether the change is good or bad.
50
+
51
+ **Don't:** Use colour alone or treat a missing comparison as zero.
52
+
53
+ ## Keep units readable
54
+
55
+ <!-- rule: units -->
56
+
57
+ **Why:** Units carry information even when they are secondary to the value.
58
+
59
+ **Rule:** Keep units smaller and in body ink.
60
+
61
+ **Do:** Keep units smaller and in body ink, and explain abbreviations in reachable text.
62
+
63
+ **Don't:** Hide units in muted text or make readers guess what an abbreviation means.
64
+
65
+ ## Scroll instead of squeezing
66
+
67
+ <!-- rule: overflow -->
68
+
69
+ **Why:** There is no universal column limit that keeps every dataset readable.
70
+
71
+ **Rule:** Scroll columns instead of squeezing them.
72
+
73
+ **Do:** Use a named keyboard-focusable scroll region with a sticky header and pinned identity column.
74
+
75
+ **Don't:** Squeeze columns until values are unreadable or silently remove data.
76
+
77
+ ## Keep updates stable
78
+
79
+ <!-- rule: states -->
80
+
81
+ **Why:** Stable rows and clear states help readers keep their place while results change.
82
+
83
+ **Rule:** Keep the table stable while results update.
84
+
85
+ **Do:** Retain rows while refreshing, report busy state, distinguish no data from no matches and errors, and preserve focus when filters change.
86
+
87
+ **Don't:** Replace the table with a blank state or move focus without telling the reader why.
@@ -0,0 +1,43 @@
1
+ # Destructive actions
2
+
3
+ ## Quiet destructive controls
4
+
5
+ <!-- rule: colour -->
6
+
7
+ **Rule:** Keep destructive controls quiet at rest and turn them `--pink` on hover.
8
+
9
+ **Why:** `--accent` makes a destructive control look ordinary.
10
+
11
+ **Except:** `--pink` also marks a seen error, not an impending action.
12
+
13
+ **Do:** Hover makes the danger row `--pink`.
14
+
15
+ **Don't:** Hover repaints the row `--accent`.
16
+
17
+ ## Name both actions
18
+
19
+ <!-- rule: wording -->
20
+
21
+ **Rule:** Name the action that destroys and the action that does not.
22
+
23
+ **Why:** “OK” explains nothing; “Revoke access” names the cost.
24
+
25
+ **Except:** Use “Cancel” when there is nothing to keep yet, such as a new form or upload.
26
+
27
+ **Do:** Each label makes sense alone.
28
+
29
+ **Don't:** Use “Are you sure?” without saying what will happen.
30
+
31
+ ## Choose confirmation or undo
32
+
33
+ <!-- rule: undo -->
34
+
35
+ **Rule:** Choose confirmation or undo based on reversibility; never use both.
36
+
37
+ **Why:** Confirmation adds a click to undoable actions, while undo is an empty promise for actions that cannot be restored.
38
+
39
+ **Except:** Confirm actions affecting many items: one row is undoable with one click, but a whole selection is not.
40
+
41
+ **Do:** Ask first when nothing can be restored.
42
+
43
+ **Don't:** Offer undo for an already-gone workspace.
@@ -0,0 +1,53 @@
1
+ # Drawers
2
+
3
+ ## One-record drawer
4
+
5
+ <!-- rule: one-record -->
6
+
7
+ **Rule:** Open a drawer to view or change one record without leaving its list.
8
+
9
+ **Why:** Closing the drawer returns the user to the opened row.
10
+
11
+ **Do:** Use a drawer for a short filter, a new key, or a reclassification.
12
+
13
+ **Don't:** Use it for work needing an address, multiple screens, or steps; use a page instead.
14
+
15
+ **Except:** Two-answer questions use confirm. Short forms fit; forms needing steps or more than one screen use a page.
16
+
17
+ ## Grouped content
18
+
19
+ <!-- rule: no-cards -->
20
+
21
+ **Rule:** Group drawer content under headings and never put a card inside it.
22
+
23
+ **Why:** The drawer already provides the edge and raised surface, so cards add unnecessary boxes.
24
+
25
+ **Do:** Use three groups, with a heading over each group’s rows and one line between groups.
26
+
27
+ **Don't:** Put each group in a card inside the drawer.
28
+
29
+ ## Three divider lines
30
+
31
+ <!-- rule: three-lines -->
32
+
33
+ **Rule:** Draw lines under the header, over the footer, and between each group; draw no other lines.
34
+
35
+ **Why:** These lines mark scrolling boundaries and separate groups, not rows.
36
+
37
+ **Do:** Separate header, footer and groups.
38
+
39
+ **Don't:** Add dividers between every row.
40
+
41
+ ## Spaced label rows
42
+
43
+ <!-- rule: rows -->
44
+
45
+ **Rule:** Put each value beside its label and separate rows with space.
46
+
47
+ **Why:** In a drawer up to 560px wide, labels and values stay together at any zoom without row lines.
48
+
49
+ **Do:** Place a short value beside its label, such as `Status — Active`.
50
+
51
+ **Don't:** Put the value at the far edge and use row lines to guide the eye back.
52
+
53
+ **Except:** Put figures readers compare down the panel, such as amounts, in a table using `table()` and right-align them.
@@ -0,0 +1,77 @@
1
+ # Going back
2
+
3
+ ## Link only for child pages
4
+
5
+ <!-- rule: below-a-list -->
6
+
7
+ **Rule:** Add a back link only when the page is under another page.
8
+
9
+ **Why:** It provides the missing move from a record to its parent list.
10
+
11
+ **Do:** From an invoice record, link to the invoice list.
12
+
13
+ **Don't:** Add one to the Invoices sidebar section; it is not a child page.
14
+
15
+ ## Name the destination
16
+
17
+ <!-- rule: name-it -->
18
+
19
+ **Rule:** Name the destination instead of using only “Back”.
20
+
21
+ **Why:** The arrow shows direction, so the words should identify the destination.
22
+
23
+ **Do:** Say “Back to Invoices”, matching the sidebar spelling; hide the arrow from screen readers.
24
+
25
+ **Don't:** Use “Back”, which does not identify the destination in a screen reader’s links list.
26
+
27
+ **Except:** A multi-screen form may use “Back” for the previous step; that is a form button, not this link.
28
+
29
+ ## Link to the parent address
30
+
31
+ <!-- rule: address-not-history -->
32
+
33
+ **Rule:** Link to the parent’s address, preserving filters, sort and page; never use history or `javascript:`.
34
+
35
+ **Why:** Addresses work with new tabs, bookmarks and sharing, while browser Back handles history.
36
+
37
+ **Do:** Use `backLink()` with the parent list address and its current state.
38
+
39
+ **Don't:** Use a history link that fails in a new tab or shared address.
40
+
41
+ **Except:** After search or another record, still link to the parent; browser Back returns to the previous location.
42
+
43
+ ## Choose one navigation method
44
+
45
+ <!-- rule: one-or-the-other -->
46
+
47
+ **Rule:** Put one back link or trail above the title, never both.
48
+
49
+ **Why:** A trail shows several levels; a back link shows one step, and `appShell()` draws only the link when both are provided.
50
+
51
+ **Do:** Align the single line above the title.
52
+
53
+ **Don't:** Repeat the hierarchy with both; the current page is then named three times.
54
+
55
+ ## Keep the section active
56
+
57
+ <!-- rule: section-lit -->
58
+
59
+ **Rule:** Keep the parent section active with `aria-current="true"`, not `"page"`, and use its name in the link.
60
+
61
+ **Why:** The record remains in its list’s section without making the list seem like the current page; `appShell()` does this when given a back link.
62
+
63
+ **Do:** Keep the sidebar row active and say “Back to Invoices”.
64
+
65
+ **Don't:** Leave the section inactive or give it another name.
66
+
67
+ ## Keep the link quiet
68
+
69
+ <!-- rule: quiet -->
70
+
71
+ **Rule:** Style the back link as secondary navigation, using `--text` despite host link colours.
72
+
73
+ **Why:** It appears on every record page and must not compete with the title or main action.
74
+
75
+ **Do:** Use body ink, no box until pointer hover, and a size smaller than the title.
76
+
77
+ **Don't:** Use two filled buttons or make leaving the record more prominent than its main action.
@@ -0,0 +1,67 @@
1
+ # Hover readouts
2
+
3
+ ## Keep readouts over the page
4
+
5
+ <!-- rule: overlay -->
6
+
7
+ **Rule:** Show hover values beside their marks, never inside page layout.
8
+
9
+ **Why:** A changing readout must not move the page.
10
+
11
+ **Do:** Float the value beside a chart point, sparkline, heatmap cell, or truncated cell.
12
+
13
+ **Don't:** Add a value line that grows the card and pushes content down.
14
+
15
+ ## Prefer above the mark
16
+
17
+ <!-- rule: above-the-mark -->
18
+
19
+ **Rule:** Open the readout above the mark; place it below only when space above is clipped and below has more room.
20
+
21
+ **Why:** This keeps the mark and pointer approach clear while keeping the readout on-screen.
22
+
23
+ **Do:** Open a readout below a tall bar near the top of a clipping region.
24
+
25
+ **Don't:** Force it above until the month and value are clipped.
26
+
27
+ ## Give one useful comparison
28
+
29
+ <!-- rule: contents -->
30
+
31
+ **Rule:** Name the point, show its value, and include no more than one comparison.
32
+
33
+ **Why:** Short readouts stay readable and do not cover nearby marks.
34
+
35
+ **Do:** Use three short lines: point, value, one comparison.
36
+
37
+ **Don't:** Wrap long text or place controls in the readout.
38
+
39
+ **Except:** Match the page’s currency and precision.
40
+
41
+ ## Tap to open or close
42
+
43
+ <!-- rule: on-touch -->
44
+
45
+ **Rule:** On touch screens, tap a mark to open the readout and tap again to move or close it.
46
+
47
+ **Why:** Hover is not readable under a finger.
48
+
49
+ **Do:** Tap another mark to move the readout, or tap the same mark or anywhere else to close it.
50
+
51
+ **Don't:** Require a hover gesture on a touch screen.
52
+
53
+ **Except:** The opening tap blocks the mark’s click but still reaches page listeners; the tap that closes the readout may also drill down.
54
+
55
+ ## Provide another access method
56
+
57
+ <!-- rule: not-only-hover -->
58
+
59
+ **Rule:** Never make hover the only way to reach a value.
60
+
61
+ **Why:** Pointer, focus, and tap must all support access, and Escape must dismiss the readout.
62
+
63
+ **Do:** Let focus or tap open the readout, and provide the leading figure plus a table or labelled series summary.
64
+
65
+ **Don't:** Require pointing at one of 365 daily points to find its value.
66
+
67
+ **Except:** Touch-screen focus without a tap must support screen-reader arrival; pointer detection must support laptop mouse hover, finger or pen tap, and keyboard focus restoration. Whether chart marks should be tab stops remains undecided; the kit adds none.
@@ -0,0 +1,57 @@
1
+ # Iconography
2
+
3
+ ## Icon-only controls
4
+
5
+ <!-- rule: icon-only -->
6
+
7
+ **Rule:** Remove a control’s label only for an action on the closed list.
8
+
9
+ **Why:** Without a legend, readers need labels for unfamiliar glyphs.
10
+
11
+ **Except:** The list grows by decision, not review debate. Today: `x` (close or dismiss), `copy` (copy to clipboard), `moreHorizontal` (overflow menu), `moreVertical` (overflow menu), `chevronDown` (expand or collapse), and `chevronUp` (expand or collapse).
12
+
13
+ **Do:** Show an overflow menu without words; keep the Settings label.
14
+
15
+ **Don't:** Use a cog without words, even with a perfect `aria-label`.
16
+
17
+ ## State and action
18
+
19
+ <!-- rule: meaning -->
20
+
21
+ **Rule:** A circled glyph represents a state; a bare glyph represents an action.
22
+
23
+ **Why:** Two meanings force readers to decide from context each time.
24
+
25
+ **Except:** Most glyphs show a thing, not a state or action, including `globe`, `database`, and `layers`. This split guides component choices, not catalogue organisation.
26
+
27
+ **Do:** Use `circleX` to report failure and the bare `x` to close a toast.
28
+
29
+ **Don't:** Use the same `x` for two different meanings.
30
+
31
+ ## Preserve glyph paths
32
+
33
+ <!-- rule: provenance -->
34
+
35
+ **Rule:** Use the Lucide path unchanged, and state when a glyph name differs.
36
+
37
+ **Why:** The shared source keeps the set consistent, while the commit records its origin.
38
+
39
+ **Except:** A brand mark without a Lucide original is allowed. `github` and `linkedin` are the vendor’s own marks and belong in BRAND.
40
+
41
+ **Do:** Keep the Lucide path unchanged and document a different name.
42
+
43
+ **Don't:** Trace or copy a path without recording the difference.
44
+
45
+ ## Status stroke width
46
+
47
+ <!-- rule: stroke-earns-the-bar -->
48
+
49
+ **Rule:** Stroke a status glyph at 1.5 CSS px or wider, or require 4.5:1 text contrast instead of 3:1 graphic contrast.
50
+
51
+ **Why:** Visible width is `stroke-width × box ÷ 24`; below 1.5 CSS px, the stroke reads as a text stem and needs 4.5:1, while above it uses WCAG 1.4.11’s 3:1 graphic bar.
52
+
53
+ **Except:** Non-status glyphs, such as close buttons and chevrons, have no five-status pair to measure; their control owns their contrast.
54
+
55
+ **Do:** Use a stroke at least 1.5 CSS px wide before using the 3:1 graphic contrast minimum.
56
+
57
+ **Don't:** Accept a thinner status stroke at only 3:1 contrast.
@@ -0,0 +1,79 @@
1
+ # Labels and titles
2
+
3
+ ## Use body ink
4
+
5
+ <!-- rule: text-ink -->
6
+
7
+ **Rule:** Use body ink for all words; use size, weight and spacing for hierarchy.
8
+
9
+ **Why:** Muted text can look decorative, especially at small sizes, even when contrast passes.
10
+
11
+ **Do:** Use body ink at xs, sm and base; let size show hierarchy.
12
+
13
+ **Don't:** Use muted ink at different sizes to show rank.
14
+
15
+ ## Limit muted ink
16
+
17
+ <!-- rule: text-ink-exceptions -->
18
+
19
+ **Rule:** Use muted or dim ink only for the three named exception classes.
20
+
21
+ **Why:** A closed list prevents fading from becoming a hierarchy cue.
22
+
23
+ **Do:** Use muted ink for a disabled control or empty field placeholder.
24
+
25
+ **Don't:** Fade timestamps, counts or enabled actions.
26
+
27
+ **Except:** Only: (1) non-word glyphs, such as arrows, chevrons or dismiss marks; (2) colour reporting off, unset, disabled or archived state; (3) a valueless slot, such as an empty field or cell placeholder. Unselected options, “No earlier figure” sentences, counts, timestamps, keyboard shortcuts, enabled actions and empty-state instructions are information, not exceptions.
28
+
29
+ ## Use sentence case
30
+
31
+ <!-- rule: sentence-case -->
32
+
33
+ **Rule:** Write every label in sentence case; never use capitals as style.
34
+
35
+ **Why:** Capitals are slower to read, take more space and make labels louder than their figures.
36
+
37
+ **Except:** Keep inherently capitalised words as written, including acronyms, currency codes and key names such as USD, API and Esc.
38
+
39
+ **Do:** Written and shown in sentence case.
40
+
41
+ **Don't:** Use all capitals, which pulls attention from the figures.
42
+
43
+ ## Rank card titles
44
+
45
+ <!-- rule: title-rank -->
46
+
47
+ **Rule:** Set a card title one rank below the page title and one above the body.
48
+
49
+ **Why:** A page-sized card title divides the page, while body-sized text does not read as a title.
50
+
51
+ **Do:** Use --text-2xl for the page title and --text-lg for the card title.
52
+
53
+ **Don't:** Use a second h1 for the card; it creates two page titles.
54
+
55
+ ## Use heading levels
56
+
57
+ <!-- rule: title-is-heading -->
58
+
59
+ **Rule:** Make a card title a heading one level below the page title, and let the class control its appearance.
60
+
61
+ **Why:** A div title is missing from the page outline.
62
+
63
+ **Do:** Use the next heading level for each card.
64
+
65
+ **Don't:** Put the card title in a div and make it look like a heading.
66
+
67
+ **Except:** A card inside a section with its own h2 uses level 3.
68
+
69
+ ## Name the kind above
70
+
71
+ <!-- rule: eyebrow-names-the-kind -->
72
+
73
+ **Rule:** Place an eyebrow above a title to identify what kind of thing it is, never as a replacement for the title.
74
+
75
+ **Why:** An eyebrow alone cannot describe the card’s content.
76
+
77
+ **Do:** Use the eyebrow for the period and the title for what is shown.
78
+
79
+ **Don't:** Use a label-sized eyebrow as the only title.
@@ -0,0 +1,68 @@
1
+ # Layout and density
2
+
3
+ ## Use the container token
4
+
5
+ <!-- rule: container -->
6
+
7
+ **Rule:** Read page width from `--container`; do not write the number.
8
+
9
+ **Why:** Shared tokens prevent conflicting page widths and topbar overrides.
10
+
11
+ **Do:** Use one token for both kit and site.
12
+
13
+ **Don't:** Put four page-width numbers in different files.
14
+
15
+
16
+ ## Use the measure token
17
+
18
+ <!-- rule: measure -->
19
+
20
+ **Rule:** Use `--measure`, never `--container`, for a reading column.
21
+
22
+ **Why:** `--container` spans the page, while `--measure` limits text beside a sidebar.
23
+
24
+ **Do:** Use `--measure` for a reading column with a sidebar.
25
+
26
+ **Don't:** Treat `--container` and `--measure` as competing page widths.
27
+
28
+ ## Use the spacing scale
29
+
30
+ <!-- rule: density -->
31
+
32
+ **Rule:** Set density with the spacing scale; there is no kit-wide density mode.
33
+
34
+ **Why:** The ten-step scale aligns gaps and padding without runtime density switching.
35
+
36
+ **Do:** Use `--space-3` for rows inside a card.
37
+
38
+ **Don't:** Set separate values such as `13px`, `9px`, `14px`, `6px`, and `17px`.
39
+
40
+ **Except:** `.ui-table--dense` is component-local: use `--space-3` across and `--space-2` down inside base `--space-4`. It still uses the scale. Do not add a compact/comfortable mode, `data-density` attribute, or row-height scale.
41
+
42
+ ## Use panel and prose units
43
+
44
+ <!-- rule: below-the-page -->
45
+
46
+ **Rule:** Below the reading column, use `--panel-*` in `px` for component boxes and `--prose-*` in `ch` for text lines.
47
+
48
+ **Why:** Panel tokens match component widths; prose tokens match text types.
49
+
50
+ **Do:** Use panel `sm/md/lg` for 320/420/560px boxes and prose tokens for caption, lede, body, and dense text.
51
+
52
+ **Don't:** Use t-shirt sizes for prose or put `ch` on a wrapper with two font sizes.
53
+
54
+ **Except:** Put `ch` on the element using its font size. The centred `.ui-section-head` uses `--panel-lg` for its 40px heading and 17px copy. Keep `.ui-footer__brand` at 300px because its flex track controls wrapping.
55
+
56
+ ## Use the three breakpoints
57
+
58
+ <!-- rule: breakpoints -->
59
+
60
+ **Rule:** Use literal breakpoints of 860px, 720px or 560px.
61
+
62
+ **Why:** Media queries cannot read custom properties, and adding a compiler would make published CSS less readable.
63
+
64
+ **Do:** Use `@media (max-width: 860px)`.
65
+
66
+ **Don't:** Use `@media (max-width: var(--panel-lg))` or add other breakpoint values.
67
+
68
+ **Except:** `560` also equals `--panel-lg`, and `860` equals `--measure`, but viewport breakpoints and box widths are independent. Do not imply a link between them.
@@ -0,0 +1,29 @@
1
+ # Microcopy and tone
2
+
3
+ ## Name the current state
4
+
5
+ <!-- rule: state-not-destination -->
6
+
7
+ **Rule:** Name a control for its current state, and rename it when the state changes.
8
+
9
+ **Why:** A name that is correct only once becomes unhelpful later.
10
+
11
+ **Do:** Name the checked switch “In-app notifications, on.”
12
+
13
+ **Don't:** Name the checked switch “Turn off in-app notifications.”
14
+
15
+ **Except:** An icon-only toggle may add the result: “Theme: Dark. Switch to light.”
16
+
17
+ ## Name every control
18
+
19
+ <!-- rule: never-nameless -->
20
+
21
+ **Rule:** Give every control a name, including controls without visible text.
22
+
23
+ **Why:** The glyph is aria-hidden, so the label is the control’s only accessible name.
24
+
25
+ **Do:** Name the button “Dismiss.”
26
+
27
+ **Don't:** Leave the icon button unnamed.
28
+
29
+ **Except:** An identifier is not a name: the `name` option of `segmented()` seeds a data hook and is never read aloud.
@@ -0,0 +1,53 @@
1
+ # Motion
2
+
3
+ ## Move after load
4
+
5
+ <!-- rule: after-load -->
6
+
7
+ **Rule:** Animate anything that appears or leaves after page load.
8
+
9
+ **Why:** Motion shows where content comes from; content present at load stays still.
10
+
11
+ **Do:** Slide a panel in from its edge.
12
+
13
+ **Don't:** Make new content appear in one frame.
14
+
15
+ **Except:** Update counts, ranges, and status lines immediately.
16
+
17
+ ## Match duration to motion
18
+
19
+ <!-- rule: durations -->
20
+
21
+ **Rule:** Use 150ms for controls, 250ms for surfaces, 400ms for entrances, and 80ms (`--dur-instant`) for presses.
22
+
23
+ **Why:** Use only `--dur-fast`, `--dur-med`, `--dur-slow`, and `--dur-instant` to keep one tempo.
24
+
25
+ **Do:** Use the matching duration token.
26
+
27
+ **Don't:** Use an unrelated duration such as 300ms.
28
+
29
+ ## Use the kit curves
30
+
31
+ <!-- rule: easing -->
32
+
33
+ **Rule:** Use `--ease` for surfaces that come and go, `--ease-out` for content that arrives and stays, and `linear` for visibility.
34
+
35
+ **Why:** Visibility keeps its old value during a transition, so easing adds no benefit.
36
+
37
+ **Do:** Transition visibility with `linear`.
38
+
39
+ **Don't:** Use CSS `ease` instead of `--ease`.
40
+
41
+ ## Honor reduced motion
42
+
43
+ <!-- rule: reduced -->
44
+
45
+ **Rule:** When a reader asks for less motion, change immediately.
46
+
47
+ **Why:** Published bundles cap animations and transitions at 0.01ms under `prefers-reduced-motion`, so focus reaches a visible control without delay.
48
+
49
+ **Do:** Open drawers and confirmations without internal transitions.
50
+
51
+ **Don't:** Keep a transition running after reduced motion is requested.
52
+
53
+ **Except:** Spinners and skeletons also stop; the surrounding region announces the wait.
@@ -0,0 +1 @@
1
+ # Guidelines
@@ -0,0 +1,89 @@
1
+ # Pagination
2
+
3
+ ## Use the server count
4
+
5
+ <!-- rule: counted-total -->
6
+
7
+ **Rule:** Use the total row count returned by the server when it is available.
8
+
9
+ **Why:** Readers need accurate ranges and totals, while some APIs can only report whether more rows exist.
10
+
11
+ **Do:** Show the current row range and the server’s total.
12
+
13
+ **Don't:** Discard a completed `COUNT(*)` result.
14
+
15
+ ## Choose the main jump
16
+
17
+ <!-- rule: the-jump -->
18
+
19
+ **Rule:** Use steps for filtering and sorting, numbers for short lists, or jump for a specific page.
20
+
21
+ **Why:** Filtering and sorting usually need quick jumps, while short lists can use numbered pages.
22
+
23
+ **Except:** Use numbered pages for short browsable lists such as changelogs and galleries.
24
+
25
+ **Do:** Use four fixed controls so a ledger’s first and last pages are one press away.
26
+
27
+ **Don't:** On page 25 of 49, show only 1, 24, 26, and 49 as direct choices.
28
+
29
+ ## Disable ends in place
30
+
31
+ <!-- rule: ends-disable -->
32
+
33
+ **Rule:** Disable controls at each end without removing them.
34
+
35
+ **Why:** Fixed controls prevent layout shifts and tell screen-reader users when they reached an end.
36
+
37
+ **Do:** Keep First and Prev in place and mark them unavailable; native `disabled` removes them from Tab order.
38
+
39
+ **Don't:** Remove Prev on page one and move the other controls left.
40
+
41
+ ## Hide one-page steps
42
+
43
+ <!-- rule: one-page -->
44
+
45
+ **Rule:** Do not show pagination steps when a table has one page.
46
+
47
+ **Why:** One-page controls provide no action.
48
+
49
+ **Do:** For 12 rows, show only the page-size control, or render nothing if no size choices exist.
50
+
51
+ **Don't:** Show two permanently useless buttons.
52
+
53
+ ## Keep page turns stable
54
+
55
+ <!-- rule: page-turn -->
56
+
57
+ **Rule:** Keep controls, rows, positions, and heights stable while the next page loads.
58
+
59
+ **Why:** Readers may press the same control twice during loading.
60
+
61
+ **Do:** Disable the controls, mark the pager busy, and keep the range readable.
62
+
63
+ **Don't:** Use a placeholder that collapses the pager or moves the next click target.
64
+
65
+ ## Announce only the range
66
+
67
+ <!-- rule: announce-range -->
68
+
69
+ **Rule:** Politely announce the complete result range, such as “1–100 of 4,812,” and nothing else.
70
+
71
+ **Why:** Rows can change without moving focus, so repeating each change creates unnecessary announcements.
72
+
73
+ **Except:** HTML callers that replace the pager must restore focus themselves; at an end, React restores focus to the remaining step after native `disabled` moves it to the body.
74
+
75
+ **Do:** Keep focus on the pressed control and announce the new range once.
76
+
77
+ **Don't:** Announce the same page change through the live region, page-size control, and every button.
78
+
79
+ ## Offer consumer page sizes
80
+
81
+ <!-- rule: page-size -->
82
+
83
+ **Rule:** Offer page sizes from 25, 50, and 100, starting at 100, and let consumers choose and persist the selection.
84
+
85
+ **Why:** Consumers may need different choices and can save them in a URL or profile across reloads.
86
+
87
+ **Do:** Place rows per page between the count and pagination steps.
88
+
89
+ **Don't:** Force one size when readers must work through 4,812 rows.
@@ -0,0 +1,39 @@
1
+ # Stat bands
2
+
3
+ ## Use a stat band
4
+
5
+ <!-- rule: a-band-not-a-card -->
6
+
7
+ **Rule:** Put key figures in a stat band, not in a card of parts.
8
+
9
+ **Why:** Each figure stays easy to compare and understand.
10
+
11
+ **Do:** Use one card per figure by default, with the figure largest, an arrow, a signed change, and one comparison caption above the row.
12
+
13
+ **Don't:** Use heading-sized figures with a capitalised 10px status chip and no comparison basis.
14
+
15
+ ## Colour by meaning
16
+
17
+ <!-- rule: tone-not-direction -->
18
+
19
+ **Rule:** Colour a change by whether it is good news, not by whether it rises or falls.
20
+
21
+ **Why:** Direction alone does not show whether a change is beneficial.
22
+
23
+ **Except:** Omitted tone and explicit “neutral” keep the arrow and comparison but use no colour for unscored volume, reader-dependent meaning, or net cashflow already explained by income less cost.
24
+
25
+ **Do:** Use four verdicts: rising Income and Cost use opposite colours; falling Unclassified is green; undeclared Net cashflow stays grey.
26
+
27
+ **Don't:** Colour four figures by sign, making three rises green, praising cost growth, and showing the year’s cleanup as red.
28
+
29
+ ## State the comparison basis
30
+
31
+ <!-- rule: say-the-basis -->
32
+
33
+ **Rule:** State in reachable text what each change is measured against.
34
+
35
+ **Why:** Percentages need a basis that readers can find.
36
+
37
+ **Do:** Put the basis once in a caption referenced by each change, or beside an individual exception; keep the caption before and outside every figure.
38
+
39
+ **Don't:** Use a title attribute, put the note below the tiles, place it inside the first figure, omit the basis, or nest captions.
@@ -0,0 +1,57 @@
1
+ # The full state set
2
+
3
+ ## Focus rings
4
+
5
+ <!-- rule: focus-visible -->
6
+
7
+ **Rule:** Give every focusable control the same `--ring`, and show it only on `:focus-visible`.
8
+
9
+ **Why:** One solid band and halo keep focus consistent while the browser decides when to show it.
10
+
11
+ **Do:** Button and input share one ring.
12
+
13
+ **Don't:** The input draws its own ring.
14
+
15
+ **Except:** Text-entry controls may match `:focus-visible` on mouse focus. Surface backgrounds set the gap colour and recompose `--ring`; adjust width and colour at the root.
16
+
17
+ ## Busy controls
18
+
19
+ <!-- rule: busy -->
20
+
21
+ **Rule:** Make busy controls disabled: use `aria-busy` and a real disabled state from one flag.
22
+
23
+ **Why:** A control that still accepts clicks can submit twice.
24
+
25
+ **Do:** Set `disabled` and `aria-busy` together.
26
+
27
+ **Don't:** Show “Saving…” while still accepting clicks.
28
+
29
+ **Except:** Disabled controls are excluded from the WCAG 1.4.3 contrast gate, so busy controls must remain visually legible.
30
+
31
+ ## Marked errors
32
+
33
+ <!-- rule: error-in-markup -->
34
+
35
+ **Rule:** Put errors in markup, not only in paint, and connect each message to its field.
36
+
37
+ **Why:** Red alone communicates the state only to sighted readers.
38
+
39
+ **Do:** The field reads its reason with it.
40
+
41
+ **Don't:** Use the same red with no connected reason.
42
+
43
+ **Except:** Put `required` in the attribute, not the label wording; the asterisk is decoration and hidden from assistive technology.
44
+
45
+ ## Screen loading
46
+
47
+ <!-- rule: loading -->
48
+
49
+ **Rule:** Design pending state for the whole screen, not only its button, and announce it.
50
+
51
+ **Why:** Silent loading gives screen-reader users no event.
52
+
53
+ **Do:** Announce the coming shape in a status region.
54
+
55
+ **Don't:** Let only the button know while the page reads as finished.
56
+
57
+ **Except:** A toast has its own live region, so a screen reporting through the toast stack needs no second one.
@@ -0,0 +1,123 @@
1
+ # The page
2
+
3
+ ## Keep one shell
4
+
5
+ <!-- rule: layout -->
6
+
7
+ **Rule:** Use one shell layout throughout the product: either the rail alone or the rail with a bar above the page containing search and the reader’s menu.
8
+
9
+ **Why:** Readers learn where search and account controls are once.
10
+
11
+ **Do:** Use the same shell on every screen.
12
+
13
+ **Don't:** Move search or the account menu between pages.
14
+
15
+ ## Choose content width
16
+
17
+ <!-- rule: width -->
18
+
19
+ **Rule:** Use the wide column for mostly tables and boards, and the centred column for mostly reading and forms.
20
+
21
+ **Why:** Tables need space, while reading needs a line length readers can follow.
22
+
23
+ **Do:** Show every table column at once in the wide column.
24
+
25
+ **Don't:** Put the same table in the centred column when its last column goes off-screen.
26
+
27
+ ## Order page headings
28
+
29
+ <!-- rule: head -->
30
+
31
+ **Rule:** Put the way back first, then the page title and a short introduction, with everything else below.
32
+
33
+ **Why:** Readers should know which page they are on before choosing filters.
34
+
35
+ **Do:** Back link → title → introduction → filters.
36
+
37
+ **Don't:** Put filters above the title.
38
+
39
+ ## Use one page title
40
+
41
+ <!-- rule: one-h1 -->
42
+
43
+ **Rule:** Give each page exactly one page title.
44
+
45
+ **Why:** Readers using heading navigation need one clear place to land.
46
+
47
+ **Do:** Use one h1 for the page.
48
+
49
+ **Don't:** Use multiple page titles or none.
50
+
51
+ ## Follow heading levels
52
+
53
+ <!-- rule: outline -->
54
+
55
+ **Rule:** Use headings in order without skipping levels, and stop at h3: page, section, then group.
56
+
57
+ **Why:** Skipped levels hide structure, and a fourth level usually means the page should be split.
58
+
59
+ **Do:** Keep each heading level under the one above it.
60
+
61
+ **Don't:** Make readers guess which content belongs together.
62
+
63
+ ## Emphasize one action
64
+
65
+ <!-- rule: one-primary -->
66
+
67
+ **Rule:** Use one filled button for the main action and give all other actions less emphasis.
68
+
69
+ **Why:** Equal emphasis makes the main action unclear.
70
+
71
+ **Do:** Make Create token primary, with Import agents and Export less prominent.
72
+
73
+ **Don't:** Give all three actions equal emphasis.
74
+
75
+ ## Limit card stacks
76
+
77
+ <!-- rule: stacking -->
78
+
79
+ **Rule:** Stack no more than six cards, and never put cards inside other cards.
80
+
81
+ **Why:** Too many cards make readers scroll past other content to find their target.
82
+
83
+ **Do:** Show up to six cards.
84
+
85
+ **Don't:** Show twelve cards or nest cards inside cards.
86
+
87
+ ## Leave pages closed
88
+
89
+ <!-- rule: at-rest -->
90
+
91
+ **Rule:** Show the page uncovered until the reader chooses to open something.
92
+
93
+ **Why:** An overlay on arrival interrupts reading and can move keyboard focus.
94
+
95
+ **Do:** Let the reader open a menu or dialog.
96
+
97
+ **Don't:** Open one automatically when the page loads.
98
+
99
+ ## Keep table density consistent
100
+
101
+ <!-- rule: density -->
102
+
103
+ **Rule:** Use compact rows in every table on the page, or use compact rows in none of them.
104
+
105
+ **Why:** Consistent row heights make tables feel like one page.
106
+
107
+ **Do:** Use compact rows in both tables.
108
+
109
+ **Don't:** Use compact rows in one table and roomy rows in another.
110
+
111
+ **Except:** Use the spacing required by the busiest table.
112
+
113
+ ## Write a useful introduction
114
+
115
+ <!-- rule: lede -->
116
+
117
+ **Rule:** Keep the introduction to two sentences or fewer and add information the title does not give.
118
+
119
+ **Why:** Repeating the title wastes the reader’s time.
120
+
121
+ **Do:** Add useful context below “Payouts.”
122
+
123
+ **Don't:** Write “This is the payouts page” below “Payouts.”
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.38.0",
3
+ "version": "0.39.1",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "exports": {
21
21
  ".": "./src/index.js",
22
+ "./guidelines/*": "./guidelines/*",
22
23
  "./motion": "./src/motion.js",
23
24
  "./inline": "./src/inline.js",
24
25
  "./css": "./src/index.css",
@@ -36,6 +37,7 @@
36
37
  "src",
37
38
  "!src/**/*.test.js",
38
39
  "react/dist",
40
+ "guidelines",
39
41
  "README.md"
40
42
  ],
41
43
  "sideEffects": [
@@ -99,5 +99,9 @@
99
99
  }
100
100
  .rx-caret {
101
101
  margin-left: 2px;
102
+ width: 16px;
103
+ height: 16px;
104
+ flex-shrink: 0;
105
+ stroke-width: 2.4;
102
106
  color: var(--muted);
103
107
  }
@@ -899,7 +899,23 @@ function DataTable({
899
899
  onSortChange?.(next);
900
900
  toFirstPage();
901
901
  };
902
- const caret = (k) => sort.key === k ? sort.dir === 1 ? " \u25B2" : " \u25BC" : " \u2195";
902
+ const caret = (k) => /* @__PURE__ */ jsxs8(
903
+ "svg",
904
+ {
905
+ className: "rx-caret",
906
+ viewBox: "0 0 24 24",
907
+ fill: "none",
908
+ stroke: "currentColor",
909
+ strokeLinecap: "round",
910
+ strokeLinejoin: "round",
911
+ "aria-hidden": "true",
912
+ focusable: "false",
913
+ children: [
914
+ (sort.key !== k || sort.dir === 1) && /* @__PURE__ */ jsx10("path", { d: sort.key === k ? "m8 14 4-4 4 4" : "m8 9 4-4 4 4" }),
915
+ (sort.key !== k || sort.dir === -1) && /* @__PURE__ */ jsx10("path", { d: sort.key === k ? "m8 10 4 4 4-4" : "m8 15 4 4 4-4" })
916
+ ]
917
+ }
918
+ );
903
919
  const pageAllOn = selectable && slice.length > 0 && slice.every((r) => selected.has(r.name));
904
920
  return /* @__PURE__ */ jsxs8(Fragment2, { children: [
905
921
  /* @__PURE__ */ jsx10(
@@ -937,7 +953,7 @@ function DataTable({
937
953
  "aria-sort": sort.key === c.key ? sort.dir === 1 ? "ascending" : "descending" : c.sortable && sortIsKnown ? "none" : void 0,
938
954
  children: c.sortable ? /* @__PURE__ */ jsxs8("button", { type: "button", className: "rx-sort", onClick: () => onSort(c.key), children: [
939
955
  c.label,
940
- /* @__PURE__ */ jsx10("span", { className: "rx-caret", "aria-hidden": "true", children: caret(c.key) })
956
+ caret(c.key)
941
957
  ] }) : c.label
942
958
  },
943
959
  c.key