@rowkit/tokens 0.0.0 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nikolai Kushner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @rowkit/tokens
2
+
3
+ [![npm](https://img.shields.io/npm/v/@rowkit/tokens?color=3b5bdb)](https://www.npmjs.com/package/@rowkit/tokens)
4
+ [![license](https://img.shields.io/npm/l/@rowkit/tokens)](https://github.com/NikolaiKushner/rowkit/blob/main/LICENSE)
5
+
6
+ The design tokens behind [rowkit](https://www.npmjs.com/package/rowkit) — colour, spacing, typography, radii, shadows, layers and motion.
7
+
8
+ Usable on its own. Nothing here depends on Vue, so a chart library, a design tool or an email template can read the same values the components use.
9
+
10
+ **[Live reference](https://rowkit.dev/foundations/tokens)** · **[GitHub](https://github.com/NikolaiKushner/rowkit)**
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm i @rowkit/tokens
16
+ ```
17
+
18
+ ## Two layers
19
+
20
+ **Primitives** are the raw ramps: `--color-primary-600` is one specific blue and means nothing on its own. **Semantic** tokens name a role — `--color-surface`, `--color-text-muted`, `--color-border` — and point at a primitive through `var()`.
21
+
22
+ Only the semantic layer changes under `.dark`, which is what makes dark mode a matter of repointing references rather than hunting hex codes.
23
+
24
+ ## Use
25
+
26
+ As a Tailwind v4 theme:
27
+
28
+ ```css
29
+ @import 'tailwindcss';
30
+ @import '@rowkit/tokens/css';
31
+ ```
32
+
33
+ Every token becomes a theme value, so `bg-surface`, `text-text-muted`, `p-4`, `rounded-md` and `shadow-lg` resolve to the scales above.
34
+
35
+ As CSS custom properties, for anything Tailwind does not cover:
36
+
37
+ ```css
38
+ .my-thing {
39
+ background: var(--color-surface-subtle);
40
+ border-radius: var(--radius-md);
41
+ }
42
+ ```
43
+
44
+ Or in TypeScript, fully typed, when a value has to reach JavaScript:
45
+
46
+ ```ts
47
+ import { tokens } from '@rowkit/tokens'
48
+
49
+ tokens.color.primary[600] // 'oklch(0.546 0.209 259)'
50
+
51
+ const series = [tokens.color.primary[500], tokens.color.success[500]]
52
+ ```
53
+
54
+ `tokens` is grouped by scale rather than flattened, so `tokens.color.primary[600]` narrows to its literal type and autocompletes at every level.
55
+
56
+ ## Notes on the values
57
+
58
+ **Colour is OKLCH, clamped to sRGB.** Every chromatic family shares one lightness ramp, so `primary-600`, `danger-600` and `success-600` carry the same perceptual weight. Chroma is clamped to the sRGB gamut on purpose: OKLCH can express colours outside it, and browsers gamut-map those by their own rules — which makes a token render differently on a P3 laptop than on an sRGB monitor.
59
+
60
+ **Contrast is asserted, not claimed.** Every semantic pairing is checked against WCAG AA in the package's own tests, in both themes.
61
+
62
+ **Spacing keys are multiples of 4px.** `4` is 1rem, `2` is 8px — the convention most Vue and Tailwind developers already carry.
63
+
64
+ **Layer order is a build gate.** `z-index.test.ts` asserts a modal sits above an overlay, a tooltip above everything, and that consecutive layers stay at least 100 apart.
65
+
66
+ ## License
67
+
68
+ MIT © Nikolai Kushner
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Colour primitives and semantic mappings.
3
+ *
4
+ * ## How the scales were derived
5
+ *
6
+ * Every chromatic family shares one lightness ramp and one chroma envelope, so
7
+ * `primary-600`, `danger-600` and `success-600` are the same perceptual weight
8
+ * and can be swapped without relayering the design. Families differ only by
9
+ * hue and a chroma factor.
10
+ *
11
+ * Chroma at each step is clamped to the sRGB gamut boundary. OKLCH can express
12
+ * colours outside sRGB, and browsers gamut-map them per their own rules — that
13
+ * makes a token render differently on a P3 laptop than on an sRGB monitor.
14
+ * Clamping trades a little vividness for identical output everywhere.
15
+ *
16
+ * `warning` carries a lightness bump through its midtones: at a shared
17
+ * lightness, yellow is far less saturated than blue or red, so the unbumped
18
+ * steps read as muddy brown rather than amber.
19
+ *
20
+ * Contrast for every semantic pair is asserted in `contrast.test.ts` — the
21
+ * ratios are a build gate, not a claim in a comment.
22
+ */
23
+ /** The eleven steps every colour family provides. */
24
+ export declare const colorSteps: readonly [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
25
+ /** One step of a colour family. */
26
+ export type ColorStep = (typeof colorSteps)[number];
27
+ /**
28
+ * Cool-slate neutral (hue 264). Roughly 70% of the pixels in a data table:
29
+ * page background, row borders, muted labels, table chrome.
30
+ */
31
+ export declare const neutral: {
32
+ readonly 50: "oklch(0.984 0.003 264)";
33
+ readonly 100: "oklch(0.968 0.004 264)";
34
+ readonly 200: "oklch(0.928 0.006 264)";
35
+ readonly 300: "oklch(0.869 0.01 264)";
36
+ readonly 400: "oklch(0.704 0.018 264)";
37
+ readonly 500: "oklch(0.551 0.024 264)";
38
+ readonly 600: "oklch(0.446 0.027 264)";
39
+ readonly 700: "oklch(0.373 0.028 264)";
40
+ readonly 800: "oklch(0.279 0.03 264)";
41
+ readonly 900: "oklch(0.21 0.033 264)";
42
+ readonly 950: "oklch(0.13 0.036 264)";
43
+ };
44
+ /** Brand blue (hue 259). Drives links, focus rings and primary actions. */
45
+ export declare const primary: {
46
+ readonly 50: "oklch(0.97 0.014 259)";
47
+ readonly 100: "oklch(0.936 0.03 259)";
48
+ readonly 200: "oklch(0.885 0.055 259)";
49
+ readonly 300: "oklch(0.809 0.095 259)";
50
+ readonly 400: "oklch(0.715 0.147 259)";
51
+ readonly 500: "oklch(0.623 0.201 259)";
52
+ readonly 600: "oklch(0.546 0.209 259)";
53
+ readonly 700: "oklch(0.488 0.187 259)";
54
+ readonly 800: "oklch(0.442 0.165 259)";
55
+ readonly 900: "oklch(0.396 0.135 259)";
56
+ readonly 950: "oklch(0.282 0.086 259)";
57
+ };
58
+ /** Green (hue 152). Reserved for successful outcomes, never for brand accent. */
59
+ export declare const success: {
60
+ readonly 50: "oklch(0.97 0.014 152)";
61
+ readonly 100: "oklch(0.936 0.036 152)";
62
+ readonly 200: "oklch(0.885 0.064 152)";
63
+ readonly 300: "oklch(0.809 0.097 152)";
64
+ readonly 400: "oklch(0.715 0.145 152)";
65
+ readonly 500: "oklch(0.623 0.162 152)";
66
+ readonly 600: "oklch(0.546 0.142 152)";
67
+ readonly 700: "oklch(0.488 0.127 152)";
68
+ readonly 800: "oklch(0.442 0.115 152)";
69
+ readonly 900: "oklch(0.396 0.103 152)";
70
+ readonly 950: "oklch(0.282 0.073 152)";
71
+ };
72
+ /** Amber (hue 75), lightness-bumped through the midtones. See the module note. */
73
+ export declare const warning: {
74
+ readonly 50: "oklch(0.97 0.016 75)";
75
+ readonly 100: "oklch(0.936 0.04 75)";
76
+ readonly 200: "oklch(0.885 0.071 75)";
77
+ readonly 300: "oklch(0.849 0.108 75)";
78
+ readonly 400: "oklch(0.785 0.162 75)";
79
+ readonly 500: "oklch(0.723 0.15 75)";
80
+ readonly 600: "oklch(0.616 0.128 75)";
81
+ readonly 700: "oklch(0.528 0.11 75)";
82
+ readonly 800: "oklch(0.442 0.092 75)";
83
+ readonly 900: "oklch(0.396 0.082 75)";
84
+ readonly 950: "oklch(0.282 0.059 75)";
85
+ };
86
+ /** Red (hue 25). Destructive actions and error states. */
87
+ export declare const danger: {
88
+ readonly 50: "oklch(0.97 0.014 25)";
89
+ readonly 100: "oklch(0.936 0.032 25)";
90
+ readonly 200: "oklch(0.885 0.06 25)";
91
+ readonly 300: "oklch(0.809 0.107 25)";
92
+ readonly 400: "oklch(0.715 0.17 25)";
93
+ readonly 500: "oklch(0.623 0.214 25)";
94
+ readonly 600: "oklch(0.546 0.218 25)";
95
+ readonly 700: "oklch(0.488 0.195 25)";
96
+ readonly 800: "oklch(0.442 0.165 25)";
97
+ readonly 900: "oklch(0.396 0.135 25)";
98
+ readonly 950: "oklch(0.282 0.086 25)";
99
+ };
100
+ /**
101
+ * Every primitive colour, keyed by the CSS custom property it becomes.
102
+ *
103
+ * These are the only place a literal colour value appears in rowkit. Everything
104
+ * else — semantic tokens, component variants — references one of these.
105
+ */
106
+ export declare const colorPrimitives: {
107
+ readonly "danger-50": string;
108
+ readonly "danger-100": string;
109
+ readonly "danger-200": string;
110
+ readonly "danger-300": string;
111
+ readonly "danger-400": string;
112
+ readonly "danger-500": string;
113
+ readonly "danger-600": string;
114
+ readonly "danger-700": string;
115
+ readonly "danger-800": string;
116
+ readonly "danger-900": string;
117
+ readonly "danger-950": string;
118
+ readonly "warning-50": string;
119
+ readonly "warning-100": string;
120
+ readonly "warning-200": string;
121
+ readonly "warning-300": string;
122
+ readonly "warning-400": string;
123
+ readonly "warning-500": string;
124
+ readonly "warning-600": string;
125
+ readonly "warning-700": string;
126
+ readonly "warning-800": string;
127
+ readonly "warning-900": string;
128
+ readonly "warning-950": string;
129
+ readonly "success-50": string;
130
+ readonly "success-100": string;
131
+ readonly "success-200": string;
132
+ readonly "success-300": string;
133
+ readonly "success-400": string;
134
+ readonly "success-500": string;
135
+ readonly "success-600": string;
136
+ readonly "success-700": string;
137
+ readonly "success-800": string;
138
+ readonly "success-900": string;
139
+ readonly "success-950": string;
140
+ readonly "primary-50": string;
141
+ readonly "primary-100": string;
142
+ readonly "primary-200": string;
143
+ readonly "primary-300": string;
144
+ readonly "primary-400": string;
145
+ readonly "primary-500": string;
146
+ readonly "primary-600": string;
147
+ readonly "primary-700": string;
148
+ readonly "primary-800": string;
149
+ readonly "primary-900": string;
150
+ readonly "primary-950": string;
151
+ readonly "neutral-50": string;
152
+ readonly "neutral-100": string;
153
+ readonly "neutral-200": string;
154
+ readonly "neutral-300": string;
155
+ readonly "neutral-400": string;
156
+ readonly "neutral-500": string;
157
+ readonly "neutral-600": string;
158
+ readonly "neutral-700": string;
159
+ readonly "neutral-800": string;
160
+ readonly "neutral-900": string;
161
+ readonly "neutral-950": string;
162
+ readonly white: "oklch(1 0 0)";
163
+ readonly black: "oklch(0 0 0)";
164
+ };
165
+ /** A reference to a primitive colour, as a CSS `var()` expression. */
166
+ export type ColorRef = `var(--color-${string})`;
167
+ /**
168
+ * Light-mode semantic colours.
169
+ *
170
+ * Semantic tokens never hold a literal colour — each one points at a primitive
171
+ * through `var()`, so re-theming means repointing references rather than
172
+ * hunting down hex codes. `semantic.test.ts` enforces this.
173
+ */
174
+ export declare const semanticColorLight: {
175
+ /** Page background, behind all surfaces. */
176
+ readonly background: `var(--color-${string})`;
177
+ /** Cards, panels, table bodies — the plane content sits on. */
178
+ readonly surface: `var(--color-${string})`;
179
+ /** Table headers, toolbars: a surface that recedes slightly. */
180
+ readonly 'surface-subtle': `var(--color-${string})`;
181
+ /** Row hover. */
182
+ readonly 'surface-hover': `var(--color-${string})`;
183
+ /** Row press / active. */
184
+ readonly 'surface-active': `var(--color-${string})`;
185
+ /** Selected table row. */
186
+ readonly 'surface-selected': `var(--color-${string})`;
187
+ /** Disabled control background. */
188
+ readonly 'surface-disabled': `var(--color-${string})`;
189
+ /**
190
+ * Loading placeholder fill.
191
+ *
192
+ * Its own token rather than a reuse of `surface-active`, which means "this
193
+ * row is being pressed". A skeleton is never interactive, so borrowing an
194
+ * interaction token would tie the two together for any future re-theme.
195
+ *
196
+ * Exempt from contrast rules: skeletons are `aria-hidden` decoration
197
+ * standing in for content that has not arrived, so there is nothing for a
198
+ * reader to perceive and WCAG 1.4.11 does not apply.
199
+ */
200
+ readonly skeleton: `var(--color-${string})`;
201
+ /** Primary body and heading text. */
202
+ readonly text: `var(--color-${string})`;
203
+ /**
204
+ * Secondary text, column labels, help text.
205
+ *
206
+ * `neutral-600`, not `500`. A table header is muted text on `surface-subtle`,
207
+ * and at `500` that pairing reached only 4.41:1 — passing on white, failing
208
+ * WCAG 1.4.3 on the recessed surface this token is most often used against.
209
+ * `600` clears it at 6.90:1 and is still 2.3× lighter than `text`, so the
210
+ * hierarchy survives.
211
+ */
212
+ readonly 'text-muted': `var(--color-${string})`;
213
+ /** Placeholders and de-emphasised metadata. */
214
+ readonly 'text-subtle': `var(--color-${string})`;
215
+ /** Text on a disabled control. */
216
+ readonly 'text-disabled': `var(--color-${string})`;
217
+ /**
218
+ * Decorative hairline: row separators, card outlines.
219
+ *
220
+ * Deliberately below 3:1 against the surface. Do not use it for the boundary
221
+ * of an interactive control — see {@link semanticColorLight['border-control']}.
222
+ */
223
+ readonly border: `var(--color-${string})`;
224
+ /** Emphasised decorative border: dividers that need to read as structure. */
225
+ readonly 'border-strong': `var(--color-${string})`;
226
+ /** Barely-there separation inside a dense group. */
227
+ readonly 'border-subtle': `var(--color-${string})`;
228
+ /**
229
+ * Boundary of an interactive control — text inputs, checkboxes, outlined
230
+ * buttons.
231
+ *
232
+ * WCAG 1.4.11 requires 3:1 against the adjacent surface for the visual
233
+ * boundary of a UI component. `border` manages only 1.24:1 and
234
+ * `border-strong` 1.49:1, so neither is legal here; this token is the
235
+ * lightest neutral that clears the bar (4.83:1 on `surface`).
236
+ */
237
+ readonly 'border-control': `var(--color-${string})`;
238
+ /** Focus ring. Never remove the ring — recolour it. */
239
+ readonly 'focus-ring': `var(--color-${string})`;
240
+ /** Base colour shadows are mixed from. */
241
+ readonly shadow: `var(--color-${string})`;
242
+ readonly 'neutral-solid': `var(--color-${string})`;
243
+ readonly 'neutral-solid-hover': `var(--color-${string})`;
244
+ readonly 'neutral-on-solid': `var(--color-${string})`;
245
+ readonly 'neutral-subtle': `var(--color-${string})`;
246
+ readonly 'neutral-on-subtle': `var(--color-${string})`;
247
+ readonly 'neutral-border': `var(--color-${string})`;
248
+ readonly 'primary-solid': `var(--color-${string})`;
249
+ readonly 'primary-solid-hover': `var(--color-${string})`;
250
+ readonly 'primary-on-solid': `var(--color-${string})`;
251
+ readonly 'primary-subtle': `var(--color-${string})`;
252
+ readonly 'primary-on-subtle': `var(--color-${string})`;
253
+ readonly 'primary-border': `var(--color-${string})`;
254
+ readonly 'success-solid': `var(--color-${string})`;
255
+ readonly 'success-solid-hover': `var(--color-${string})`;
256
+ readonly 'success-on-solid': `var(--color-${string})`;
257
+ readonly 'success-subtle': `var(--color-${string})`;
258
+ readonly 'success-on-subtle': `var(--color-${string})`;
259
+ readonly 'success-border': `var(--color-${string})`;
260
+ readonly 'warning-solid': `var(--color-${string})`;
261
+ readonly 'warning-solid-hover': `var(--color-${string})`;
262
+ readonly 'warning-on-solid': `var(--color-${string})`;
263
+ readonly 'warning-subtle': `var(--color-${string})`;
264
+ readonly 'warning-on-subtle': `var(--color-${string})`;
265
+ readonly 'warning-border': `var(--color-${string})`;
266
+ readonly 'danger-solid': `var(--color-${string})`;
267
+ readonly 'danger-solid-hover': `var(--color-${string})`;
268
+ readonly 'danger-on-solid': `var(--color-${string})`;
269
+ readonly 'danger-subtle': `var(--color-${string})`;
270
+ readonly 'danger-on-subtle': `var(--color-${string})`;
271
+ readonly 'danger-border': `var(--color-${string})`;
272
+ };
273
+ /**
274
+ * Dark-mode semantic colours, applied under `.dark`.
275
+ *
276
+ * Solid fills use the bright `400` step with dark text rather than mirroring
277
+ * light mode's `600` with white text. On a near-black page a `600` fill only
278
+ * reaches 3.6–4.4:1 against the background — the button itself becomes hard to
279
+ * locate even though its label is legible. The `400` fill scores 7.4–8.5:1 on
280
+ * both label and background.
281
+ */
282
+ export declare const semanticColorDark: {
283
+ readonly background: `var(--color-${string})`;
284
+ readonly surface: `var(--color-${string})`;
285
+ readonly 'surface-subtle': `var(--color-${string})`;
286
+ readonly 'surface-hover': `var(--color-${string})`;
287
+ readonly 'surface-active': `var(--color-${string})`;
288
+ readonly 'surface-selected': `var(--color-${string})`;
289
+ readonly 'surface-disabled': `var(--color-${string})`;
290
+ readonly skeleton: `var(--color-${string})`;
291
+ readonly text: `var(--color-${string})`;
292
+ readonly 'text-muted': `var(--color-${string})`;
293
+ readonly 'text-subtle': `var(--color-${string})`;
294
+ readonly 'text-disabled': `var(--color-${string})`;
295
+ readonly border: `var(--color-${string})`;
296
+ readonly 'border-strong': `var(--color-${string})`;
297
+ readonly 'border-subtle': `var(--color-${string})`;
298
+ readonly 'border-control': `var(--color-${string})`;
299
+ readonly 'focus-ring': `var(--color-${string})`;
300
+ readonly shadow: `var(--color-${string})`;
301
+ readonly 'neutral-solid': `var(--color-${string})`;
302
+ readonly 'neutral-solid-hover': `var(--color-${string})`;
303
+ readonly 'neutral-on-solid': `var(--color-${string})`;
304
+ readonly 'neutral-subtle': `var(--color-${string})`;
305
+ readonly 'neutral-on-subtle': `var(--color-${string})`;
306
+ readonly 'neutral-border': `var(--color-${string})`;
307
+ readonly 'primary-solid': `var(--color-${string})`;
308
+ readonly 'primary-solid-hover': `var(--color-${string})`;
309
+ readonly 'primary-on-solid': `var(--color-${string})`;
310
+ readonly 'primary-subtle': `var(--color-${string})`;
311
+ readonly 'primary-on-subtle': `var(--color-${string})`;
312
+ readonly 'primary-border': `var(--color-${string})`;
313
+ readonly 'success-solid': `var(--color-${string})`;
314
+ readonly 'success-solid-hover': `var(--color-${string})`;
315
+ readonly 'success-on-solid': `var(--color-${string})`;
316
+ readonly 'success-subtle': `var(--color-${string})`;
317
+ readonly 'success-on-subtle': `var(--color-${string})`;
318
+ readonly 'success-border': `var(--color-${string})`;
319
+ readonly 'warning-solid': `var(--color-${string})`;
320
+ readonly 'warning-solid-hover': `var(--color-${string})`;
321
+ readonly 'warning-on-solid': `var(--color-${string})`;
322
+ readonly 'warning-subtle': `var(--color-${string})`;
323
+ readonly 'warning-on-subtle': `var(--color-${string})`;
324
+ readonly 'warning-border': `var(--color-${string})`;
325
+ readonly 'danger-solid': `var(--color-${string})`;
326
+ readonly 'danger-solid-hover': `var(--color-${string})`;
327
+ readonly 'danger-on-solid': `var(--color-${string})`;
328
+ readonly 'danger-subtle': `var(--color-${string})`;
329
+ readonly 'danger-on-subtle': `var(--color-${string})`;
330
+ readonly 'danger-border': `var(--color-${string})`;
331
+ };
332
+ /** Names of every semantic colour token. */
333
+ export type SemanticColorName = keyof typeof semanticColorLight;
package/dist/css.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Emits the Tailwind v4 `@theme` block for the rowkit token set.
3
+ *
4
+ * The TypeScript objects are the single source of truth; this function derives
5
+ * the stylesheet from them, so the two cannot drift. `css.test.ts` asserts that
6
+ * every token in every scale reaches the output.
7
+ *
8
+ * Dark mode works by repointing *semantic* tokens under `.dark`. Primitives are
9
+ * emitted once and never change — `--color-primary-600` is the same colour in
10
+ * both themes; what changes is which primitive `--color-primary-solid` points
11
+ * at. That is why the theme can flip without a single hardcoded colour moving.
12
+ *
13
+ * @returns The complete stylesheet, ready to write to disk.
14
+ */
15
+ export declare function buildThemeCss(): string;