@momoi-labs/kiso 0.1.0 → 0.3.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.
@@ -14,10 +14,23 @@ npm run build
14
14
  ```
15
15
 
16
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.
17
+ `tokens.scss`.
18
+
19
+ Each custom property is declared **once**, in `:root`. A role whose two themes
20
+ differ is emitted as CSS `light-dark()`, and the theme is chosen by
21
+ `color-scheme`:
22
+
23
+ ```css
24
+ :root { color-scheme: light dark; }
25
+ [data-theme="light"] { color-scheme: light; }
26
+ [data-theme="dark"] { color-scheme: dark; }
27
+ ```
28
+
29
+ So the default — no `data-theme` attribute on `<html>` — follows the operating
30
+ system, with no media query and no JavaScript. An explicit choice flips one
31
+ property. See [Theme](patterns/settings.md#theme) for the full contract.
32
+
33
+ Import `tokens.css`; application CSS should need no raw color values.
21
34
 
22
35
  ```css
23
36
  @import "../../tokens/build/tokens.css";
@@ -49,6 +62,74 @@ color values.
49
62
  | `focus` | Keyboard focus ring; never text. | `accent.300` | `accent.base` |
50
63
  | `disabled` | Disabled text and controls only. | `neutral.600` | `neutral.400` |
51
64
 
65
+ ### Fills and their foregrounds
66
+
67
+ A filled control needs a text colour of its own. Without one it inherits the
68
+ page foreground, the fill loses its contrast, and the control degrades into an
69
+ outline — which is how a violet design system ends up rendering grey.
70
+
71
+ | Role | Meaning and use | Dark primitive | Light primitive |
72
+ | --- | --- | --- | --- |
73
+ | `primary-foreground` | Text and icons on a `primary` fill. | `neutral.900` | `white` |
74
+ | `primary-hover` | `primary` fill on hover. | `accent.300` | `accent.800` |
75
+ | `danger-foreground` | Text and icons on a `danger` fill. | `neutral.900` | `white` |
76
+ | `secondary` | Neutral button fill, range track, count badge; never text. | `neutral.700` | `neutral.300` |
77
+ | `secondary-foreground` | Text on a `secondary` fill. | `foreground` | `foreground` |
78
+ | `secondary-hover` | `secondary` fill on hover; never text. | `neutral.600` | `neutral.400` |
79
+ | `selected` | Selected row, active nav item, highlighted result; never text. | `accent.900` | `accent.200` |
80
+ | `selected-foreground` | Text on a `selected` fill. | `foreground` | `foreground` |
81
+
82
+ `primary-foreground` and `danger-foreground` invert with their fill: near-black
83
+ on the light lilac of dark theme, white on the deep violet of light theme. Both
84
+ are gated at 4.5:1 **against their own fill**, not against a surface.
85
+
86
+ ### Component surfaces
87
+
88
+ | Role | Meaning and use | Dark primitive | Light primitive |
89
+ | --- | --- | --- | --- |
90
+ | `card` | Panel and card fill. Alias of `surface`. | `neutral.800` | `neutral.100` |
91
+ | `card-foreground` | Text on `card`. Alias of `foreground`. | `foreground` | `foreground` |
92
+ | `popover` | Menus, dialogs, palette. Alias of `elevated-surface`. | `neutral.700` | `white` |
93
+ | `popover-foreground` | Text on `popover`. Alias of `foreground`. | `foreground` | `foreground` |
94
+ | `muted` | Recessed fill one step off `card`: table headers, card footers, segmented tracks. | `neutral.700` | `neutral.200` |
95
+ | `sidebar` | Application shell navigation column. | `neutral.950` | `neutral.100` |
96
+ | `sidebar-border` | Divider between sidebar and content. Alias of `border`. | `border` | `border` |
97
+ | `disabled-surface` | Fill of a disabled control. | `neutral.800` | `neutral.200` |
98
+ | `skeleton` | Loading placeholder fill. | `neutral.700` | `neutral.300` |
99
+ | `overlay` | Scrim behind a modal layer. | `black` at 60% | `black` at 40% |
100
+
101
+ ### Lines, tints, and state
102
+
103
+ | Role | Meaning and use | Dark primitive | Light primitive |
104
+ | --- | --- | --- | --- |
105
+ | `border-strong` | Hover outlines, switch tracks, gridlines, corner marks; never text. | `neutral.500` | `neutral.500` |
106
+ | `input` | Form control outline at rest. Alias of `border`. | `border` | `border` |
107
+ | `corner-mark` | Corner registration marks. Alias of `border-strong`. | `border-strong` | `border-strong` |
108
+ | `hatch` | Hatch stripe over a region that is not data. | `neutral.700` | `neutral.300` |
109
+ | `accent-surface` | Faintest accent tint: row hover, ghost hover; never text. | `accent.950` | `accent.50` |
110
+ | `accent-surface-hover` | Accent tint one step stronger; never text. | `accent.900` | `accent.200` |
111
+ | `ring` | Focus ring, active drag handle. Alias of `focus`. | `focus` | `focus` |
112
+ | `link` | Inline and standalone links. Alias of `primary`. | `primary` | `primary` |
113
+ | `shadow-hairline` | Shadow colour for a resting control's contact line. | `black` at 30% | `black` at 5% |
114
+ | `shadow-contact` | Shadow colour for separated and floating layers. | `black` at 40% | `black` at 8% |
115
+
116
+ `border-strong` and `corner-mark` are non-text roles but are gated at 3:1
117
+ against every surface: a registration mark that cannot be seen is not a mark.
118
+
119
+ ### Status fills
120
+
121
+ Each status role gains a tinted fill and an outline, both the status hue at low
122
+ alpha. No status introduces a second hue.
123
+
124
+ | Role | Dark | Light |
125
+ | --- | --- | --- |
126
+ | `success-surface`, `warning-surface`, `danger-surface`, `info-surface` | status hue at 12% | status hue at 10% |
127
+ | `success-border`, `warning-border`, `danger-border`, `info-border` | status hue at 35% | status hue at 30% |
128
+
129
+ `warning-on-dark`, `danger-on-dark`, and `info-on-dark` are theme-invariant on
130
+ purpose. They are for surfaces that stay dark in both themes — log views and
131
+ terminals — which cannot follow `color-scheme`, so their text cannot either.
132
+
52
133
  Use `foreground` for default reading, `muted-foreground` when content is
53
134
  secondary but still needs normal-text contrast, and `subtle-foreground` only
54
135
  for large or non-essential supporting copy. Use `primary` for the action that
@@ -70,16 +151,135 @@ themes it resolves the semantic aliases and checks:
70
151
  and `elevated-surface`;
71
152
  - `subtle-foreground` at **3:1** or better against those surfaces, restricting
72
153
  it to large text and non-essential metadata;
73
- - `focus` at **3:1** or better against `background` for visible focus rings.
154
+ - `border-strong` and `corner-mark` at **3:1** or better against those
155
+ surfaces, per WCAG 1.4.11 for non-text boundaries;
156
+ - `focus` at **3:1** or better against `background` for visible focus rings;
157
+ - each foreground-on-fill pair — `primary-foreground` on `primary`,
158
+ `danger-foreground` on `danger`, `secondary-foreground` on `secondary`,
159
+ `selected-foreground` on `selected`, `card-foreground` on `card`, and
160
+ `popover-foreground` on `popover` — at **4.5:1** or better. This pair is what
161
+ a system without `*-foreground` slots gets wrong, so it is gated rather than
162
+ asserted.
74
163
 
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:
164
+ `disabled` and `disabled-surface` are intentionally outside the gate because
165
+ inactive controls are exempt from WCAG 1.4.3. `background`, `surface`,
166
+ `elevated-surface`, `border`, and the tinted `*-surface` roles are not text
167
+ roles. Run the gate with:
78
168
 
79
169
  ```sh
80
170
  node scripts/check-contrast.mjs tokens/tokens.json
81
171
  ```
82
172
 
173
+ ## Control size is not touch target
174
+
175
+ `size.control.*` is the height of a single-line control. `size.touch.min` is
176
+ the WCAG 2.2 target-size minimum. They are separate tokens because they answer
177
+ different questions, and collapsing them is what makes a dense console look
178
+ like a toy.
179
+
180
+ | Token | Value | Use |
181
+ | --- | --- | --- |
182
+ | `--size-control-xs` | 24px | Inline table-row actions, dense toolbars. |
183
+ | `--size-control-sm` | 32px | Toolbars, segmented controls, compact forms. |
184
+ | `--size-control-md` | 36px | **The default.** Buttons, inputs, selects. |
185
+ | `--size-control-lg` | 40px | Primary calls to action, table row height. |
186
+ | `--size-touch-min` | 44px | Accessibility minimum — see below. |
187
+
188
+ `--size-touch-min` is applied **only** inside `@media (pointer: coarse)`, as a
189
+ `min-height` on top of a control size. Never as the control size itself.
190
+
191
+ ```css
192
+ .btn { height: var(--size-control-md); }
193
+
194
+ @media (pointer: coarse) {
195
+ .btn { min-height: var(--size-touch-min); }
196
+ }
197
+ ```
198
+
199
+ Layout sizes: `--size-sidebar` (248px) is the shell navigation column;
200
+ `--size-content-max` (1280px) is the maximum content measure. Icon boxes are
201
+ `--size-icon-sm` (14px), `--size-icon-md` (16px, the default), and
202
+ `--size-icon-lg` (20px).
203
+
204
+ ## Corners: marks, not radius
205
+
206
+ Panels are square. `--radius-surface` is `0px`, and a panel's corner treatment
207
+ is a **corner mark** instead: two 1px ticks per corner, each lying along the
208
+ frame line it extends and stopping `--corner-mark-gap` short of it, so the mark
209
+ points at the corner without touching it.
210
+
211
+ | Token | Value | Meaning |
212
+ | --- | --- | --- |
213
+ | `--corner-mark` | `1` | Opacity: marks on (`1`) or off (`0`). |
214
+ | `--corner-mark-tick` | 4px | Length of one tick. |
215
+ | `--corner-mark-gap` | 2px | Distance from tick end to the frame. |
216
+
217
+ Two ticks per corner, not four. A full cross puts its other two arms directly
218
+ on top of the 1px panel border, where they are invisible; dropping them halves
219
+ the paint and makes the hollow centre explicit rather than accidental.
220
+
221
+ The gap **is** the mark. Close it and this is just a thicker border.
222
+
223
+ Marks appear on every panel, without exception. There is no rounded mode and no
224
+ `data-corners` attribute — the choice was made once, here.
225
+
226
+ The remaining radii only take the bite off controls:
227
+
228
+ | Token | Value | Use |
229
+ | --- | --- | --- |
230
+ | `--radius-xs` | 2px | Checkboxes, chart bars, the smallest controls. |
231
+ | `--radius-sm` | 3px | Extra-small buttons, focus-ring rounding. |
232
+ | `--radius-md` | 4px | **Buttons, inputs, menu items** — the control default. |
233
+ | `--radius-lg` | 5px | Segmented tracks and other control groups. |
234
+ | `--radius-full` | 9999px | Pills, dots, switches, avatars. |
235
+ | `--radius-surface` | 0px | **Panels, cards, tables, dialogs** — always. |
236
+
237
+ ## Hatch
238
+
239
+ A diagonal hatch marks a region that is **not data**. It is the companion to
240
+ the corner marks, and it has exactly three sanctioned uses:
241
+
242
+ - an empty state — nothing here yet;
243
+ - a chrome band — this strip is title and controls, not content;
244
+ - an unavailable pane — the data does not exist right now.
245
+
246
+ ```css
247
+ .hatch {
248
+ background-image: repeating-linear-gradient(
249
+ var(--hatch-angle),
250
+ var(--color-hatch) 0,
251
+ var(--color-hatch) var(--hatch-line),
252
+ transparent var(--hatch-line),
253
+ transparent var(--hatch-period)
254
+ );
255
+ }
256
+ ```
257
+
258
+ `--hatch-line` is 5px, `--hatch-period` 10px, `--hatch-angle` 45deg. The stripe
259
+ colour is one step off the surface it sits on. Do not raise the contrast to
260
+ make it "read better": if it is loud enough to notice while reading, it is in
261
+ the wrong place.
262
+
263
+ ## Shadow is a colour
264
+
265
+ `--shadow-xs`, `-sm`, `-md`, and `-lg` carry the geometry. The colour is a
266
+ token — `--color-shadow-hairline` or `--color-shadow-contact` — so a shadow
267
+ deepens with the theme instead of staying a fixed black at a fixed alpha.
268
+
269
+ | Token | Use |
270
+ | --- | --- |
271
+ | `--shadow-xs` | Resting controls: buttons, inputs, selected segments. A contact line, not a shadow. |
272
+ | `--shadow-sm` | Cards and panels on the canvas. |
273
+ | `--shadow-md` | Popovers and dropdowns. |
274
+ | `--shadow-lg` | Dialogs, drawers, and the command palette — the only layers that float free. |
275
+
276
+ ## Type scale
277
+
278
+ 11, 12, 14, 16, 18, 22, 30 — `metadata`, `label`, `body`, `h3`, `h2`, `h1`,
279
+ `display`. Tighter than a modular ramp on purpose: these are the steps a
280
+ console actually uses, each distinguishable from its neighbour at a 14px body.
281
+ Body is 14px, not 16px, because a console is read at desk distance, in density.
282
+
83
283
  ## Generated files
84
284
 
85
285
  All four files in `tokens/build/` are committed. This makes the published