@senakster/senui-tokens 0.0.0-stage → 1.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/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # @senakster/senui-tokens
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e1cbee4: Adds `--senui-motion-duration-rotation` (960 ms): one turn of essential continuous rotation, such as a progress spinner, and nothing else. It has no reduced-motion value: slow it with the reduce factor instead, as `calc(var(--senui-motion-duration-rotation) * (4 - 3 * var(--senui-motion-reduce)))`, so a consumer who sets `--senui-motion-reduce` to `0` slows it too. The `motion.duration` descriptions now say that colour and opacity keep their duration under reduced motion.
8
+
9
+ ## 1.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - 7952797: The first stable release. From 1.0.0 on, a component, prop, value, class, token or export is renamed or removed only in a major version, after a minor that deprecates it: a deprecated prop warns once in development and says what to use instead, and the docs mark it. New tokens, components and props come in minor versions.
14
+
15
+ `@senakster/senui-tokens` is a peer dependency of `@senakster/senui`, so an app has exactly one copy of the tokens: install both packages, and load `@senakster/senui-tokens/tokens.css` before any component styles. A major version of the tokens is always a major version of the components too.
16
+
17
+ `tokens.css` declares the cascade layers `senui.tokens`, `senui.base` and `senui.components`, so your own unlayered styles override every token and component without raising specificity. Colours follow the page's colour scheme through `light-dark()`, and motion follows `prefers-reduced-motion`. Override a token on `:root, [data-theme]`; a subtree with `data-theme="light"` or `data-theme="dark"` gets its own background and text colour.
18
+
19
+ Load every component's styles with `@senakster/senui/styles.css`, or only the ones you use with `@senakster/senui/styles/<Name>.css`. A component's stylesheet loads the styles of the components it renders, so `styles/Dialog.css` brings Button's. Every module is its own file in the package, so a bundler keeps only the components you import.
20
+
21
+ Each component's accessibility is documented as a contract in `components.json` and on its docs page, including what your app must still do. Some manual screen-reader and keyboard runs predate later changes to their component; the docs mark those runs as not re-tested.
22
+
23
+ ### Minor Changes
24
+
25
+ - 9459bf1: Add the `--senui-motion-easing-linear` token, for continuous motion such as the loading spinner.
26
+ - 9459bf1: Add the `--senui-color-bg-backdrop` token (the scrim behind a modal) and `--senui-size-dialog-sm`, `-md` and `-lg` (dialog widths).
27
+ - 9459bf1: Add the `--senui-size-illustration-sm` and `-lg` tokens (an illustration beside text, and above it).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 senakster
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 CHANGED
@@ -1,3 +1,164 @@
1
- # Temporary Holding Version
1
+ # @senakster/senui-tokens
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Design tokens for senui: CSS custom properties in `@layer senui.tokens`, plus a typed JS map.
4
+
5
+ ```css
6
+ @import '@senakster/senui-tokens/tokens.css';
7
+ ```
8
+
9
+ ```ts
10
+ import { tokens } from '@senakster/senui-tokens';
11
+ tokens['color.fg.default'].cssVar; // '--senui-color-fg-default'
12
+ ```
13
+
14
+ Every token in `tokens.css` is public API. Renaming or removing one is a major version.
15
+
16
+ ## Writing tokens
17
+
18
+ Tokens are written by hand in `src/*.tokens.json`, in the
19
+ [DTCG 2025.10](https://www.designtokens.org/tr/2025.10/format/) format, and built by
20
+ `pnpm build` (`node sd.config.mjs && tsdown`). The pipeline is described in ADR-0004.
21
+
22
+ [`examples/example.tokens.json`](examples/example.tokens.json) is a complete example covering
23
+ every category and every `$extensions.senui` key. It isn't built or shipped, but a test builds it,
24
+ so it always passes the pipeline's checks. Copy from it freely; the values are placeholders.
25
+
26
+ ### Tiers
27
+
28
+ - **Primitives** go under a top-level `primitive` group: `primitive.color.blue.600`. They are
29
+ never emitted. A token that aliases one compiles to its resolved value, so consumers can't
30
+ depend on primitive names.
31
+ - **Semantic tokens** are every other top-level group (`color`, `space`, `radius`, `font`,
32
+ `motion`, `elevation`, `size`). The path becomes the custom property: `color.fg.danger` →
33
+ `--senui-color-fg-danger`.
34
+ - **Component tokens** (`--senui-button-bg`) are not defined here. Each component defines its own
35
+ with semantic defaults.
36
+
37
+ **State variants are suffixed siblings, never children** (ADR-0005): `color.bg.accent`,
38
+ `color.bg.accent-hover`, `color.bg.accent-active`. A base token stays a leaf, so adding a state
39
+ never renames it.
40
+
41
+ ### Values
42
+
43
+ Use the DTCG object forms:
44
+
45
+ ```json
46
+ { "$type": "color", "$value": { "colorSpace": "oklch", "components": [0.55, 0.2, 260] } }
47
+ { "$type": "dimension", "$value": { "value": 12, "unit": "px" } }
48
+ { "$type": "duration", "$value": { "value": 120, "unit": "ms" } }
49
+ ```
50
+
51
+ Colours are authored in oklch and emitted as `oklch()`. Any colour space DTCG allows also works,
52
+ and it is kept as authored.
53
+
54
+ Make a shadow's `color` a reference to a public colour token (`"{color.shadow}"`). It is then
55
+ emitted as `var(--senui-color-shadow)`, so the shadow follows that token's dark value. A
56
+ primitive reference or an inline colour is resolved to a single value and stays the same in both
57
+ themes.
58
+
59
+ ### `$extensions.senui`
60
+
61
+ | Key | On | Meaning |
62
+ | --------------- | ------------------- | -------------------------------------------------------------------------------------------- |
63
+ | `dark` | every public colour | Required. The dark-theme value or alias; the build emits `light-dark(<$value>, <dark>)`. |
64
+ | `contrast` | public colours | `[{ "against": "{color.bg.default}", "min": 4.5 }, …]`: at least one pair, unless `surface`. |
65
+ | `surface` | public colours | `true` opts the colour out of contrast pairs. For backgrounds and WCAG-exempt colours only. |
66
+ | `reducedMotion` | any token | The value under `prefers-reduced-motion: reduce`. |
67
+
68
+ `dark` is only allowed on colour tokens, because `light-dark()` only takes colours.
69
+
70
+ Every public colour needs either contrast pairs or `"surface": true` (ADR-0005). Use `surface` for
71
+ backgrounds and fills, and for colours WCAG exempts: disabled controls, decorative dividers and
72
+ shadows. Say which in the token's `$description`, because opting out has to be visible.
73
+
74
+ Each `contrast` pair is checked in both themes, after compositing translucent colours. An
75
+ out-of-sRGB colour is measured both gamut-mapped and clipped, and the lower ratio counts. Use 4.5
76
+ for text and 3 for non-text (borders, focus rings, icons). A pair below its minimum fails the build
77
+ and names the token, the background, the theme and the ratio, to three decimals. So does a
78
+ malformed pair, such as a missing or misspelled `min`.
79
+
80
+ ### Rules the CSS adds
81
+
82
+ - **Themed subtrees are self-contained.** `tokens.css` sets `background-color`, `color` and
83
+ `accent-color` on every `[data-theme]` element, in `@layer senui.base`. A token set must define
84
+ `color.bg.default`, `color.fg.default` and `color.fg.accent`, or the build fails.
85
+ - **Motion is multiplied by `--senui-motion-reduce`**, which is `1` normally and `0` under reduced
86
+ motion. Components write `calc(var(--senui-motion-distance-md) * var(--senui-motion-reduce))`, so
87
+ a consumer who overrides a distance can't bring motion back.
88
+ - **Focus rings** are drawn as `outline: var(--senui-focus-ring-width) solid
89
+ var(--senui-color-focus-ring)` with `outline-offset: var(--senui-focus-ring-offset)`. That puts
90
+ the ring on the page background, which is what its contrast pairs are checked against. Never use
91
+ `box-shadow` for a ring: forced-colours mode removes it.
92
+ - **Elevated surfaces** (`bg.raised` plus an `elevation` shadow) also need a border, for the same
93
+ reason.
94
+
95
+ Example:
96
+
97
+ ```json
98
+ {
99
+ "primitive": {
100
+ "color": {
101
+ "ink": {
102
+ "$type": "color",
103
+ "$value": { "colorSpace": "oklch", "components": [0.2, 0.02, 260] }
104
+ },
105
+ "paper": {
106
+ "$type": "color",
107
+ "$value": { "colorSpace": "oklch", "components": [0.98, 0.005, 260] }
108
+ }
109
+ }
110
+ },
111
+ "color": {
112
+ "bg": {
113
+ "default": {
114
+ "$type": "color",
115
+ "$value": "{primitive.color.paper}",
116
+ "$extensions": { "senui": { "dark": "{primitive.color.ink}", "surface": true } }
117
+ }
118
+ },
119
+ "fg": {
120
+ "default": {
121
+ "$type": "color",
122
+ "$description": "Body text",
123
+ "$value": "{primitive.color.ink}",
124
+ "$extensions": {
125
+ "senui": {
126
+ "dark": "{primitive.color.paper}",
127
+ "contrast": [{ "against": "{color.bg.default}", "min": 4.5 }]
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Theming
137
+
138
+ `color-scheme: light dark` is set on `:where(:root)`, so the default follows the operating system.
139
+ `data-theme="light"` or `data-theme="dark"` on any element fixes the theme for that subtree, and
140
+ that element gets its own background and text colour.
141
+
142
+ A light-only app opts out of dark mode with one unlayered rule:
143
+
144
+ ```css
145
+ :root {
146
+ color-scheme: light;
147
+ }
148
+ ```
149
+
150
+ To override a colour, give both themes. A single value replaces the dark value too, and it skips
151
+ the contrast check that the token passed:
152
+
153
+ ```css
154
+ :root,
155
+ [data-theme] {
156
+ --senui-color-fg-accent: light-dark(oklch(50% 0.15 150), oklch(75% 0.15 150));
157
+ }
158
+ ```
159
+
160
+ Tokens are declared on `:root` and again on every `[data-theme]` element (ADR-0010). Build tools
161
+ that don't yet target `light-dark()`, including Vite's default production build, rewrite it into
162
+ variables that resolve where the token is declared; declaring it on each themed subtree keeps those
163
+ subtrees correct. The cost is that an override on `:root` alone stops at the first `data-theme`
164
+ element, so include `[data-theme]` in your selector.
@@ -0,0 +1,469 @@
1
+ //#region src/generated/tokens.d.ts
2
+ export declare const tokens: {
3
+ readonly "border.width.default": {
4
+ readonly cssVar: "--senui-border-width-default";
5
+ readonly type: "dimension";
6
+ readonly value: "1px";
7
+ };
8
+ readonly "border.width.strong": {
9
+ readonly cssVar: "--senui-border-width-strong";
10
+ readonly type: "dimension";
11
+ readonly value: "2px";
12
+ readonly description: "Invalid and selected controls, so state is not shown by colour alone.";
13
+ };
14
+ readonly "color.bg.accent": {
15
+ readonly cssVar: "--senui-color-bg-accent";
16
+ readonly type: "color";
17
+ readonly value: "oklch(52% 0.18 260)";
18
+ readonly description: "Fill for primary actions.";
19
+ readonly dark: "oklch(72% 0.14 255)";
20
+ };
21
+ readonly "color.bg.accent-active": {
22
+ readonly cssVar: "--senui-color-bg-accent-active";
23
+ readonly type: "color";
24
+ readonly value: "oklch(40% 0.15 260)";
25
+ readonly description: "bg.accent while pressed.";
26
+ readonly dark: "oklch(86% 0.07 255)";
27
+ };
28
+ readonly "color.bg.accent-hover": {
29
+ readonly cssVar: "--senui-color-bg-accent-hover";
30
+ readonly type: "color";
31
+ readonly value: "oklch(46% 0.17 260)";
32
+ readonly description: "bg.accent under the pointer.";
33
+ readonly dark: "oklch(80% 0.11 255)";
34
+ };
35
+ readonly "color.bg.backdrop": {
36
+ readonly cssVar: "--senui-color-bg-backdrop";
37
+ readonly type: "color";
38
+ readonly value: "rgb(0% 0% 0% / 0.5)";
39
+ readonly description: "The scrim behind a modal (the dialog's ::backdrop). Translucent, so the page stays visible but recedes. Darker in dark mode, where the page is already dark. Never a text background.";
40
+ readonly dark: "rgb(0% 0% 0% / 0.7)";
41
+ };
42
+ readonly "color.bg.danger": {
43
+ readonly cssVar: "--senui-color-bg-danger";
44
+ readonly type: "color";
45
+ readonly value: "oklch(53% 0.2 27)";
46
+ readonly description: "Fill for destructive actions.";
47
+ readonly dark: "oklch(72% 0.16 25)";
48
+ };
49
+ readonly "color.bg.danger-active": {
50
+ readonly cssVar: "--senui-color-bg-danger-active";
51
+ readonly type: "color";
52
+ readonly value: "oklch(41% 0.17 27)";
53
+ readonly description: "bg.danger while pressed.";
54
+ readonly dark: "oklch(86% 0.07 25)";
55
+ };
56
+ readonly "color.bg.danger-hover": {
57
+ readonly cssVar: "--senui-color-bg-danger-hover";
58
+ readonly type: "color";
59
+ readonly value: "oklch(47% 0.19 27)";
60
+ readonly description: "bg.danger under the pointer.";
61
+ readonly dark: "oklch(80% 0.11 25)";
62
+ };
63
+ readonly "color.bg.default": {
64
+ readonly cssVar: "--senui-color-bg-default";
65
+ readonly type: "color";
66
+ readonly value: "oklch(99% 0.003 260)";
67
+ readonly description: "Page and surface background.";
68
+ readonly dark: "oklch(16% 0.012 260)";
69
+ };
70
+ readonly "color.bg.disabled": {
71
+ readonly cssVar: "--senui-color-bg-disabled";
72
+ readonly type: "color";
73
+ readonly value: "oklch(97% 0.005 260)";
74
+ readonly description: "Fill of a disabled control. Exempt from contrast (WCAG 1.4.3, 1.4.11).";
75
+ readonly dark: "oklch(22% 0.015 260)";
76
+ };
77
+ readonly "color.bg.raised": {
78
+ readonly cssVar: "--senui-color-bg-raised";
79
+ readonly type: "color";
80
+ readonly value: "oklch(99% 0.003 260)";
81
+ readonly description: "Popovers, menus, dialogs. Lighter than bg.default in dark mode, because shadows barely show there. Pair with a border: forced-colours mode removes shadows.";
82
+ readonly dark: "oklch(19% 0.013 260)";
83
+ };
84
+ readonly "color.bg.subtle": {
85
+ readonly cssVar: "--senui-color-bg-subtle";
86
+ readonly type: "color";
87
+ readonly value: "oklch(97% 0.005 260)";
88
+ readonly description: "Recessed areas: wells, table stripes, secondary buttons.";
89
+ readonly dark: "oklch(22% 0.015 260)";
90
+ };
91
+ readonly "color.bg.subtle-active": {
92
+ readonly cssVar: "--senui-color-bg-subtle-active";
93
+ readonly type: "color";
94
+ readonly value: "oklch(91% 0.008 260)";
95
+ readonly description: "bg.subtle while pressed.";
96
+ readonly dark: "oklch(30% 0.015 260)";
97
+ };
98
+ readonly "color.bg.subtle-hover": {
99
+ readonly cssVar: "--senui-color-bg-subtle-hover";
100
+ readonly type: "color";
101
+ readonly value: "oklch(94% 0.007 260)";
102
+ readonly description: "bg.subtle under the pointer.";
103
+ readonly dark: "oklch(26% 0.015 260)";
104
+ };
105
+ readonly "color.border.danger": {
106
+ readonly cssVar: "--senui-color-border-danger";
107
+ readonly type: "color";
108
+ readonly value: "oklch(53% 0.2 27)";
109
+ readonly description: "Outline of an invalid field (WCAG 1.4.11). Use with border.width.strong and an error message, never alone (1.4.1).";
110
+ readonly dark: "oklch(72% 0.16 25)";
111
+ };
112
+ readonly "color.border.default": {
113
+ readonly cssVar: "--senui-color-border-default";
114
+ readonly type: "color";
115
+ readonly value: "oklch(85% 0.01 260)";
116
+ readonly description: "Decorative dividers. Too faint to identify a control; use border.strong for that.";
117
+ readonly dark: "oklch(30% 0.015 260)";
118
+ };
119
+ readonly "color.border.disabled": {
120
+ readonly cssVar: "--senui-color-border-disabled";
121
+ readonly type: "color";
122
+ readonly value: "oklch(85% 0.01 260)";
123
+ readonly description: "Outline of a disabled control. Exempt from contrast (WCAG 1.4.11).";
124
+ readonly dark: "oklch(30% 0.015 260)";
125
+ };
126
+ readonly "color.border.strong": {
127
+ readonly cssVar: "--senui-color-border-strong";
128
+ readonly type: "color";
129
+ readonly value: "oklch(60% 0.015 260)";
130
+ readonly description: "Borders that identify a control, such as input outlines (WCAG 1.4.11).";
131
+ readonly dark: "oklch(60% 0.015 260)";
132
+ };
133
+ readonly "color.fg.accent": {
134
+ readonly cssVar: "--senui-color-fg-accent";
135
+ readonly type: "color";
136
+ readonly value: "oklch(52% 0.18 260)";
137
+ readonly description: "Links and accent text.";
138
+ readonly dark: "oklch(72% 0.14 255)";
139
+ };
140
+ readonly "color.fg.danger": {
141
+ readonly cssVar: "--senui-color-fg-danger";
142
+ readonly type: "color";
143
+ readonly value: "oklch(53% 0.2 27)";
144
+ readonly description: "Error messages. Colour alone must never carry an error (WCAG 1.4.1): pair it with text or an icon.";
145
+ readonly dark: "oklch(72% 0.16 25)";
146
+ };
147
+ readonly "color.fg.default": {
148
+ readonly cssVar: "--senui-color-fg-default";
149
+ readonly type: "color";
150
+ readonly value: "oklch(22% 0.015 260)";
151
+ readonly description: "Body text.";
152
+ readonly dark: "oklch(97% 0.005 260)";
153
+ };
154
+ readonly "color.fg.disabled": {
155
+ readonly cssVar: "--senui-color-fg-disabled";
156
+ readonly type: "color";
157
+ readonly value: "oklch(60% 0.015 260)";
158
+ readonly description: "Text of a disabled control. Exempt from contrast (WCAG 1.4.3).";
159
+ readonly dark: "oklch(50% 0.015 260)";
160
+ };
161
+ readonly "color.fg.muted": {
162
+ readonly cssVar: "--senui-color-fg-muted";
163
+ readonly type: "color";
164
+ readonly value: "oklch(50% 0.015 260)";
165
+ readonly description: "Secondary text: hints, captions, placeholders.";
166
+ readonly dark: "oklch(85% 0.01 260)";
167
+ };
168
+ readonly "color.fg.on-accent": {
169
+ readonly cssVar: "--senui-color-fg-on-accent";
170
+ readonly type: "color";
171
+ readonly value: "oklch(99% 0.003 260)";
172
+ readonly description: "Text and icons on the accent fills.";
173
+ readonly dark: "oklch(16% 0.012 260)";
174
+ };
175
+ readonly "color.fg.on-danger": {
176
+ readonly cssVar: "--senui-color-fg-on-danger";
177
+ readonly type: "color";
178
+ readonly value: "oklch(99% 0.003 260)";
179
+ readonly description: "Text and icons on the danger fills.";
180
+ readonly dark: "oklch(16% 0.012 260)";
181
+ };
182
+ readonly "color.focus.ring": {
183
+ readonly cssVar: "--senui-color-focus-ring";
184
+ readonly type: "color";
185
+ readonly value: "oklch(52% 0.18 260)";
186
+ readonly description: "Focus indicator (WCAG 1.4.11, 2.4.7). Drawn as an outline offset by focus.ring-offset, so it sits on the page background, never on a fill (ADR-0005).";
187
+ readonly dark: "oklch(72% 0.14 255)";
188
+ };
189
+ readonly "color.shadow": {
190
+ readonly cssVar: "--senui-color-shadow";
191
+ readonly type: "color";
192
+ readonly value: "rgb(0% 0% 0% / 0.12)";
193
+ readonly description: "Shadow colour used by the elevation tokens. Decorative.";
194
+ readonly dark: "rgb(0% 0% 0% / 0.5)";
195
+ };
196
+ readonly "elevation.1": {
197
+ readonly cssVar: "--senui-elevation-1";
198
+ readonly type: "shadow";
199
+ readonly value: "0px 1px 2px 0px var(--senui-color-shadow)";
200
+ };
201
+ readonly "elevation.2": {
202
+ readonly cssVar: "--senui-elevation-2";
203
+ readonly type: "shadow";
204
+ readonly value: "0px 4px 12px 0px var(--senui-color-shadow)";
205
+ };
206
+ readonly "focus.ring-offset": {
207
+ readonly cssVar: "--senui-focus-ring-offset";
208
+ readonly type: "dimension";
209
+ readonly value: "2px";
210
+ readonly description: "Gap between a control and its focus ring, so the ring sits on the page background (ADR-0005).";
211
+ };
212
+ readonly "focus.ring-width": {
213
+ readonly cssVar: "--senui-focus-ring-width";
214
+ readonly type: "dimension";
215
+ readonly value: "2px";
216
+ readonly description: "Outline width of the focus ring.";
217
+ };
218
+ readonly "font.family.mono": {
219
+ readonly cssVar: "--senui-font-family-mono";
220
+ readonly type: "fontFamily";
221
+ readonly value: "ui-monospace, 'SF Mono', Menlo, Consolas, 'Cascadia Mono', 'Liberation Mono', monospace";
222
+ };
223
+ readonly "font.family.sans": {
224
+ readonly cssVar: "--senui-font-family-sans";
225
+ readonly type: "fontFamily";
226
+ readonly value: "system-ui, sans-serif";
227
+ };
228
+ readonly "font.line-height.normal": {
229
+ readonly cssVar: "--senui-font-line-height-normal";
230
+ readonly type: "number";
231
+ readonly value: "1.5";
232
+ };
233
+ readonly "font.line-height.tight": {
234
+ readonly cssVar: "--senui-font-line-height-tight";
235
+ readonly type: "number";
236
+ readonly value: "1.25";
237
+ };
238
+ readonly "font.size.lg": {
239
+ readonly cssVar: "--senui-font-size-lg";
240
+ readonly type: "dimension";
241
+ readonly value: "1.25rem";
242
+ };
243
+ readonly "font.size.md": {
244
+ readonly cssVar: "--senui-font-size-md";
245
+ readonly type: "dimension";
246
+ readonly value: "1rem";
247
+ };
248
+ readonly "font.size.sm": {
249
+ readonly cssVar: "--senui-font-size-sm";
250
+ readonly type: "dimension";
251
+ readonly value: "0.875rem";
252
+ };
253
+ readonly "font.weight.bold": {
254
+ readonly cssVar: "--senui-font-weight-bold";
255
+ readonly type: "fontWeight";
256
+ readonly value: "700";
257
+ };
258
+ readonly "font.weight.medium": {
259
+ readonly cssVar: "--senui-font-weight-medium";
260
+ readonly type: "fontWeight";
261
+ readonly value: "500";
262
+ };
263
+ readonly "font.weight.regular": {
264
+ readonly cssVar: "--senui-font-weight-regular";
265
+ readonly type: "fontWeight";
266
+ readonly value: "400";
267
+ };
268
+ readonly "motion.distance.md": {
269
+ readonly cssVar: "--senui-motion-distance-md";
270
+ readonly type: "dimension";
271
+ readonly value: "8px";
272
+ readonly reducedMotion: "0px";
273
+ };
274
+ readonly "motion.distance.sm": {
275
+ readonly cssVar: "--senui-motion-distance-sm";
276
+ readonly type: "dimension";
277
+ readonly value: "4px";
278
+ readonly reducedMotion: "0px";
279
+ };
280
+ readonly "motion.duration.fast": {
281
+ readonly cssVar: "--senui-motion-duration-fast";
282
+ readonly type: "duration";
283
+ readonly value: "120ms";
284
+ };
285
+ readonly "motion.duration.moderate": {
286
+ readonly cssVar: "--senui-motion-duration-moderate";
287
+ readonly type: "duration";
288
+ readonly value: "200ms";
289
+ };
290
+ readonly "motion.duration.rotation": {
291
+ readonly cssVar: "--senui-motion-duration-rotation";
292
+ readonly type: "duration";
293
+ readonly value: "960ms";
294
+ readonly description: "One turn of essential continuous rotation (a progress spinner), and nothing else. Slow it by the factor rather than stopping it: × (4 − 3 × motion.reduce) (ADR-0017, ADR-0024).";
295
+ };
296
+ readonly "motion.duration.slow": {
297
+ readonly cssVar: "--senui-motion-duration-slow";
298
+ readonly type: "duration";
299
+ readonly value: "320ms";
300
+ };
301
+ readonly "motion.easing.enter": {
302
+ readonly cssVar: "--senui-motion-easing-enter";
303
+ readonly type: "cubicBezier";
304
+ readonly value: "cubic-bezier(0, 0, 0, 1)";
305
+ };
306
+ readonly "motion.easing.exit": {
307
+ readonly cssVar: "--senui-motion-easing-exit";
308
+ readonly type: "cubicBezier";
309
+ readonly value: "cubic-bezier(0.3, 0, 1, 1)";
310
+ };
311
+ readonly "motion.easing.linear": {
312
+ readonly cssVar: "--senui-motion-easing-linear";
313
+ readonly type: "cubicBezier";
314
+ readonly value: "cubic-bezier(0, 0, 1, 1)";
315
+ readonly description: "Constant speed, for continuous motion such as a loading spinner.";
316
+ };
317
+ readonly "motion.easing.standard": {
318
+ readonly cssVar: "--senui-motion-easing-standard";
319
+ readonly type: "cubicBezier";
320
+ readonly value: "cubic-bezier(0.2, 0, 0, 1)";
321
+ };
322
+ readonly "motion.reduce": {
323
+ readonly cssVar: "--senui-motion-reduce";
324
+ readonly type: "number";
325
+ readonly value: "1";
326
+ readonly description: "1 normally, 0 under prefers-reduced-motion. Multiply every movement by it, so an override of a distance can't bring motion back (ADR-0005).";
327
+ readonly reducedMotion: "0";
328
+ };
329
+ readonly "motion.scale.enter": {
330
+ readonly cssVar: "--senui-motion-scale-enter";
331
+ readonly type: "number";
332
+ readonly value: "0.96";
333
+ readonly description: "Starting scale for elements that grow in. 1 under reduced motion; apply as 1 - (1 - scale) × motion.reduce.";
334
+ readonly reducedMotion: "1";
335
+ };
336
+ readonly "radius.full": {
337
+ readonly cssVar: "--senui-radius-full";
338
+ readonly type: "dimension";
339
+ readonly value: "9999px";
340
+ };
341
+ readonly "radius.lg": {
342
+ readonly cssVar: "--senui-radius-lg";
343
+ readonly type: "dimension";
344
+ readonly value: "12px";
345
+ };
346
+ readonly "radius.md": {
347
+ readonly cssVar: "--senui-radius-md";
348
+ readonly type: "dimension";
349
+ readonly value: "8px";
350
+ };
351
+ readonly "radius.sm": {
352
+ readonly cssVar: "--senui-radius-sm";
353
+ readonly type: "dimension";
354
+ readonly value: "4px";
355
+ };
356
+ readonly "size.control.lg": {
357
+ readonly cssVar: "--senui-size-control-lg";
358
+ readonly type: "dimension";
359
+ readonly value: "2.5rem";
360
+ };
361
+ readonly "size.control.md": {
362
+ readonly cssVar: "--senui-size-control-md";
363
+ readonly type: "dimension";
364
+ readonly value: "2rem";
365
+ };
366
+ readonly "size.control.sm": {
367
+ readonly cssVar: "--senui-size-control-sm";
368
+ readonly type: "dimension";
369
+ readonly value: "1.5rem";
370
+ };
371
+ readonly "size.dialog.lg": {
372
+ readonly cssVar: "--senui-size-dialog-lg";
373
+ readonly type: "dimension";
374
+ readonly value: "48rem";
375
+ };
376
+ readonly "size.dialog.md": {
377
+ readonly cssVar: "--senui-size-dialog-md";
378
+ readonly type: "dimension";
379
+ readonly value: "32rem";
380
+ };
381
+ readonly "size.dialog.sm": {
382
+ readonly cssVar: "--senui-size-dialog-sm";
383
+ readonly type: "dimension";
384
+ readonly value: "24rem";
385
+ };
386
+ readonly "size.illustration.lg": {
387
+ readonly cssVar: "--senui-size-illustration-lg";
388
+ readonly type: "dimension";
389
+ readonly value: "5rem";
390
+ };
391
+ readonly "size.illustration.sm": {
392
+ readonly cssVar: "--senui-size-illustration-sm";
393
+ readonly type: "dimension";
394
+ readonly value: "2.5rem";
395
+ };
396
+ readonly "size.target.min": {
397
+ readonly cssVar: "--senui-size-target-min";
398
+ readonly type: "dimension";
399
+ readonly value: "24px";
400
+ readonly description: "Smallest allowed pointer target (WCAG 2.5.8). In px, because WCAG measures in CSS px.";
401
+ };
402
+ readonly "space.1": {
403
+ readonly cssVar: "--senui-space-1";
404
+ readonly type: "dimension";
405
+ readonly value: "0.25rem";
406
+ };
407
+ readonly "space.10": {
408
+ readonly cssVar: "--senui-space-10";
409
+ readonly type: "dimension";
410
+ readonly value: "2.5rem";
411
+ };
412
+ readonly "space.12": {
413
+ readonly cssVar: "--senui-space-12";
414
+ readonly type: "dimension";
415
+ readonly value: "3rem";
416
+ };
417
+ readonly "space.2": {
418
+ readonly cssVar: "--senui-space-2";
419
+ readonly type: "dimension";
420
+ readonly value: "0.5rem";
421
+ };
422
+ readonly "space.3": {
423
+ readonly cssVar: "--senui-space-3";
424
+ readonly type: "dimension";
425
+ readonly value: "0.75rem";
426
+ };
427
+ readonly "space.4": {
428
+ readonly cssVar: "--senui-space-4";
429
+ readonly type: "dimension";
430
+ readonly value: "1rem";
431
+ };
432
+ readonly "space.5": {
433
+ readonly cssVar: "--senui-space-5";
434
+ readonly type: "dimension";
435
+ readonly value: "1.25rem";
436
+ };
437
+ readonly "space.6": {
438
+ readonly cssVar: "--senui-space-6";
439
+ readonly type: "dimension";
440
+ readonly value: "1.5rem";
441
+ };
442
+ readonly "space.8": {
443
+ readonly cssVar: "--senui-space-8";
444
+ readonly type: "dimension";
445
+ readonly value: "2rem";
446
+ };
447
+ };
448
+ //#endregion
449
+ //#region src/types.d.ts
450
+ /** One public design token, as it appears in `tokens.css` (ADR-0004). */
451
+ interface Token {
452
+ /** The custom property to use in CSS, e.g. `--senui-color-fg-danger`. */
453
+ readonly cssVar: `--senui-${string}`;
454
+ /** The DTCG `$type`, e.g. `color`, `dimension`, `duration`. */
455
+ readonly type: string;
456
+ /** The CSS value; for colours, the light-theme value. */
457
+ readonly value: string;
458
+ /** The dark-theme value. Present on every colour token. */
459
+ readonly dark?: string;
460
+ /** The value under `prefers-reduced-motion: reduce`, where it differs. */
461
+ readonly reducedMotion?: string;
462
+ readonly description?: string;
463
+ }
464
+ //#endregion
465
+ //#region src/index.d.ts
466
+ /** The key of a public token, e.g. `'color.fg.danger'`. */
467
+ export type TokenKey = keyof typeof tokens;
468
+ //#endregion
469
+ export type { Token };
package/dist/index.js ADDED
@@ -0,0 +1,449 @@
1
+ //#region src/generated/tokens.ts
2
+ const tokens = {
3
+ "border.width.default": {
4
+ "cssVar": "--senui-border-width-default",
5
+ "type": "dimension",
6
+ "value": "1px"
7
+ },
8
+ "border.width.strong": {
9
+ "cssVar": "--senui-border-width-strong",
10
+ "type": "dimension",
11
+ "value": "2px",
12
+ "description": "Invalid and selected controls, so state is not shown by colour alone."
13
+ },
14
+ "color.bg.accent": {
15
+ "cssVar": "--senui-color-bg-accent",
16
+ "type": "color",
17
+ "value": "oklch(52% 0.18 260)",
18
+ "description": "Fill for primary actions.",
19
+ "dark": "oklch(72% 0.14 255)"
20
+ },
21
+ "color.bg.accent-active": {
22
+ "cssVar": "--senui-color-bg-accent-active",
23
+ "type": "color",
24
+ "value": "oklch(40% 0.15 260)",
25
+ "description": "bg.accent while pressed.",
26
+ "dark": "oklch(86% 0.07 255)"
27
+ },
28
+ "color.bg.accent-hover": {
29
+ "cssVar": "--senui-color-bg-accent-hover",
30
+ "type": "color",
31
+ "value": "oklch(46% 0.17 260)",
32
+ "description": "bg.accent under the pointer.",
33
+ "dark": "oklch(80% 0.11 255)"
34
+ },
35
+ "color.bg.backdrop": {
36
+ "cssVar": "--senui-color-bg-backdrop",
37
+ "type": "color",
38
+ "value": "rgb(0% 0% 0% / 0.5)",
39
+ "description": "The scrim behind a modal (the dialog's ::backdrop). Translucent, so the page stays visible but recedes. Darker in dark mode, where the page is already dark. Never a text background.",
40
+ "dark": "rgb(0% 0% 0% / 0.7)"
41
+ },
42
+ "color.bg.danger": {
43
+ "cssVar": "--senui-color-bg-danger",
44
+ "type": "color",
45
+ "value": "oklch(53% 0.2 27)",
46
+ "description": "Fill for destructive actions.",
47
+ "dark": "oklch(72% 0.16 25)"
48
+ },
49
+ "color.bg.danger-active": {
50
+ "cssVar": "--senui-color-bg-danger-active",
51
+ "type": "color",
52
+ "value": "oklch(41% 0.17 27)",
53
+ "description": "bg.danger while pressed.",
54
+ "dark": "oklch(86% 0.07 25)"
55
+ },
56
+ "color.bg.danger-hover": {
57
+ "cssVar": "--senui-color-bg-danger-hover",
58
+ "type": "color",
59
+ "value": "oklch(47% 0.19 27)",
60
+ "description": "bg.danger under the pointer.",
61
+ "dark": "oklch(80% 0.11 25)"
62
+ },
63
+ "color.bg.default": {
64
+ "cssVar": "--senui-color-bg-default",
65
+ "type": "color",
66
+ "value": "oklch(99% 0.003 260)",
67
+ "description": "Page and surface background.",
68
+ "dark": "oklch(16% 0.012 260)"
69
+ },
70
+ "color.bg.disabled": {
71
+ "cssVar": "--senui-color-bg-disabled",
72
+ "type": "color",
73
+ "value": "oklch(97% 0.005 260)",
74
+ "description": "Fill of a disabled control. Exempt from contrast (WCAG 1.4.3, 1.4.11).",
75
+ "dark": "oklch(22% 0.015 260)"
76
+ },
77
+ "color.bg.raised": {
78
+ "cssVar": "--senui-color-bg-raised",
79
+ "type": "color",
80
+ "value": "oklch(99% 0.003 260)",
81
+ "description": "Popovers, menus, dialogs. Lighter than bg.default in dark mode, because shadows barely show there. Pair with a border: forced-colours mode removes shadows.",
82
+ "dark": "oklch(19% 0.013 260)"
83
+ },
84
+ "color.bg.subtle": {
85
+ "cssVar": "--senui-color-bg-subtle",
86
+ "type": "color",
87
+ "value": "oklch(97% 0.005 260)",
88
+ "description": "Recessed areas: wells, table stripes, secondary buttons.",
89
+ "dark": "oklch(22% 0.015 260)"
90
+ },
91
+ "color.bg.subtle-active": {
92
+ "cssVar": "--senui-color-bg-subtle-active",
93
+ "type": "color",
94
+ "value": "oklch(91% 0.008 260)",
95
+ "description": "bg.subtle while pressed.",
96
+ "dark": "oklch(30% 0.015 260)"
97
+ },
98
+ "color.bg.subtle-hover": {
99
+ "cssVar": "--senui-color-bg-subtle-hover",
100
+ "type": "color",
101
+ "value": "oklch(94% 0.007 260)",
102
+ "description": "bg.subtle under the pointer.",
103
+ "dark": "oklch(26% 0.015 260)"
104
+ },
105
+ "color.border.danger": {
106
+ "cssVar": "--senui-color-border-danger",
107
+ "type": "color",
108
+ "value": "oklch(53% 0.2 27)",
109
+ "description": "Outline of an invalid field (WCAG 1.4.11). Use with border.width.strong and an error message, never alone (1.4.1).",
110
+ "dark": "oklch(72% 0.16 25)"
111
+ },
112
+ "color.border.default": {
113
+ "cssVar": "--senui-color-border-default",
114
+ "type": "color",
115
+ "value": "oklch(85% 0.01 260)",
116
+ "description": "Decorative dividers. Too faint to identify a control; use border.strong for that.",
117
+ "dark": "oklch(30% 0.015 260)"
118
+ },
119
+ "color.border.disabled": {
120
+ "cssVar": "--senui-color-border-disabled",
121
+ "type": "color",
122
+ "value": "oklch(85% 0.01 260)",
123
+ "description": "Outline of a disabled control. Exempt from contrast (WCAG 1.4.11).",
124
+ "dark": "oklch(30% 0.015 260)"
125
+ },
126
+ "color.border.strong": {
127
+ "cssVar": "--senui-color-border-strong",
128
+ "type": "color",
129
+ "value": "oklch(60% 0.015 260)",
130
+ "description": "Borders that identify a control, such as input outlines (WCAG 1.4.11).",
131
+ "dark": "oklch(60% 0.015 260)"
132
+ },
133
+ "color.fg.accent": {
134
+ "cssVar": "--senui-color-fg-accent",
135
+ "type": "color",
136
+ "value": "oklch(52% 0.18 260)",
137
+ "description": "Links and accent text.",
138
+ "dark": "oklch(72% 0.14 255)"
139
+ },
140
+ "color.fg.danger": {
141
+ "cssVar": "--senui-color-fg-danger",
142
+ "type": "color",
143
+ "value": "oklch(53% 0.2 27)",
144
+ "description": "Error messages. Colour alone must never carry an error (WCAG 1.4.1): pair it with text or an icon.",
145
+ "dark": "oklch(72% 0.16 25)"
146
+ },
147
+ "color.fg.default": {
148
+ "cssVar": "--senui-color-fg-default",
149
+ "type": "color",
150
+ "value": "oklch(22% 0.015 260)",
151
+ "description": "Body text.",
152
+ "dark": "oklch(97% 0.005 260)"
153
+ },
154
+ "color.fg.disabled": {
155
+ "cssVar": "--senui-color-fg-disabled",
156
+ "type": "color",
157
+ "value": "oklch(60% 0.015 260)",
158
+ "description": "Text of a disabled control. Exempt from contrast (WCAG 1.4.3).",
159
+ "dark": "oklch(50% 0.015 260)"
160
+ },
161
+ "color.fg.muted": {
162
+ "cssVar": "--senui-color-fg-muted",
163
+ "type": "color",
164
+ "value": "oklch(50% 0.015 260)",
165
+ "description": "Secondary text: hints, captions, placeholders.",
166
+ "dark": "oklch(85% 0.01 260)"
167
+ },
168
+ "color.fg.on-accent": {
169
+ "cssVar": "--senui-color-fg-on-accent",
170
+ "type": "color",
171
+ "value": "oklch(99% 0.003 260)",
172
+ "description": "Text and icons on the accent fills.",
173
+ "dark": "oklch(16% 0.012 260)"
174
+ },
175
+ "color.fg.on-danger": {
176
+ "cssVar": "--senui-color-fg-on-danger",
177
+ "type": "color",
178
+ "value": "oklch(99% 0.003 260)",
179
+ "description": "Text and icons on the danger fills.",
180
+ "dark": "oklch(16% 0.012 260)"
181
+ },
182
+ "color.focus.ring": {
183
+ "cssVar": "--senui-color-focus-ring",
184
+ "type": "color",
185
+ "value": "oklch(52% 0.18 260)",
186
+ "description": "Focus indicator (WCAG 1.4.11, 2.4.7). Drawn as an outline offset by focus.ring-offset, so it sits on the page background, never on a fill (ADR-0005).",
187
+ "dark": "oklch(72% 0.14 255)"
188
+ },
189
+ "color.shadow": {
190
+ "cssVar": "--senui-color-shadow",
191
+ "type": "color",
192
+ "value": "rgb(0% 0% 0% / 0.12)",
193
+ "description": "Shadow colour used by the elevation tokens. Decorative.",
194
+ "dark": "rgb(0% 0% 0% / 0.5)"
195
+ },
196
+ "elevation.1": {
197
+ "cssVar": "--senui-elevation-1",
198
+ "type": "shadow",
199
+ "value": "0px 1px 2px 0px var(--senui-color-shadow)"
200
+ },
201
+ "elevation.2": {
202
+ "cssVar": "--senui-elevation-2",
203
+ "type": "shadow",
204
+ "value": "0px 4px 12px 0px var(--senui-color-shadow)"
205
+ },
206
+ "focus.ring-offset": {
207
+ "cssVar": "--senui-focus-ring-offset",
208
+ "type": "dimension",
209
+ "value": "2px",
210
+ "description": "Gap between a control and its focus ring, so the ring sits on the page background (ADR-0005)."
211
+ },
212
+ "focus.ring-width": {
213
+ "cssVar": "--senui-focus-ring-width",
214
+ "type": "dimension",
215
+ "value": "2px",
216
+ "description": "Outline width of the focus ring."
217
+ },
218
+ "font.family.mono": {
219
+ "cssVar": "--senui-font-family-mono",
220
+ "type": "fontFamily",
221
+ "value": "ui-monospace, 'SF Mono', Menlo, Consolas, 'Cascadia Mono', 'Liberation Mono', monospace"
222
+ },
223
+ "font.family.sans": {
224
+ "cssVar": "--senui-font-family-sans",
225
+ "type": "fontFamily",
226
+ "value": "system-ui, sans-serif"
227
+ },
228
+ "font.line-height.normal": {
229
+ "cssVar": "--senui-font-line-height-normal",
230
+ "type": "number",
231
+ "value": "1.5"
232
+ },
233
+ "font.line-height.tight": {
234
+ "cssVar": "--senui-font-line-height-tight",
235
+ "type": "number",
236
+ "value": "1.25"
237
+ },
238
+ "font.size.lg": {
239
+ "cssVar": "--senui-font-size-lg",
240
+ "type": "dimension",
241
+ "value": "1.25rem"
242
+ },
243
+ "font.size.md": {
244
+ "cssVar": "--senui-font-size-md",
245
+ "type": "dimension",
246
+ "value": "1rem"
247
+ },
248
+ "font.size.sm": {
249
+ "cssVar": "--senui-font-size-sm",
250
+ "type": "dimension",
251
+ "value": "0.875rem"
252
+ },
253
+ "font.weight.bold": {
254
+ "cssVar": "--senui-font-weight-bold",
255
+ "type": "fontWeight",
256
+ "value": "700"
257
+ },
258
+ "font.weight.medium": {
259
+ "cssVar": "--senui-font-weight-medium",
260
+ "type": "fontWeight",
261
+ "value": "500"
262
+ },
263
+ "font.weight.regular": {
264
+ "cssVar": "--senui-font-weight-regular",
265
+ "type": "fontWeight",
266
+ "value": "400"
267
+ },
268
+ "motion.distance.md": {
269
+ "cssVar": "--senui-motion-distance-md",
270
+ "type": "dimension",
271
+ "value": "8px",
272
+ "reducedMotion": "0px"
273
+ },
274
+ "motion.distance.sm": {
275
+ "cssVar": "--senui-motion-distance-sm",
276
+ "type": "dimension",
277
+ "value": "4px",
278
+ "reducedMotion": "0px"
279
+ },
280
+ "motion.duration.fast": {
281
+ "cssVar": "--senui-motion-duration-fast",
282
+ "type": "duration",
283
+ "value": "120ms"
284
+ },
285
+ "motion.duration.moderate": {
286
+ "cssVar": "--senui-motion-duration-moderate",
287
+ "type": "duration",
288
+ "value": "200ms"
289
+ },
290
+ "motion.duration.rotation": {
291
+ "cssVar": "--senui-motion-duration-rotation",
292
+ "type": "duration",
293
+ "value": "960ms",
294
+ "description": "One turn of essential continuous rotation (a progress spinner), and nothing else. Slow it by the factor rather than stopping it: × (4 − 3 × motion.reduce) (ADR-0017, ADR-0024)."
295
+ },
296
+ "motion.duration.slow": {
297
+ "cssVar": "--senui-motion-duration-slow",
298
+ "type": "duration",
299
+ "value": "320ms"
300
+ },
301
+ "motion.easing.enter": {
302
+ "cssVar": "--senui-motion-easing-enter",
303
+ "type": "cubicBezier",
304
+ "value": "cubic-bezier(0, 0, 0, 1)"
305
+ },
306
+ "motion.easing.exit": {
307
+ "cssVar": "--senui-motion-easing-exit",
308
+ "type": "cubicBezier",
309
+ "value": "cubic-bezier(0.3, 0, 1, 1)"
310
+ },
311
+ "motion.easing.linear": {
312
+ "cssVar": "--senui-motion-easing-linear",
313
+ "type": "cubicBezier",
314
+ "value": "cubic-bezier(0, 0, 1, 1)",
315
+ "description": "Constant speed, for continuous motion such as a loading spinner."
316
+ },
317
+ "motion.easing.standard": {
318
+ "cssVar": "--senui-motion-easing-standard",
319
+ "type": "cubicBezier",
320
+ "value": "cubic-bezier(0.2, 0, 0, 1)"
321
+ },
322
+ "motion.reduce": {
323
+ "cssVar": "--senui-motion-reduce",
324
+ "type": "number",
325
+ "value": "1",
326
+ "description": "1 normally, 0 under prefers-reduced-motion. Multiply every movement by it, so an override of a distance can't bring motion back (ADR-0005).",
327
+ "reducedMotion": "0"
328
+ },
329
+ "motion.scale.enter": {
330
+ "cssVar": "--senui-motion-scale-enter",
331
+ "type": "number",
332
+ "value": "0.96",
333
+ "description": "Starting scale for elements that grow in. 1 under reduced motion; apply as 1 - (1 - scale) × motion.reduce.",
334
+ "reducedMotion": "1"
335
+ },
336
+ "radius.full": {
337
+ "cssVar": "--senui-radius-full",
338
+ "type": "dimension",
339
+ "value": "9999px"
340
+ },
341
+ "radius.lg": {
342
+ "cssVar": "--senui-radius-lg",
343
+ "type": "dimension",
344
+ "value": "12px"
345
+ },
346
+ "radius.md": {
347
+ "cssVar": "--senui-radius-md",
348
+ "type": "dimension",
349
+ "value": "8px"
350
+ },
351
+ "radius.sm": {
352
+ "cssVar": "--senui-radius-sm",
353
+ "type": "dimension",
354
+ "value": "4px"
355
+ },
356
+ "size.control.lg": {
357
+ "cssVar": "--senui-size-control-lg",
358
+ "type": "dimension",
359
+ "value": "2.5rem"
360
+ },
361
+ "size.control.md": {
362
+ "cssVar": "--senui-size-control-md",
363
+ "type": "dimension",
364
+ "value": "2rem"
365
+ },
366
+ "size.control.sm": {
367
+ "cssVar": "--senui-size-control-sm",
368
+ "type": "dimension",
369
+ "value": "1.5rem"
370
+ },
371
+ "size.dialog.lg": {
372
+ "cssVar": "--senui-size-dialog-lg",
373
+ "type": "dimension",
374
+ "value": "48rem"
375
+ },
376
+ "size.dialog.md": {
377
+ "cssVar": "--senui-size-dialog-md",
378
+ "type": "dimension",
379
+ "value": "32rem"
380
+ },
381
+ "size.dialog.sm": {
382
+ "cssVar": "--senui-size-dialog-sm",
383
+ "type": "dimension",
384
+ "value": "24rem"
385
+ },
386
+ "size.illustration.lg": {
387
+ "cssVar": "--senui-size-illustration-lg",
388
+ "type": "dimension",
389
+ "value": "5rem"
390
+ },
391
+ "size.illustration.sm": {
392
+ "cssVar": "--senui-size-illustration-sm",
393
+ "type": "dimension",
394
+ "value": "2.5rem"
395
+ },
396
+ "size.target.min": {
397
+ "cssVar": "--senui-size-target-min",
398
+ "type": "dimension",
399
+ "value": "24px",
400
+ "description": "Smallest allowed pointer target (WCAG 2.5.8). In px, because WCAG measures in CSS px."
401
+ },
402
+ "space.1": {
403
+ "cssVar": "--senui-space-1",
404
+ "type": "dimension",
405
+ "value": "0.25rem"
406
+ },
407
+ "space.10": {
408
+ "cssVar": "--senui-space-10",
409
+ "type": "dimension",
410
+ "value": "2.5rem"
411
+ },
412
+ "space.12": {
413
+ "cssVar": "--senui-space-12",
414
+ "type": "dimension",
415
+ "value": "3rem"
416
+ },
417
+ "space.2": {
418
+ "cssVar": "--senui-space-2",
419
+ "type": "dimension",
420
+ "value": "0.5rem"
421
+ },
422
+ "space.3": {
423
+ "cssVar": "--senui-space-3",
424
+ "type": "dimension",
425
+ "value": "0.75rem"
426
+ },
427
+ "space.4": {
428
+ "cssVar": "--senui-space-4",
429
+ "type": "dimension",
430
+ "value": "1rem"
431
+ },
432
+ "space.5": {
433
+ "cssVar": "--senui-space-5",
434
+ "type": "dimension",
435
+ "value": "1.25rem"
436
+ },
437
+ "space.6": {
438
+ "cssVar": "--senui-space-6",
439
+ "type": "dimension",
440
+ "value": "1.5rem"
441
+ },
442
+ "space.8": {
443
+ "cssVar": "--senui-space-8",
444
+ "type": "dimension",
445
+ "value": "2rem"
446
+ }
447
+ };
448
+ //#endregion
449
+ export { tokens };
@@ -0,0 +1,112 @@
1
+ /* Generated by @senakster/senui-tokens. Do not edit; change src/*.tokens.json instead. */
2
+ @layer senui.tokens, senui.base, senui.components;
3
+
4
+ @layer senui.tokens {
5
+ :where(:root) {
6
+ color-scheme: light dark;
7
+ }
8
+
9
+ :where([data-theme="light"]) {
10
+ color-scheme: light;
11
+ }
12
+
13
+ :where([data-theme="dark"]) {
14
+ color-scheme: dark;
15
+ }
16
+
17
+ :where(:root, [data-theme]) {
18
+ --senui-border-width-default: 1px;
19
+ --senui-border-width-strong: 2px;
20
+ --senui-color-bg-accent: light-dark(oklch(52% 0.18 260), oklch(72% 0.14 255));
21
+ --senui-color-bg-accent-active: light-dark(oklch(40% 0.15 260), oklch(86% 0.07 255));
22
+ --senui-color-bg-accent-hover: light-dark(oklch(46% 0.17 260), oklch(80% 0.11 255));
23
+ --senui-color-bg-backdrop: light-dark(rgb(0% 0% 0% / 0.5), rgb(0% 0% 0% / 0.7));
24
+ --senui-color-bg-danger: light-dark(oklch(53% 0.2 27), oklch(72% 0.16 25));
25
+ --senui-color-bg-danger-active: light-dark(oklch(41% 0.17 27), oklch(86% 0.07 25));
26
+ --senui-color-bg-danger-hover: light-dark(oklch(47% 0.19 27), oklch(80% 0.11 25));
27
+ --senui-color-bg-default: light-dark(oklch(99% 0.003 260), oklch(16% 0.012 260));
28
+ --senui-color-bg-disabled: light-dark(oklch(97% 0.005 260), oklch(22% 0.015 260));
29
+ --senui-color-bg-raised: light-dark(oklch(99% 0.003 260), oklch(19% 0.013 260));
30
+ --senui-color-bg-subtle: light-dark(oklch(97% 0.005 260), oklch(22% 0.015 260));
31
+ --senui-color-bg-subtle-active: light-dark(oklch(91% 0.008 260), oklch(30% 0.015 260));
32
+ --senui-color-bg-subtle-hover: light-dark(oklch(94% 0.007 260), oklch(26% 0.015 260));
33
+ --senui-color-border-danger: light-dark(oklch(53% 0.2 27), oklch(72% 0.16 25));
34
+ --senui-color-border-default: light-dark(oklch(85% 0.01 260), oklch(30% 0.015 260));
35
+ --senui-color-border-disabled: light-dark(oklch(85% 0.01 260), oklch(30% 0.015 260));
36
+ --senui-color-border-strong: light-dark(oklch(60% 0.015 260), oklch(60% 0.015 260));
37
+ --senui-color-fg-accent: light-dark(oklch(52% 0.18 260), oklch(72% 0.14 255));
38
+ --senui-color-fg-danger: light-dark(oklch(53% 0.2 27), oklch(72% 0.16 25));
39
+ --senui-color-fg-default: light-dark(oklch(22% 0.015 260), oklch(97% 0.005 260));
40
+ --senui-color-fg-disabled: light-dark(oklch(60% 0.015 260), oklch(50% 0.015 260));
41
+ --senui-color-fg-muted: light-dark(oklch(50% 0.015 260), oklch(85% 0.01 260));
42
+ --senui-color-fg-on-accent: light-dark(oklch(99% 0.003 260), oklch(16% 0.012 260));
43
+ --senui-color-fg-on-danger: light-dark(oklch(99% 0.003 260), oklch(16% 0.012 260));
44
+ --senui-color-focus-ring: light-dark(oklch(52% 0.18 260), oklch(72% 0.14 255));
45
+ --senui-color-shadow: light-dark(rgb(0% 0% 0% / 0.12), rgb(0% 0% 0% / 0.5));
46
+ --senui-elevation-1: 0px 1px 2px 0px var(--senui-color-shadow);
47
+ --senui-elevation-2: 0px 4px 12px 0px var(--senui-color-shadow);
48
+ --senui-focus-ring-offset: 2px;
49
+ --senui-focus-ring-width: 2px;
50
+ --senui-font-family-mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'Cascadia Mono', 'Liberation Mono', monospace;
51
+ --senui-font-family-sans: system-ui, sans-serif;
52
+ --senui-font-line-height-normal: 1.5;
53
+ --senui-font-line-height-tight: 1.25;
54
+ --senui-font-size-lg: 1.25rem;
55
+ --senui-font-size-md: 1rem;
56
+ --senui-font-size-sm: 0.875rem;
57
+ --senui-font-weight-bold: 700;
58
+ --senui-font-weight-medium: 500;
59
+ --senui-font-weight-regular: 400;
60
+ --senui-motion-distance-md: 8px;
61
+ --senui-motion-distance-sm: 4px;
62
+ --senui-motion-duration-fast: 120ms;
63
+ --senui-motion-duration-moderate: 200ms;
64
+ --senui-motion-duration-rotation: 960ms;
65
+ --senui-motion-duration-slow: 320ms;
66
+ --senui-motion-easing-enter: cubic-bezier(0, 0, 0, 1);
67
+ --senui-motion-easing-exit: cubic-bezier(0.3, 0, 1, 1);
68
+ --senui-motion-easing-linear: cubic-bezier(0, 0, 1, 1);
69
+ --senui-motion-easing-standard: cubic-bezier(0.2, 0, 0, 1);
70
+ --senui-motion-reduce: 1;
71
+ --senui-motion-scale-enter: 0.96;
72
+ --senui-radius-full: 9999px;
73
+ --senui-radius-lg: 12px;
74
+ --senui-radius-md: 8px;
75
+ --senui-radius-sm: 4px;
76
+ --senui-size-control-lg: 2.5rem;
77
+ --senui-size-control-md: 2rem;
78
+ --senui-size-control-sm: 1.5rem;
79
+ --senui-size-dialog-lg: 48rem;
80
+ --senui-size-dialog-md: 32rem;
81
+ --senui-size-dialog-sm: 24rem;
82
+ --senui-size-illustration-lg: 5rem;
83
+ --senui-size-illustration-sm: 2.5rem;
84
+ --senui-size-target-min: 24px;
85
+ --senui-space-1: 0.25rem;
86
+ --senui-space-10: 2.5rem;
87
+ --senui-space-12: 3rem;
88
+ --senui-space-2: 0.5rem;
89
+ --senui-space-3: 0.75rem;
90
+ --senui-space-4: 1rem;
91
+ --senui-space-5: 1.25rem;
92
+ --senui-space-6: 1.5rem;
93
+ --senui-space-8: 2rem;
94
+ }
95
+
96
+ @media (prefers-reduced-motion: reduce) {
97
+ :where(:root, [data-theme]) {
98
+ --senui-motion-distance-md: 0px;
99
+ --senui-motion-distance-sm: 0px;
100
+ --senui-motion-reduce: 0;
101
+ --senui-motion-scale-enter: 1;
102
+ }
103
+ }
104
+ }
105
+
106
+ @layer senui.base {
107
+ :where([data-theme]) {
108
+ background-color: var(--senui-color-bg-default);
109
+ color: var(--senui-color-fg-default);
110
+ accent-color: var(--senui-color-fg-accent);
111
+ }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@senakster/senui-tokens",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.1.0",
4
+ "description": "Design tokens for senui: CSS custom properties and a typed JS map.",
5
+ "license": "MIT",
6
+ "homepage": "https://senakster.github.io/senui/",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/senakster/senui.git",
10
+ "directory": "packages/tokens"
11
+ },
12
+ "bugs": "https://github.com/senakster/senui/issues",
13
+ "type": "module",
14
+ "sideEffects": [
15
+ "**/*.css"
16
+ ],
17
+ "files": [
18
+ "dist",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "exports": {
22
+ ".": "./dist/index.js",
23
+ "./tokens.css": "./dist/tokens.css",
24
+ "./package.json": "./package.json"
25
+ },
26
+ "devDependencies": {
27
+ "colorjs.io": "0.5.2",
28
+ "publint": "0.3.25",
29
+ "style-dictionary": "5.6.1",
30
+ "tsdown": "0.23.1",
31
+ "typescript": "6.0.3",
32
+ "vite": "8.3.4",
33
+ "vitest": "5.0.4"
34
+ },
35
+ "scripts": {
36
+ "build": "node sd.config.mjs && tsdown",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run"
39
+ }
6
40
  }