@momoi-labs/kiso 0.1.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/LICENSE +21 -0
- package/README.md +63 -0
- package/kiso/AGENTS.md +50 -0
- package/kiso/README.md +62 -0
- package/kiso/docs/accessibility.md +87 -0
- package/kiso/docs/brand.md +95 -0
- package/kiso/docs/components/README.md +58 -0
- package/kiso/docs/components/alert.md +158 -0
- package/kiso/docs/components/badge.md +135 -0
- package/kiso/docs/components/breadcrumb.md +66 -0
- package/kiso/docs/components/button.md +168 -0
- package/kiso/docs/components/card.md +154 -0
- package/kiso/docs/components/checkbox.md +91 -0
- package/kiso/docs/components/command-palette.md +165 -0
- package/kiso/docs/components/drawer.md +79 -0
- package/kiso/docs/components/dropdown-menu.md +178 -0
- package/kiso/docs/components/empty-state.md +142 -0
- package/kiso/docs/components/form-field.md +115 -0
- package/kiso/docs/components/header.md +79 -0
- package/kiso/docs/components/helper-text.md +86 -0
- package/kiso/docs/components/icon-button.md +161 -0
- package/kiso/docs/components/input.md +99 -0
- package/kiso/docs/components/label.md +88 -0
- package/kiso/docs/components/link.md +152 -0
- package/kiso/docs/components/modal-dialog.md +82 -0
- package/kiso/docs/components/navigation.md +68 -0
- package/kiso/docs/components/page-header.md +70 -0
- package/kiso/docs/components/pagination.md +129 -0
- package/kiso/docs/components/popover.md +74 -0
- package/kiso/docs/components/search.md +147 -0
- package/kiso/docs/components/select.md +105 -0
- package/kiso/docs/components/sidebar.md +74 -0
- package/kiso/docs/components/skeleton.md +140 -0
- package/kiso/docs/components/spinner.md +125 -0
- package/kiso/docs/components/switch.md +92 -0
- package/kiso/docs/components/table.md +255 -0
- package/kiso/docs/components/tabs.md +69 -0
- package/kiso/docs/components/textarea.md +91 -0
- package/kiso/docs/components/toast.md +80 -0
- package/kiso/docs/components/tooltip.md +162 -0
- package/kiso/docs/components/validation-message.md +96 -0
- package/kiso/docs/data-interfaces.md +309 -0
- package/kiso/docs/evolution.md +35 -0
- package/kiso/docs/patterns/README.md +40 -0
- package/kiso/docs/patterns/application-shell.md +106 -0
- package/kiso/docs/patterns/command-palette.md +142 -0
- package/kiso/docs/patterns/confirmations.md +158 -0
- package/kiso/docs/patterns/crud.md +139 -0
- package/kiso/docs/patterns/dashboard.md +102 -0
- package/kiso/docs/patterns/destructive-actions.md +137 -0
- package/kiso/docs/patterns/developer-oriented-interfaces.md +162 -0
- package/kiso/docs/patterns/empty-states.md +76 -0
- package/kiso/docs/patterns/errors.md +93 -0
- package/kiso/docs/patterns/filtering.md +147 -0
- package/kiso/docs/patterns/keyboard-shortcuts.md +155 -0
- package/kiso/docs/patterns/large-data-tables.md +182 -0
- package/kiso/docs/patterns/list-detail.md +118 -0
- package/kiso/docs/patterns/loading.md +80 -0
- package/kiso/docs/patterns/login-authentication.md +101 -0
- package/kiso/docs/patterns/onboarding.md +94 -0
- package/kiso/docs/patterns/pagination.md +121 -0
- package/kiso/docs/patterns/permission-denied.md +84 -0
- package/kiso/docs/patterns/search.md +150 -0
- package/kiso/docs/patterns/settings.md +100 -0
- package/kiso/docs/patterns/sorting.md +121 -0
- package/kiso/docs/principles.md +122 -0
- package/kiso/docs/tokens.md +95 -0
- package/kiso/docs/voice-and-tone.md +154 -0
- package/package.json +42 -0
- package/tokens/build/tokens.css +143 -0
- package/tokens/build/tokens.d.ts +160 -0
- package/tokens/build/tokens.json +88 -0
- package/tokens/build/tokens.scss +89 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Sorting
|
|
2
|
+
|
|
3
|
+
Column sorting with visible indicators and consistent cycle. This pattern
|
|
4
|
+
governs how a [Table / DataTable](../components/table.md) orders its rows by a
|
|
5
|
+
sortable column, and how that order is communicated.
|
|
6
|
+
|
|
7
|
+
User story #7.
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Sorting lets the person reorder the current result set by a comparable column —
|
|
12
|
+
name, lag, modified date, row count. It answers "which of these is most/least
|
|
13
|
+
X?" without the person scrolling to compare manually. Sorting orders the set
|
|
14
|
+
that [Search](search.md) and [filtering](filtering.md) have already narrowed.
|
|
15
|
+
|
|
16
|
+
## Component composition
|
|
17
|
+
|
|
18
|
+
| Region | Compose with | Role |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| Sortable header | [Table / DataTable](../components/table.md) ColumnHeaderCell with sort control | Button inside the header that toggles sort |
|
|
21
|
+
| Sort indicator | icon in the header (▲ / ▼) | Visual + accessible direction; not color alone |
|
|
22
|
+
| Unsortable header | plain `<th>` | No sort control, no `aria-sort` |
|
|
23
|
+
| Sort announcement | polite live region | "Sorted by lag, descending" when not obvious from focus |
|
|
24
|
+
|
|
25
|
+
The sort control is a button (or a nested button) inside the `<th>`. It is not
|
|
26
|
+
a separate "Sort" dropdown outside the table.
|
|
27
|
+
|
|
28
|
+
## Flow
|
|
29
|
+
|
|
30
|
+
1. Person views the table with its default sort (or unsorted).
|
|
31
|
+
2. Person activates a sortable header (click or keyboard `Enter`/`Space` on
|
|
32
|
+
the header button).
|
|
33
|
+
3. Sort cycles: unsorted → ascending → descending → unsorted (or
|
|
34
|
+
ascending → descending → ascending if a default sort is required and
|
|
35
|
+
"unsorted" is not meaningful).
|
|
36
|
+
4. The rows reorder. If server-side, the region shows
|
|
37
|
+
[Skeleton](../components/skeleton.md) rows during the fetch; if client-side,
|
|
38
|
+
the set reorders immediately.
|
|
39
|
+
5. The header's `aria-sort` and visible indicator update.
|
|
40
|
+
6. The sort interacts with [pagination](pagination.md): changing sort on a
|
|
41
|
+
paged set resets to page 1 (the person should see the top of the new order).
|
|
42
|
+
|
|
43
|
+
## States
|
|
44
|
+
|
|
45
|
+
| State | Header behavior | Tokens |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| unsorted | No indicator. `aria-sort="none"`. Sort control still present on sortable headers. | Header default tokens. |
|
|
48
|
+
| ascending | ▲ indicator. `aria-sort="ascending"`. | Indicator `--color-foreground` or `--color-primary`. |
|
|
49
|
+
| descending | ▼ indicator. `aria-sort="descending"`. | Same as ascending. |
|
|
50
|
+
| applying | Server-side: body shows Skeleton rows; `aria-busy` on the region. Header indicator already shows the new direction. | |
|
|
51
|
+
| error | Sort query failed → [Alert](../components/alert.md). Do not leave a stale sort indicator with mismatched rows. | |
|
|
52
|
+
|
|
53
|
+
The sort indicator must be visible without relying on color alone. Use an icon
|
|
54
|
+
(▲ / ▼ or an equivalent) plus `aria-sort`.
|
|
55
|
+
|
|
56
|
+
## Layout sketch
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
60
|
+
│ Toolbar │
|
|
61
|
+
│ [🔍 Search replicas...] [Status▾] │
|
|
62
|
+
│ 18 replicas │
|
|
63
|
+
├──────────────────────────────────────────────────────────────────────┤
|
|
64
|
+
│ Table / DataTable │
|
|
65
|
+
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
66
|
+
│ │ Name ▲ Host Status Lag ▼ Region │ │
|
|
67
|
+
│ │ analytics db.an.example ok 1.2 s ap │ │
|
|
68
|
+
│ │ replica-01 db.eu.example degraded 820 ms eu │ │
|
|
69
|
+
│ │ replica-03 db.eu.backup degraded 410 ms eu │ │
|
|
70
|
+
│ │ replica-07 db.eu.dr ok — eu │ │
|
|
71
|
+
│ │ ... │ │
|
|
72
|
+
│ └────────────────────────────────────────────────────────────────┘ │
|
|
73
|
+
│ ← 1 2 3 … → │
|
|
74
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`Lag ▼` is the active sort (descending): highest lag first. `Name ▲` shows a
|
|
78
|
+
previous or secondary direction indicator only if the surface supports
|
|
79
|
+
multi-sort; otherwise only one column carries an indicator at a time.
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
82
|
+
|
|
83
|
+
- One primary sort column at a time unless the product explicitly supports
|
|
84
|
+
multi-sort. Multi-sort is rare; if supported, document the precedence order
|
|
85
|
+
and show secondary indicators subtly.
|
|
86
|
+
- Default sort is allowed and often sensible (e.g. "Modified, descending").
|
|
87
|
+
The default-sorted column shows its indicator on first render.
|
|
88
|
+
- The sort cycle is consistent across the product. Pick one —
|
|
89
|
+
unsorted → asc → desc → unsorted, or asc → desc → asc — and keep it.
|
|
90
|
+
- Sort changes the **order of the current result set** (client) or the
|
|
91
|
+
**query** (server). Document which on the screen or in its notes.
|
|
92
|
+
- Changing sort on a paged set resets to page 1. The person expects to see the
|
|
93
|
+
top of the new order, not page 3 of the old one.
|
|
94
|
+
- Do not sort by a column the person cannot see. If a column is hidden by
|
|
95
|
+
responsive collapse ([large-data-tables](large-data-tables.md)), it should
|
|
96
|
+
not be the active sort — or its sort must transfer to a visible column.
|
|
97
|
+
- Unsortable columns (e.g. row actions, a Badge-only status that has no natural
|
|
98
|
+
order) have no sort control and no `aria-sort`.
|
|
99
|
+
- Sort interacts with [filtering](filtering.md) and [Search](search.md): the
|
|
100
|
+
filtered/searched set is what gets sorted. Sorting does not widen the set.
|
|
101
|
+
|
|
102
|
+
## Accessibility
|
|
103
|
+
|
|
104
|
+
- Sortable headers use a button with `aria-sort` on the `<th>`:
|
|
105
|
+
`ascending`, `descending`, or `none`.
|
|
106
|
+
- The sort direction is announced when it changes, especially when focus does
|
|
107
|
+
not remain on the header (e.g. server-side sort that swaps rows). Use a
|
|
108
|
+
polite live region: "Sorted by lag, descending."
|
|
109
|
+
- The header button has an accessible name that includes the column: "Sort by
|
|
110
|
+
lag". The visible label plus icon is the name; do not rely on the icon alone.
|
|
111
|
+
- Keyboard: `Tab` reaches sortable headers; `Enter` / `Space` toggles sort.
|
|
112
|
+
Arrow-key grid navigation, if implemented, follows the
|
|
113
|
+
[Table](../components/table.md) keyboard model.
|
|
114
|
+
|
|
115
|
+
## Related patterns
|
|
116
|
+
|
|
117
|
+
- [Search](search.md) — narrows the set before sorting.
|
|
118
|
+
- [Filtering](filtering.md) — narrows the set before sorting.
|
|
119
|
+
- [Pagination](pagination.md) — pages the sorted set.
|
|
120
|
+
- [Large data tables](large-data-tables.md) — tables where sort + scroll is the
|
|
121
|
+
primary navigation.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Design Principles
|
|
2
|
+
|
|
3
|
+
These principles govern *how Momoi Labs product interfaces are designed*. They
|
|
4
|
+
are the design analog to the repository's
|
|
5
|
+
[engineering principles](../../docs/PRINCIPLES.md) — separate in domain, but
|
|
6
|
+
philosophically coherent with them.
|
|
7
|
+
|
|
8
|
+
Use them when a design choice has more than one correct answer. When a
|
|
9
|
+
principle and a convenience conflict, the principle wins. When two principles
|
|
10
|
+
conflict, the order below is the tie-breaker.
|
|
11
|
+
|
|
12
|
+
## Relationship to engineering principles
|
|
13
|
+
|
|
14
|
+
The engineering principles (`docs/PRINCIPLES.md`) govern *how this repository
|
|
15
|
+
is built*: smallest viable change, commit history tells a story, interfaces are
|
|
16
|
+
a compatibility contract, no silent defaults, decisions are recorded
|
|
17
|
+
append-only.
|
|
18
|
+
|
|
19
|
+
These design principles govern *how product interfaces look and feel*. They do
|
|
20
|
+
not duplicate the engineering principles. They are coherent with them in
|
|
21
|
+
spirit:
|
|
22
|
+
|
|
23
|
+
- Engineering's "no silent defaults" has a design analog — "no ad-hoc
|
|
24
|
+
tokens/components when an equivalent exists" — which will live in
|
|
25
|
+
`kiso/AGENTS.md` (a later epic), not here.
|
|
26
|
+
- Engineering's "smallest viable change" echoes in design's "useful over
|
|
27
|
+
decorative": both ask *does this need to be here?*
|
|
28
|
+
- Engineering's "decisions are recorded" echoes in design's "opinionated, not
|
|
29
|
+
restrictive": have a position, and make it findable.
|
|
30
|
+
|
|
31
|
+
Do not conflate the two. When an engineering question arises in design work
|
|
32
|
+
(e.g. "should this be a new component?"), defer to the engineering principles.
|
|
33
|
+
|
|
34
|
+
## 1. Useful over decorative
|
|
35
|
+
|
|
36
|
+
Every element in a product interface must serve a purpose. Decoration that does
|
|
37
|
+
not help the person do something is removed — not minimized, removed.
|
|
38
|
+
|
|
39
|
+
When choosing between two directions, prefer the one that:
|
|
40
|
+
|
|
41
|
+
- helps the person understand or act, rather than merely impresses;
|
|
42
|
+
- uses whitespace and hierarchy instead of ornament to create structure;
|
|
43
|
+
- defers any visual flourish that the task does not require.
|
|
44
|
+
|
|
45
|
+
This is the first principle because it is the most common question: *should
|
|
46
|
+
this be here?* If it does not help, it does not belong.
|
|
47
|
+
|
|
48
|
+
## 2. Clarity over cleverness
|
|
49
|
+
|
|
50
|
+
A clear interface is better than a clever one. Novelty for its own sake makes a
|
|
51
|
+
product harder to learn and harder to maintain.
|
|
52
|
+
|
|
53
|
+
When choosing between two directions, prefer the one that:
|
|
54
|
+
|
|
55
|
+
- uses familiar patterns where a familiar pattern already works;
|
|
56
|
+
- makes the current state and available actions obvious;
|
|
57
|
+
- resists a novel solution unless the novel solution is materially better — and
|
|
58
|
+
if it is, records why.
|
|
59
|
+
|
|
60
|
+
## 3. Technical, not intimidating
|
|
61
|
+
|
|
62
|
+
Momoi builds developer tools, DB/infra tooling, and data-heavy interfaces.
|
|
63
|
+
These can be dense and precise without being hostile.
|
|
64
|
+
|
|
65
|
+
When choosing between two directions, prefer the one that:
|
|
66
|
+
|
|
67
|
+
- trusts the person's competence — show exact values, raw data, and technical
|
|
68
|
+
terms where they are the clearest language;
|
|
69
|
+
- adds context and affordance rather than dumbing down — explain, don't hide;
|
|
70
|
+
- keeps density high where density aids understanding, and uses hierarchy to
|
|
71
|
+
keep it navigable.
|
|
72
|
+
|
|
73
|
+
## 4. Quiet interfaces, strong hierarchy
|
|
74
|
+
|
|
75
|
+
Calm surfaces with strong information hierarchy. Visual noise is low; the
|
|
76
|
+
structure does the guiding.
|
|
77
|
+
|
|
78
|
+
When choosing between two directions, prefer the one that:
|
|
79
|
+
|
|
80
|
+
- uses one accent for attention, not a palette competing for it;
|
|
81
|
+
- lets typography and spacing establish rank, rather than color or weight
|
|
82
|
+
escalation;
|
|
83
|
+
- reserves emphasis for the thing that matters most on the screen right now.
|
|
84
|
+
|
|
85
|
+
## 5. Opinionated, not restrictive
|
|
86
|
+
|
|
87
|
+
Momoi has a position on how things should look and sound. That position makes
|
|
88
|
+
decisions faster and products more consistent. But it should never block a
|
|
89
|
+
legitimate need.
|
|
90
|
+
|
|
91
|
+
When choosing between two directions, prefer the one that:
|
|
92
|
+
|
|
93
|
+
- has a clear default and a documented reason for it;
|
|
94
|
+
- allows escape hatches when a genuine case demands it — and records the
|
|
95
|
+
escape;
|
|
96
|
+
- does not invent a rule just to forbid something; every restriction should
|
|
97
|
+
trace back to a principle above.
|
|
98
|
+
|
|
99
|
+
## Conflict resolution (tie-breaker)
|
|
100
|
+
|
|
101
|
+
When two principles conflict, resolve in this order:
|
|
102
|
+
|
|
103
|
+
1. **Useful over decorative** — if one option serves the person and the other
|
|
104
|
+
decorates, the useful one wins. This is almost always the first cut.
|
|
105
|
+
2. **Clarity over cleverness** — if both are useful, the clearer one wins.
|
|
106
|
+
3. **Technical, not intimidating** — if both are clear, prefer the one that
|
|
107
|
+
respects and engages the person's competence.
|
|
108
|
+
4. **Quiet interfaces, strong hierarchy** — if both are technical, prefer the
|
|
109
|
+
calmer, more hierarchically structured one.
|
|
110
|
+
5. **Opinionated, not restrictive** — apply defaults and positions, but yield
|
|
111
|
+
to a documented, genuine exception.
|
|
112
|
+
|
|
113
|
+
The order moves from *does it belong?* through *is it clear?* through *is it
|
|
114
|
+
respectful?* through *is it calm?* to *is it consistent?* — a funnel from
|
|
115
|
+
existence to polish. If a conflict is not resolved after walking this list, the
|
|
116
|
+
design principle is missing and should be added (in a later epic, through real
|
|
117
|
+
product need — not speculatively here).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
*Design principles are separate from engineering principles but philosophically
|
|
122
|
+
coherent. Read both. Do not duplicate.*
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Kiso design tokens
|
|
2
|
+
|
|
3
|
+
Kiso tokens are a two-layer interface. `tokens/tokens.json` is the single DTCG
|
|
4
|
+
2025.10 source: `color.*` contains raw palette primitives, while `semantic.*`
|
|
5
|
+
names the roles a product needs. Components consume semantic colors only; they
|
|
6
|
+
must not use a primitive, a generated primitive variable, or a raw hex value.
|
|
7
|
+
If no semantic role fits, propose a role instead of bypassing this interface.
|
|
8
|
+
|
|
9
|
+
Install dependencies and build with Style Dictionary v5:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm ci
|
|
13
|
+
npm run build
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The build emits `tokens/build/tokens.css`, `tokens.json`, `tokens.d.ts`, and
|
|
17
|
+
`tokens.scss`. CSS contains the dark default in `:root`, light overrides in
|
|
18
|
+
`[data-theme="light"]`, and the reduced-motion override. Import `tokens.css`,
|
|
19
|
+
then toggle the light theme on an ancestor; application CSS should need no raw
|
|
20
|
+
color values.
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
@import "../../tokens/build/tokens.css";
|
|
24
|
+
|
|
25
|
+
.panel {
|
|
26
|
+
color: var(--color-foreground);
|
|
27
|
+
background: var(--color-surface);
|
|
28
|
+
border: 1px solid var(--color-border);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Semantic colors
|
|
33
|
+
|
|
34
|
+
| Role | Meaning and use | Dark primitive | Light primitive |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `background` | Application canvas; never text. | `neutral.900` | `neutral.200` |
|
|
37
|
+
| `surface` | Cards, panels, and table rows. | `neutral.800` | `neutral.100` |
|
|
38
|
+
| `elevated-surface` | Menus, popovers, and dialogs. | `neutral.700` | `white` |
|
|
39
|
+
| `foreground` | Primary text and content that must carry the strongest hierarchy. | `neutral.100` | `neutral.900` |
|
|
40
|
+
| `muted-foreground` | Secondary text and labels. It remains normal-text eligible. | `neutral.400` | `neutral.600` |
|
|
41
|
+
| `subtle-foreground` | Placeholders, timestamps, and non-essential hints; large text only, never body copy. | `neutral.500` | `neutral.500` |
|
|
42
|
+
| `border` | Dividers and control outlines; never text. | `neutral.600` | `neutral.300` |
|
|
43
|
+
| `primary` | The main interactive action: links, active states, and primary controls. | `accent.base` | `accent.base` |
|
|
44
|
+
| `accent` | Secondary emphasis and highlights, not the page's main action. | `accent.300` | `accent.800` |
|
|
45
|
+
| `success` | Positive or completed state. | `status.success` | `status.success` |
|
|
46
|
+
| `warning` | Caution or a condition needing attention. | `status.warning` | `status.warning` |
|
|
47
|
+
| `danger` | Error or destructive action. | `status.danger` | `status.danger` |
|
|
48
|
+
| `info` | Neutral informational state. | `status.info` | `status.info` |
|
|
49
|
+
| `focus` | Keyboard focus ring; never text. | `accent.300` | `accent.base` |
|
|
50
|
+
| `disabled` | Disabled text and controls only. | `neutral.600` | `neutral.400` |
|
|
51
|
+
|
|
52
|
+
Use `foreground` for default reading, `muted-foreground` when content is
|
|
53
|
+
secondary but still needs normal-text contrast, and `subtle-foreground` only
|
|
54
|
+
for large or non-essential supporting copy. Use `primary` for the action that
|
|
55
|
+
drives the current task; use `accent` to draw secondary attention without
|
|
56
|
+
creating another primary action.
|
|
57
|
+
|
|
58
|
+
The semantic aliases deliberately point at different primitives by theme.
|
|
59
|
+
Status primitives and `accent.base` are themselves mode-aware, so the same
|
|
60
|
+
semantic role preserves its meaning and contrast rather than preserving a
|
|
61
|
+
literal color.
|
|
62
|
+
|
|
63
|
+
## AA gate
|
|
64
|
+
|
|
65
|
+
`scripts/check-contrast.mjs` is the build-time AA gate. In both dark and light
|
|
66
|
+
themes it resolves the semantic aliases and checks:
|
|
67
|
+
|
|
68
|
+
- `foreground`, `muted-foreground`, `primary`, `accent`, `success`, `warning`,
|
|
69
|
+
`danger`, and `info` at **4.5:1** or better against `background`, `surface`,
|
|
70
|
+
and `elevated-surface`;
|
|
71
|
+
- `subtle-foreground` at **3:1** or better against those surfaces, restricting
|
|
72
|
+
it to large text and non-essential metadata;
|
|
73
|
+
- `focus` at **3:1** or better against `background` for visible focus rings.
|
|
74
|
+
|
|
75
|
+
`disabled` is intentionally outside the gate because inactive controls are
|
|
76
|
+
exempt from WCAG 1.4.3. `background`, `surface`, `elevated-surface`, and `border`
|
|
77
|
+
are not text roles. Run the gate with:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
node scripts/check-contrast.mjs tokens/tokens.json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Generated files
|
|
84
|
+
|
|
85
|
+
All four files in `tokens/build/` are committed. This makes the published
|
|
86
|
+
artifacts directly consumable without requiring downstream projects to install
|
|
87
|
+
Style Dictionary. Do not edit them: change `tokens/tokens.json` or the build
|
|
88
|
+
configuration and regenerate. CI rebuilds the artifacts and fails if the
|
|
89
|
+
committed output has drifted, so `tokens/build/` is intentionally not ignored.
|
|
90
|
+
|
|
91
|
+
Published releases expose the artifacts as `@momoi-labs/kiso/tokens.css`,
|
|
92
|
+
`@momoi-labs/kiso/tokens.json`, `@momoi-labs/kiso/tokens.scss`, and
|
|
93
|
+
`@momoi-labs/kiso/tokens.d.ts`. Kiso's Markdown contracts are available below
|
|
94
|
+
`@momoi-labs/kiso/contracts/` so consumers can pin the contracts and generated
|
|
95
|
+
tokens to the same version.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Voice and Tone — Product UI Microcopy
|
|
2
|
+
|
|
3
|
+
This document governs how product UI copy **sounds**. It is the voice layer of
|
|
4
|
+
the [brand](./brand.md), made concrete for the words on screen.
|
|
5
|
+
|
|
6
|
+
It covers **product UI** — labels, buttons, states, errors, empty states,
|
|
7
|
+
confirmations. It does not cover marketing copy, documentation prose, or the
|
|
8
|
+
marketing site's voice. The marketing site is more experimental; products are
|
|
9
|
+
lived in.
|
|
10
|
+
|
|
11
|
+
## The core decision: universal directness vs reserved terminal flourish
|
|
12
|
+
|
|
13
|
+
Momoi's marketing site has a terminal-inspired voice. For products, that voice
|
|
14
|
+
splits into two layers with different rules:
|
|
15
|
+
|
|
16
|
+
### Universal directness (applies everywhere)
|
|
17
|
+
|
|
18
|
+
All product UI copy is **direct, brief, and structured**. This is not a style
|
|
19
|
+
preference — it is a baseline requirement for every word on screen.
|
|
20
|
+
|
|
21
|
+
- **Direct.** Say what the thing is and what it does. No hedging, no
|
|
22
|
+
filler, no "please" or "sorry" in functional copy.
|
|
23
|
+
- **Brief.** The fewest words that are still clear. If a label works at two
|
|
24
|
+
words, it does not need three.
|
|
25
|
+
- **Structured.** Prefer scannable structure (labels, lists, known patterns)
|
|
26
|
+
over prose paragraphs. Dense data gets a table, not a sentence.
|
|
27
|
+
|
|
28
|
+
### Reserved terminal flourish (chrome and metadata only)
|
|
29
|
+
|
|
30
|
+
The site's literal terminal flourishes — "~/ABOUT" navigation prefixes, "$
|
|
31
|
+
export THEME=light" command-style actions — are **reserved for chrome and
|
|
32
|
+
metadata**. They do not appear in:
|
|
33
|
+
|
|
34
|
+
- **Body copy.** Explanatory text is plain and direct.
|
|
35
|
+
- **Error messages.** Errors follow their own mandatory structure (below).
|
|
36
|
+
- **Functional UI text.** Buttons, form labels, status indicators, and
|
|
37
|
+
navigation use plain language.
|
|
38
|
+
|
|
39
|
+
Terminal flourish is appropriate in chrome where it reinforces identity without
|
|
40
|
+
costing clarity — e.g. a subtle metadata label, a decorative section eyebrow.
|
|
41
|
+
When in doubt, leave it out. Directness always works; flourish is optional.
|
|
42
|
+
|
|
43
|
+
## Error messages (hard rule)
|
|
44
|
+
|
|
45
|
+
Error messages **must** follow this three-part structure:
|
|
46
|
+
|
|
47
|
+
1. **What happened.** State the problem in plain language.
|
|
48
|
+
2. **Why, when possible.** If the cause is knowable, explain it briefly. If it
|
|
49
|
+
is not, skip this part rather than guess.
|
|
50
|
+
3. **What the person can do now.** Offer a concrete next step or recovery
|
|
51
|
+
action.
|
|
52
|
+
|
|
53
|
+
This is a **hard rule**, not a suggestion. An error without a recovery path is
|
|
54
|
+
incomplete.
|
|
55
|
+
|
|
56
|
+
**Examples:**
|
|
57
|
+
|
|
58
|
+
> Connection to the database failed. The server at `db.example.com:5432` did
|
|
59
|
+
> not respond within 5 seconds. Check that the database is running and
|
|
60
|
+
> reachable, then retry.
|
|
61
|
+
|
|
62
|
+
> This query was canceled. It exceeded the 30-second timeout. Narrow the query
|
|
63
|
+
> scope or increase the timeout in Settings.
|
|
64
|
+
|
|
65
|
+
> You don't have permission to delete this record. Only workspace admins can
|
|
66
|
+
> delete records. Ask an admin to perform this action or to grant you the
|
|
67
|
+
> role.
|
|
68
|
+
|
|
69
|
+
**Anti-patterns** (never do these):
|
|
70
|
+
|
|
71
|
+
- Cryptic codes without context: `Error: ECONNREFUSED`
|
|
72
|
+
- Accusatory tone: "You entered an invalid value."
|
|
73
|
+
- Apologetic filler: "Oops! We're sorry, but something went wrong."
|
|
74
|
+
- No recovery path: "Operation failed."
|
|
75
|
+
|
|
76
|
+
## Specific UI states
|
|
77
|
+
|
|
78
|
+
### Empty states
|
|
79
|
+
|
|
80
|
+
Helpful and actionable, not apologetic or cute. State what is empty, why it
|
|
81
|
+
might be, and what to do next.
|
|
82
|
+
|
|
83
|
+
> No queries yet. Create your first query to see it here.
|
|
84
|
+
|
|
85
|
+
> This workspace has no members. Invite people to collaborate.
|
|
86
|
+
|
|
87
|
+
Do not say "Nothing to see here" or "It's lonely in here." Empty states are a
|
|
88
|
+
signpost, not a joke.
|
|
89
|
+
|
|
90
|
+
### Loading states
|
|
91
|
+
|
|
92
|
+
Informative without being chatty. State what is happening; do not narrate
|
|
93
|
+
enthusiasm.
|
|
94
|
+
|
|
95
|
+
> Loading queries…
|
|
96
|
+
|
|
97
|
+
> Connecting to database…
|
|
98
|
+
|
|
99
|
+
Do not say "Hang tight!" or "Cooking something up…". A loading state that takes
|
|
100
|
+
longer than a moment may add a reason ("Loading 1,240 rows…").
|
|
101
|
+
|
|
102
|
+
### Destructive actions and confirmations
|
|
103
|
+
|
|
104
|
+
Clear without being alarming. Name the action and its consequence; let the
|
|
105
|
+
person decide.
|
|
106
|
+
|
|
107
|
+
> Delete "production-db"? This cannot be undone.
|
|
108
|
+
|
|
109
|
+
> Disconnect from `staging-db`? Active queries will be terminated.
|
|
110
|
+
|
|
111
|
+
Do not say "Are you absolutely sure?" or wrap confirmations in warning colors
|
|
112
|
+
that imply danger beyond the actual stakes. Match the confirmation's weight to
|
|
113
|
+
the action's irreversibility.
|
|
114
|
+
|
|
115
|
+
### Permission denied
|
|
116
|
+
|
|
117
|
+
Explain clearly without sounding accusatory or corporate. State what was
|
|
118
|
+
denied, why, and what to do.
|
|
119
|
+
|
|
120
|
+
> You can't edit this query. Only the query author and workspace admins can
|
|
121
|
+
> edit it. Ask an admin to grant you access.
|
|
122
|
+
|
|
123
|
+
Do not say "Access Denied" in red with no context. Do not say "You are not
|
|
124
|
+
authorized to perform this action" — it is corporate and unhelpful.
|
|
125
|
+
|
|
126
|
+
## Product UI voice vs documentation tone
|
|
127
|
+
|
|
128
|
+
Product UI copy is **terse and structural**. Documentation is **prose that
|
|
129
|
+
explains**. They share directness but differ in form:
|
|
130
|
+
|
|
131
|
+
- **Product UI:** "Delete query" / "3 of 12 rows selected" / "Reconnect"
|
|
132
|
+
- **Documentation:** "To delete a query, open its menu and choose Delete. The
|
|
133
|
+
query is removed from the workspace; its results are not exported anywhere."
|
|
134
|
+
|
|
135
|
+
Do not write product UI copy as documentation, and do not write documentation
|
|
136
|
+
as a series of labels. If a UI element needs more explanation than a label
|
|
137
|
+
allows, link to documentation rather than inflating the label.
|
|
138
|
+
|
|
139
|
+
## Quick reference
|
|
140
|
+
|
|
141
|
+
| Situation | Rule |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| Any copy | Direct, brief, structured |
|
|
144
|
+
| Error | What happened → why (if possible) → what to do now |
|
|
145
|
+
| Empty state | What's empty → why → what to do |
|
|
146
|
+
| Loading | What's happening, no chatter |
|
|
147
|
+
| Destructive | Name action + consequence, match weight to stakes |
|
|
148
|
+
| Permission denied | What was denied → why → what to do |
|
|
149
|
+
| Terminal flourish | Chrome/metadata only, never body/errors/functional text |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
*Directness is universal. Terminal flourish is reserved. Errors always have a
|
|
154
|
+
recovery path.*
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@momoi-labs/kiso",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kiso design-system contracts and generated design tokens",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/momoi-labs/blueprint.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"files": [
|
|
12
|
+
"kiso/",
|
|
13
|
+
"tokens/build/"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
"./tokens.css": "./tokens/build/tokens.css",
|
|
17
|
+
"./tokens.json": "./tokens/build/tokens.json",
|
|
18
|
+
"./tokens.scss": "./tokens/build/tokens.scss",
|
|
19
|
+
"./tokens.d.ts": "./tokens/build/tokens.d.ts",
|
|
20
|
+
"./contracts/*": "./kiso/*"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "style-dictionary build --config style-dictionary.config.mjs",
|
|
24
|
+
"check": "npm run build && npm run check:generated && npm run check:tokens",
|
|
25
|
+
"check:generated": "git diff --exit-code -- tokens/build/",
|
|
26
|
+
"check:tokens": "node scripts/validate-dtcg.mjs && node scripts/check-contrast.mjs tokens/tokens.json && node scripts/check-component-token-refs.mjs",
|
|
27
|
+
"changeset": "changeset",
|
|
28
|
+
"release": "changeset publish"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"provenance": true
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@changesets/cli": "^2.29.7",
|
|
36
|
+
"ajv": "^8.17.1",
|
|
37
|
+
"style-dictionary": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* Kiso design tokens — generated from tokens/tokens.json. Do not edit. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--color-white: #ffffff;
|
|
5
|
+
--color-black: #000000;
|
|
6
|
+
--color-neutral-100: #faf9f7;
|
|
7
|
+
--color-neutral-200: #f4f2ee;
|
|
8
|
+
--color-neutral-300: #e0dcd6;
|
|
9
|
+
--color-neutral-400: #b6b2ae;
|
|
10
|
+
--color-neutral-500: #8a857f;
|
|
11
|
+
--color-neutral-600: #5f5b57;
|
|
12
|
+
--color-neutral-700: #312f37;
|
|
13
|
+
--color-neutral-800: #26252b;
|
|
14
|
+
--color-neutral-900: #1b1a1e;
|
|
15
|
+
--color-accent-200: #ede8fd;
|
|
16
|
+
--color-accent-300: #ded5fb;
|
|
17
|
+
--color-accent-400: #c4b4f5;
|
|
18
|
+
--color-accent-600: #7d68d1;
|
|
19
|
+
--color-accent-700: #6552b8;
|
|
20
|
+
--color-accent-800: #46309e;
|
|
21
|
+
--color-accent-900: #2e2170;
|
|
22
|
+
--color-accent-base: #cbbdf7;
|
|
23
|
+
--color-status-success: #4ed69a;
|
|
24
|
+
--color-status-warning: #e8c05a;
|
|
25
|
+
--color-status-danger: #f4867f;
|
|
26
|
+
--color-status-info: #8fc9f5;
|
|
27
|
+
--color-background: var(--color-neutral-900);
|
|
28
|
+
--color-surface: var(--color-neutral-800);
|
|
29
|
+
--color-elevated-surface: var(--color-neutral-700);
|
|
30
|
+
--color-foreground: var(--color-neutral-100);
|
|
31
|
+
--color-muted-foreground: var(--color-neutral-400);
|
|
32
|
+
--color-subtle-foreground: var(--color-neutral-500);
|
|
33
|
+
--color-border: var(--color-neutral-600);
|
|
34
|
+
--color-primary: var(--color-accent-base);
|
|
35
|
+
--color-accent: var(--color-accent-300);
|
|
36
|
+
--color-success: var(--color-status-success);
|
|
37
|
+
--color-warning: var(--color-status-warning);
|
|
38
|
+
--color-danger: var(--color-status-danger);
|
|
39
|
+
--color-info: var(--color-status-info);
|
|
40
|
+
--color-focus: var(--color-accent-300);
|
|
41
|
+
--color-disabled: var(--color-neutral-600);
|
|
42
|
+
--font-heading: Inter, system-ui, sans-serif;
|
|
43
|
+
--font-body: Inter, system-ui, sans-serif;
|
|
44
|
+
--font-mono: JetBrains Mono, ui-monospace, monospace;
|
|
45
|
+
--type-size-metadata: 11px;
|
|
46
|
+
--type-size-label: 13px;
|
|
47
|
+
--type-size-body: 16px;
|
|
48
|
+
--type-size-h3: 19px;
|
|
49
|
+
--type-size-h2: 23px;
|
|
50
|
+
--type-size-h1: 28px;
|
|
51
|
+
--type-size-display: 33px;
|
|
52
|
+
--type-weight-regular: 400;
|
|
53
|
+
--type-weight-medium: 500;
|
|
54
|
+
--type-weight-semibold: 600;
|
|
55
|
+
--type-weight-bold: 700;
|
|
56
|
+
--type-line-height-tight: 1.2;
|
|
57
|
+
--type-line-height-normal: 1.5;
|
|
58
|
+
--type-line-height-relaxed: 1.6;
|
|
59
|
+
--type-letter-spacing-heading: -0.01rem;
|
|
60
|
+
--type-letter-spacing-normal: 0rem;
|
|
61
|
+
--type-letter-spacing-label: 0.02rem;
|
|
62
|
+
--type-role-display-font-family: var(--font-heading);
|
|
63
|
+
--type-role-display-font-size: var(--type-size-display);
|
|
64
|
+
--type-role-display-font-weight: var(--type-weight-medium);
|
|
65
|
+
--type-role-display-letter-spacing: var(--type-letter-spacing-heading);
|
|
66
|
+
--type-role-display-line-height: var(--type-line-height-tight);
|
|
67
|
+
--type-role-heading-font-family: var(--font-heading);
|
|
68
|
+
--type-role-heading-font-size: var(--type-size-h1);
|
|
69
|
+
--type-role-heading-font-weight: var(--type-weight-semibold);
|
|
70
|
+
--type-role-heading-letter-spacing: var(--type-letter-spacing-heading);
|
|
71
|
+
--type-role-heading-line-height: var(--type-line-height-tight);
|
|
72
|
+
--type-role-body-font-family: var(--font-body);
|
|
73
|
+
--type-role-body-font-size: var(--type-size-body);
|
|
74
|
+
--type-role-body-font-weight: var(--type-weight-regular);
|
|
75
|
+
--type-role-body-letter-spacing: var(--type-letter-spacing-normal);
|
|
76
|
+
--type-role-body-line-height: var(--type-line-height-normal);
|
|
77
|
+
--type-role-label-font-family: var(--font-body);
|
|
78
|
+
--type-role-label-font-size: var(--type-size-label);
|
|
79
|
+
--type-role-label-font-weight: var(--type-weight-medium);
|
|
80
|
+
--type-role-label-letter-spacing: var(--type-letter-spacing-label);
|
|
81
|
+
--type-role-label-line-height: var(--type-line-height-normal);
|
|
82
|
+
--type-role-metadata-font-family: var(--font-body);
|
|
83
|
+
--type-role-metadata-font-size: var(--type-size-metadata);
|
|
84
|
+
--type-role-metadata-font-weight: var(--type-weight-regular);
|
|
85
|
+
--type-role-metadata-letter-spacing: var(--type-letter-spacing-normal);
|
|
86
|
+
--type-role-metadata-line-height: var(--type-line-height-normal);
|
|
87
|
+
--type-role-code-font-family: var(--font-mono);
|
|
88
|
+
--type-role-code-font-size: var(--type-size-label);
|
|
89
|
+
--type-role-code-font-weight: var(--type-weight-regular);
|
|
90
|
+
--type-role-code-letter-spacing: var(--type-letter-spacing-normal);
|
|
91
|
+
--type-role-code-line-height: var(--type-line-height-normal);
|
|
92
|
+
--type-role-numeric-font-family: var(--font-body);
|
|
93
|
+
--type-role-numeric-font-size: var(--type-size-body);
|
|
94
|
+
--type-role-numeric-font-weight: var(--type-weight-regular);
|
|
95
|
+
--type-role-numeric-letter-spacing: var(--type-letter-spacing-normal);
|
|
96
|
+
--type-role-numeric-line-height: var(--type-line-height-normal);
|
|
97
|
+
--spacing-xs: 4px;
|
|
98
|
+
--spacing-sm: 8px;
|
|
99
|
+
--spacing-md: 12px;
|
|
100
|
+
--spacing-lg: 16px;
|
|
101
|
+
--spacing-xl: 24px;
|
|
102
|
+
--spacing-2xl: 32px;
|
|
103
|
+
--spacing-3xl: 48px;
|
|
104
|
+
--spacing-4xl: 64px;
|
|
105
|
+
--radius-sm: 4px;
|
|
106
|
+
--radius-md: 8px;
|
|
107
|
+
--radius-lg: 12px;
|
|
108
|
+
--radius-full: 9999px;
|
|
109
|
+
--shadow-sm: 0px 1px 2px 0px rgba(0, 0, 0, 0.24);
|
|
110
|
+
--shadow-md: 0px 2px 8px 0px rgba(0, 0, 0, 0.32);
|
|
111
|
+
--motion-duration-fast: 120ms;
|
|
112
|
+
--motion-duration-normal: 200ms;
|
|
113
|
+
--motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
114
|
+
--breakpoint-sm: 640px;
|
|
115
|
+
--breakpoint-md: 768px;
|
|
116
|
+
--breakpoint-lg: 1024px;
|
|
117
|
+
--breakpoint-xl: 1280px;
|
|
118
|
+
--font-variant-numeric: tabular-nums;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
[data-theme="light"] {
|
|
122
|
+
--color-accent-base: #5b3fc4;
|
|
123
|
+
--color-status-success: #0c6b43;
|
|
124
|
+
--color-status-warning: #7a5000;
|
|
125
|
+
--color-status-danger: #ad2f29;
|
|
126
|
+
--color-status-info: #15559e;
|
|
127
|
+
--color-background: var(--color-neutral-200);
|
|
128
|
+
--color-surface: var(--color-neutral-100);
|
|
129
|
+
--color-elevated-surface: var(--color-white);
|
|
130
|
+
--color-foreground: var(--color-neutral-900);
|
|
131
|
+
--color-muted-foreground: var(--color-neutral-600);
|
|
132
|
+
--color-border: var(--color-neutral-300);
|
|
133
|
+
--color-accent: var(--color-accent-800);
|
|
134
|
+
--color-focus: var(--color-accent-base);
|
|
135
|
+
--color-disabled: var(--color-neutral-400);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@media (prefers-reduced-motion: reduce) {
|
|
139
|
+
:root {
|
|
140
|
+
--motion-duration-fast: 0s;
|
|
141
|
+
--motion-duration-normal: 0s;
|
|
142
|
+
}
|
|
143
|
+
}
|