@handamade/psi-tokens 0.25.0 → 0.27.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/dist/acme.css +1 -0
- package/dist/components/checkbox.js +3 -1
- package/dist/components/checkbox.vars.css +2 -1
- package/dist/components/radio-group.d.ts +5 -0
- package/dist/components/radio-group.js +15 -0
- package/dist/components/radio-group.vars.css +12 -0
- package/dist/components/registry.js +6 -0
- package/dist/components/text.d.ts +6 -0
- package/dist/components/text.js +9 -0
- package/dist/components/text.vars.css +6 -0
- package/dist/components/textarea.d.ts +6 -0
- package/dist/components/textarea.js +18 -0
- package/dist/components/textarea.vars.css +15 -0
- package/dist/components.css +38 -1
- package/dist/contrast-matrix.d.ts +5 -0
- package/dist/contrast-matrix.js +8 -0
- package/dist/dark.css +1 -0
- package/dist/dtcg/acme.json +12 -0
- package/dist/dtcg/dark.json +12 -0
- package/dist/dtcg/ember.json +12 -0
- package/dist/dtcg/light.json +12 -0
- package/dist/ember.css +1 -0
- package/dist/generate/derive.js +6 -3
- package/dist/generate/palette.js +2 -2
- package/dist/light.css +1 -0
- package/dist/resolved/acme.json +14 -0
- package/dist/resolved/dark.json +14 -0
- package/dist/resolved/ember.json +14 -0
- package/dist/resolved/light.json +14 -0
- package/dist/scope-map.json +51 -0
- package/dist/theme/gate.js +2 -2
- package/dist/themes/dark.js +4 -0
- package/dist/themes/light.js +4 -0
- package/dist/types/index.d.ts +1 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
package/dist/acme.css
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
--psi-border-faint: oklch(from var(--psi-fg-primary) l c h / 0.08);
|
|
41
41
|
--psi-border-neutral: oklch(from var(--psi-fg-primary) l c h / 0.15);
|
|
42
42
|
--psi-border-strong: oklch(from var(--psi-fg-primary) l c h / 0.3);
|
|
43
|
+
--psi-border-control: oklch(from var(--psi-fg-primary) l c h / 0.55);
|
|
43
44
|
--psi-border-focus: var(--psi-fg-accent);
|
|
44
45
|
--psi-scrim-soft: oklch(from var(--psi-bg-primary) l c h / 0.25);
|
|
45
46
|
--psi-scrim-medium: oklch(from var(--psi-bg-primary) l c h / 0.35);
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
export const checkboxVars = {
|
|
3
3
|
fg: "var(--psi-fg-primary)",
|
|
4
4
|
"box-bg": "var(--psi-bg-primary)",
|
|
5
|
-
"box-border": "var(--psi-border-
|
|
5
|
+
"box-border": "var(--psi-border-control)",
|
|
6
6
|
"box-bg-checked": "var(--psi-fill-accent)",
|
|
7
7
|
"box-border-checked": "var(--psi-fill-accent)",
|
|
8
|
+
// D108: an unchecked, aria-invalid box, in the error colour Input uses.
|
|
9
|
+
"box-border-error": "var(--psi-fg-danger)",
|
|
8
10
|
// D56: tracks a sharper theme down, never rounder than radius-4 — the box
|
|
9
11
|
// is ~16px, so the 8px control default would read as a circle.
|
|
10
12
|
"box-radius": "min(var(--psi-control-radius), var(--psi-radius-4))",
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
:where(:root, [data-psi-theme]) {
|
|
3
3
|
--psi-checkbox-fg: var(--psi-fg-primary);
|
|
4
4
|
--psi-checkbox-box-bg: var(--psi-bg-primary);
|
|
5
|
-
--psi-checkbox-box-border: var(--psi-border-
|
|
5
|
+
--psi-checkbox-box-border: var(--psi-border-control);
|
|
6
6
|
--psi-checkbox-box-bg-checked: var(--psi-fill-accent);
|
|
7
7
|
--psi-checkbox-box-border-checked: var(--psi-fill-accent);
|
|
8
|
+
--psi-checkbox-box-border-error: var(--psi-fg-danger);
|
|
8
9
|
--psi-checkbox-box-radius: min(var(--psi-control-radius), var(--psi-radius-4));
|
|
9
10
|
--psi-checkbox-check-fg: var(--psi-fg-on-accent);
|
|
10
11
|
--psi-checkbox-focus-ring: var(--psi-border-focus);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** RadioGroup / Radio component tokens (--psi-radio-group-*) — D104. One
|
|
2
|
+
* module serves both, so one prefix. The checkbox's recipe on a circle: the
|
|
3
|
+
* checked circle fills with the accent and shows a dot in fg-on-accent,
|
|
4
|
+
* exactly the checkbox's checked pair, already gated. No new contrast pair. */
|
|
5
|
+
export declare const radioGroupVars: Record<string, string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** RadioGroup / Radio component tokens (--psi-radio-group-*) — D104. One
|
|
2
|
+
* module serves both, so one prefix. The checkbox's recipe on a circle: the
|
|
3
|
+
* checked circle fills with the accent and shows a dot in fg-on-accent,
|
|
4
|
+
* exactly the checkbox's checked pair, already gated. No new contrast pair. */
|
|
5
|
+
export const radioGroupVars = {
|
|
6
|
+
fg: "var(--psi-fg-primary)",
|
|
7
|
+
"circle-bg": "var(--psi-bg-primary)",
|
|
8
|
+
"circle-border": "var(--psi-border-control)",
|
|
9
|
+
"circle-bg-checked": "var(--psi-fill-accent)",
|
|
10
|
+
"circle-border-checked": "var(--psi-fill-accent)",
|
|
11
|
+
// D108: an unchecked, aria-invalid circle, in the error colour Input uses.
|
|
12
|
+
"circle-border-error": "var(--psi-fg-danger)",
|
|
13
|
+
"dot-fg": "var(--psi-fg-on-accent)",
|
|
14
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
15
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-radio-group-fg: var(--psi-fg-primary);
|
|
4
|
+
--psi-radio-group-circle-bg: var(--psi-bg-primary);
|
|
5
|
+
--psi-radio-group-circle-border: var(--psi-border-control);
|
|
6
|
+
--psi-radio-group-circle-bg-checked: var(--psi-fill-accent);
|
|
7
|
+
--psi-radio-group-circle-border-checked: var(--psi-fill-accent);
|
|
8
|
+
--psi-radio-group-circle-border-error: var(--psi-fg-danger);
|
|
9
|
+
--psi-radio-group-dot-fg: var(--psi-fg-on-accent);
|
|
10
|
+
--psi-radio-group-focus-ring: var(--psi-border-focus);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -15,6 +15,7 @@ import { switchVars } from "./switch.js";
|
|
|
15
15
|
import { tableVars } from "./table.js";
|
|
16
16
|
import { tagVars } from "./tag.js";
|
|
17
17
|
import { toolbarVars } from "./toolbar.js";
|
|
18
|
+
import { textVars } from "./text.js";
|
|
18
19
|
import { tooltipVars } from "./tooltip.js";
|
|
19
20
|
import { cardVars } from "./card.js";
|
|
20
21
|
import { controlVars } from "./control.js";
|
|
@@ -27,6 +28,8 @@ import { toastVars } from "./toast.js";
|
|
|
27
28
|
import { tabsVars } from "./tabs.js";
|
|
28
29
|
import { fieldVars } from "./field.js";
|
|
29
30
|
import { descriptionListVars } from "./description-list.js";
|
|
31
|
+
import { textareaVars } from "./textarea.js";
|
|
32
|
+
import { radioGroupVars } from "./radio-group.js";
|
|
30
33
|
export const componentVars = {
|
|
31
34
|
"app-shell": appShellVars,
|
|
32
35
|
banner: bannerVars,
|
|
@@ -42,6 +45,7 @@ export const componentVars = {
|
|
|
42
45
|
menu: menuVars,
|
|
43
46
|
navbar: navbarVars,
|
|
44
47
|
panel: panelVars,
|
|
48
|
+
"radio-group": radioGroupVars,
|
|
45
49
|
select: selectVars,
|
|
46
50
|
skeleton: skeletonVars,
|
|
47
51
|
"nav-tree": navTreeVars,
|
|
@@ -52,7 +56,9 @@ export const componentVars = {
|
|
|
52
56
|
tabs: tabsVars,
|
|
53
57
|
"description-list": descriptionListVars,
|
|
54
58
|
tag: tagVars,
|
|
59
|
+
textarea: textareaVars,
|
|
55
60
|
toolbar: toolbarVars,
|
|
61
|
+
text: textVars,
|
|
56
62
|
toast: toastVars,
|
|
57
63
|
tooltip: tooltipVars,
|
|
58
64
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Text component tokens (--psi-text-fg*) — D109. Pure indirection onto the
|
|
2
|
+
* semantic layer. fgPrimary and fgSecondary are gated at 4.5 on every
|
|
3
|
+
* surface in wcagAAPairs, so no new contrast pair. The prefix shares
|
|
4
|
+
* `--psi-text-` with the type styles (`--psi-text-14-20-regular`); no type
|
|
5
|
+
* style is named `fg`, so the two cannot collide. */
|
|
6
|
+
export declare const textVars: Record<string, string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Text component tokens (--psi-text-fg*) — D109. Pure indirection onto the
|
|
2
|
+
* semantic layer. fgPrimary and fgSecondary are gated at 4.5 on every
|
|
3
|
+
* surface in wcagAAPairs, so no new contrast pair. The prefix shares
|
|
4
|
+
* `--psi-text-` with the type styles (`--psi-text-14-20-regular`); no type
|
|
5
|
+
* style is named `fg`, so the two cannot collide. */
|
|
6
|
+
export const textVars = {
|
|
7
|
+
fg: "var(--psi-fg-primary)",
|
|
8
|
+
"fg-secondary": "var(--psi-fg-secondary)",
|
|
9
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Textarea component tokens (--psi-textarea-*) — D104. Pure aliases of the
|
|
2
|
+
* input's semantics and the 32px value ramp, so a multi-line field reads as
|
|
3
|
+
* the same control family as Input. Every value is an existing gated
|
|
4
|
+
* semantic; counter-fg is fg-secondary, gated at 4.5 on every surface. No
|
|
5
|
+
* size ramp: a textarea's height is its rows, not a control size. */
|
|
6
|
+
export declare const textareaVars: Record<string, string>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Textarea component tokens (--psi-textarea-*) — D104. Pure aliases of the
|
|
2
|
+
* input's semantics and the 32px value ramp, so a multi-line field reads as
|
|
3
|
+
* the same control family as Input. Every value is an existing gated
|
|
4
|
+
* semantic; counter-fg is fg-secondary, gated at 4.5 on every surface. No
|
|
5
|
+
* size ramp: a textarea's height is its rows, not a control size. */
|
|
6
|
+
export const textareaVars = {
|
|
7
|
+
bg: "var(--psi-bg-primary)",
|
|
8
|
+
fg: "var(--psi-fg-primary)",
|
|
9
|
+
placeholder: "var(--psi-fg-tertiary)",
|
|
10
|
+
border: "var(--psi-border-neutral)",
|
|
11
|
+
"border-hover": "var(--psi-border-strong)",
|
|
12
|
+
"border-error": "var(--psi-fg-danger)",
|
|
13
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
14
|
+
radius: "var(--psi-control-radius)",
|
|
15
|
+
"padding-inline": "var(--psi-control-value-32-padding-inline)",
|
|
16
|
+
font: "var(--psi-control-value-32-font)",
|
|
17
|
+
"counter-fg": "var(--psi-fg-secondary)",
|
|
18
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-textarea-bg: var(--psi-bg-primary);
|
|
4
|
+
--psi-textarea-fg: var(--psi-fg-primary);
|
|
5
|
+
--psi-textarea-placeholder: var(--psi-fg-tertiary);
|
|
6
|
+
--psi-textarea-border: var(--psi-border-neutral);
|
|
7
|
+
--psi-textarea-border-hover: var(--psi-border-strong);
|
|
8
|
+
--psi-textarea-border-error: var(--psi-fg-danger);
|
|
9
|
+
--psi-textarea-focus-ring: var(--psi-border-focus);
|
|
10
|
+
--psi-textarea-radius: var(--psi-control-radius);
|
|
11
|
+
--psi-textarea-padding-inline: var(--psi-control-value-32-padding-inline);
|
|
12
|
+
--psi-textarea-font: var(--psi-control-value-32-font);
|
|
13
|
+
--psi-textarea-counter-fg: var(--psi-fg-secondary);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/components.css
CHANGED
|
@@ -100,9 +100,10 @@
|
|
|
100
100
|
:where(:root, [data-psi-theme]) {
|
|
101
101
|
--psi-checkbox-fg: var(--psi-fg-primary);
|
|
102
102
|
--psi-checkbox-box-bg: var(--psi-bg-primary);
|
|
103
|
-
--psi-checkbox-box-border: var(--psi-border-
|
|
103
|
+
--psi-checkbox-box-border: var(--psi-border-control);
|
|
104
104
|
--psi-checkbox-box-bg-checked: var(--psi-fill-accent);
|
|
105
105
|
--psi-checkbox-box-border-checked: var(--psi-fill-accent);
|
|
106
|
+
--psi-checkbox-box-border-error: var(--psi-fg-danger);
|
|
106
107
|
--psi-checkbox-box-radius: min(var(--psi-control-radius), var(--psi-radius-4));
|
|
107
108
|
--psi-checkbox-check-fg: var(--psi-fg-on-accent);
|
|
108
109
|
--psi-checkbox-focus-ring: var(--psi-border-focus);
|
|
@@ -257,6 +258,19 @@
|
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
@layer psi.components {
|
|
262
|
+
:where(:root, [data-psi-theme]) {
|
|
263
|
+
--psi-radio-group-fg: var(--psi-fg-primary);
|
|
264
|
+
--psi-radio-group-circle-bg: var(--psi-bg-primary);
|
|
265
|
+
--psi-radio-group-circle-border: var(--psi-border-control);
|
|
266
|
+
--psi-radio-group-circle-bg-checked: var(--psi-fill-accent);
|
|
267
|
+
--psi-radio-group-circle-border-checked: var(--psi-fill-accent);
|
|
268
|
+
--psi-radio-group-circle-border-error: var(--psi-fg-danger);
|
|
269
|
+
--psi-radio-group-dot-fg: var(--psi-fg-on-accent);
|
|
270
|
+
--psi-radio-group-focus-ring: var(--psi-border-focus);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
260
274
|
@layer psi.components {
|
|
261
275
|
:where(:root, [data-psi-theme]) {
|
|
262
276
|
--psi-select-bg: var(--psi-bg-primary);
|
|
@@ -411,6 +425,22 @@
|
|
|
411
425
|
}
|
|
412
426
|
}
|
|
413
427
|
|
|
428
|
+
@layer psi.components {
|
|
429
|
+
:where(:root, [data-psi-theme]) {
|
|
430
|
+
--psi-textarea-bg: var(--psi-bg-primary);
|
|
431
|
+
--psi-textarea-fg: var(--psi-fg-primary);
|
|
432
|
+
--psi-textarea-placeholder: var(--psi-fg-tertiary);
|
|
433
|
+
--psi-textarea-border: var(--psi-border-neutral);
|
|
434
|
+
--psi-textarea-border-hover: var(--psi-border-strong);
|
|
435
|
+
--psi-textarea-border-error: var(--psi-fg-danger);
|
|
436
|
+
--psi-textarea-focus-ring: var(--psi-border-focus);
|
|
437
|
+
--psi-textarea-radius: var(--psi-control-radius);
|
|
438
|
+
--psi-textarea-padding-inline: var(--psi-control-value-32-padding-inline);
|
|
439
|
+
--psi-textarea-font: var(--psi-control-value-32-font);
|
|
440
|
+
--psi-textarea-counter-fg: var(--psi-fg-secondary);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
414
444
|
@layer psi.components {
|
|
415
445
|
:where(:root, [data-psi-theme]) {
|
|
416
446
|
--psi-toolbar-control-width: 200px;
|
|
@@ -418,6 +448,13 @@
|
|
|
418
448
|
}
|
|
419
449
|
}
|
|
420
450
|
|
|
451
|
+
@layer psi.components {
|
|
452
|
+
:where(:root, [data-psi-theme]) {
|
|
453
|
+
--psi-text-fg: var(--psi-fg-primary);
|
|
454
|
+
--psi-text-fg-secondary: var(--psi-fg-secondary);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
421
458
|
@layer psi.components {
|
|
422
459
|
:where(:root, [data-psi-theme]) {
|
|
423
460
|
--psi-toast-bg: var(--psi-surface-bg);
|
|
@@ -32,3 +32,8 @@ export declare const wcagAAPairs: ContrastPair[];
|
|
|
32
32
|
* button/tag label on its variant background ≥ 4.5"). Tint-variant labels are
|
|
33
33
|
* already covered by the fg-on-tint pairs in wcagAAPairs. */
|
|
34
34
|
export declare const componentLabelPairs: ContrastPair[];
|
|
35
|
+
/** WCAG 1.4.11 non-text contrast (D106): 3:1 for the visual information that
|
|
36
|
+
* identifies a control. An unchecked checkbox or radio is identified by its
|
|
37
|
+
* outline alone, against its own fill (bgPrimary) and the surface it sits on
|
|
38
|
+
* (bgPrimary on the canvas, bgSecondary on Panel, Dialog and Menu). */
|
|
39
|
+
export declare const nonTextPairs: ContrastPair[];
|
package/dist/contrast-matrix.js
CHANGED
|
@@ -136,3 +136,11 @@ export const componentLabelPairs = [
|
|
|
136
136
|
{ fg: "fgStaticBlack", bg: "fillWarning", minRatio: 4.5 }, // Tag warning (D22)
|
|
137
137
|
{ fg: "fgOnInverted", bg: "bgInverted", minRatio: 4.5 }, // Tooltip label (D46 inversion pair)
|
|
138
138
|
];
|
|
139
|
+
/** WCAG 1.4.11 non-text contrast (D106): 3:1 for the visual information that
|
|
140
|
+
* identifies a control. An unchecked checkbox or radio is identified by its
|
|
141
|
+
* outline alone, against its own fill (bgPrimary) and the surface it sits on
|
|
142
|
+
* (bgPrimary on the canvas, bgSecondary on Panel, Dialog and Menu). */
|
|
143
|
+
export const nonTextPairs = [
|
|
144
|
+
{ fg: "borderControl", bg: "bgPrimary", minRatio: 3 },
|
|
145
|
+
{ fg: "borderControl", bg: "bgSecondary", minRatio: 3 },
|
|
146
|
+
];
|
package/dist/dark.css
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
--psi-border-faint: oklch(from var(--psi-fg-primary) l c h / 0.08);
|
|
41
41
|
--psi-border-neutral: oklch(from var(--psi-fg-primary) l c h / 0.15);
|
|
42
42
|
--psi-border-strong: oklch(from var(--psi-fg-primary) l c h / 0.3);
|
|
43
|
+
--psi-border-control: oklch(from var(--psi-fg-primary) l c h / 0.4);
|
|
43
44
|
--psi-border-focus: var(--psi-fg-accent);
|
|
44
45
|
--psi-scrim-soft: oklch(from var(--psi-bg-primary) l c h / 0.25);
|
|
45
46
|
--psi-scrim-medium: oklch(from var(--psi-bg-primary) l c h / 0.35);
|
package/dist/dtcg/acme.json
CHANGED
|
@@ -420,6 +420,18 @@
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
},
|
|
423
|
+
"control": {
|
|
424
|
+
"$type": "color",
|
|
425
|
+
"$value": "#3c28258c",
|
|
426
|
+
"$description": "ref(fgPrimary) → alpha(0.55)",
|
|
427
|
+
"$extensions": {
|
|
428
|
+
"psi": {
|
|
429
|
+
"scopes": [
|
|
430
|
+
"border"
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
},
|
|
423
435
|
"focus": {
|
|
424
436
|
"$type": "color",
|
|
425
437
|
"$value": "#b10b00",
|
package/dist/dtcg/dark.json
CHANGED
|
@@ -420,6 +420,18 @@
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
},
|
|
423
|
+
"control": {
|
|
424
|
+
"$type": "color",
|
|
425
|
+
"$value": "#e3e8ee66",
|
|
426
|
+
"$description": "ref(fgPrimary) → alpha(0.4)",
|
|
427
|
+
"$extensions": {
|
|
428
|
+
"psi": {
|
|
429
|
+
"scopes": [
|
|
430
|
+
"border"
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
},
|
|
423
435
|
"focus": {
|
|
424
436
|
"$type": "color",
|
|
425
437
|
"$value": "#7daeff",
|
package/dist/dtcg/ember.json
CHANGED
|
@@ -420,6 +420,18 @@
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
},
|
|
423
|
+
"control": {
|
|
424
|
+
"$type": "color",
|
|
425
|
+
"$value": "#f3ede666",
|
|
426
|
+
"$description": "ref(fgPrimary) → alpha(0.4)",
|
|
427
|
+
"$extensions": {
|
|
428
|
+
"psi": {
|
|
429
|
+
"scopes": [
|
|
430
|
+
"border"
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
},
|
|
423
435
|
"focus": {
|
|
424
436
|
"$type": "color",
|
|
425
437
|
"$value": "#ff885d",
|
package/dist/dtcg/light.json
CHANGED
|
@@ -420,6 +420,18 @@
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
},
|
|
423
|
+
"control": {
|
|
424
|
+
"$type": "color",
|
|
425
|
+
"$value": "#222f3c8c",
|
|
426
|
+
"$description": "ref(fgPrimary) → alpha(0.55)",
|
|
427
|
+
"$extensions": {
|
|
428
|
+
"psi": {
|
|
429
|
+
"scopes": [
|
|
430
|
+
"border"
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
},
|
|
423
435
|
"focus": {
|
|
424
436
|
"$type": "color",
|
|
425
437
|
"$value": "#0053c9",
|
package/dist/ember.css
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
--psi-border-faint: oklch(from var(--psi-fg-primary) l c h / 0.08);
|
|
41
41
|
--psi-border-neutral: oklch(from var(--psi-fg-primary) l c h / 0.15);
|
|
42
42
|
--psi-border-strong: oklch(from var(--psi-fg-primary) l c h / 0.3);
|
|
43
|
+
--psi-border-control: oklch(from var(--psi-fg-primary) l c h / 0.4);
|
|
43
44
|
--psi-border-focus: var(--psi-fg-accent);
|
|
44
45
|
--psi-scrim-soft: oklch(from var(--psi-bg-primary) l c h / 0.25);
|
|
45
46
|
--psi-scrim-medium: oklch(from var(--psi-bg-primary) l c h / 0.35);
|
package/dist/generate/derive.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkContrast, componentLabelPairs, wcagAAPairs } from "../contrast-matrix.js";
|
|
1
|
+
import { checkContrast, componentLabelPairs, nonTextPairs, wcagAAPairs } from "../contrast-matrix.js";
|
|
2
2
|
import { resolve } from "../dsl/resolver.js";
|
|
3
3
|
import { cap, set, slot, token } from "../dsl/builders.js";
|
|
4
4
|
import { assembleCustomerTheme, } from "../themes/customers/index.js";
|
|
@@ -25,7 +25,10 @@ function withAlpha(hex, alpha) {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* The gate, and it must be the SAME set `scripts/build.ts` runs over every
|
|
28
|
-
* committed theme: `[...wcagAAPairs, ...componentLabelPairs]`,
|
|
28
|
+
* committed theme: `[...wcagAAPairs, ...componentLabelPairs, ...nonTextPairs]`,
|
|
29
|
+
* 38 pairs (this said 33 until D106; D62 and D86 had grown it). The two
|
|
30
|
+
* non-text pairs (D106) are not solved here: `borderControl` is a fixed alpha
|
|
31
|
+
* of `fgPrimary`, and the sweep test proves it holds.
|
|
29
32
|
*
|
|
30
33
|
* Checking only `wcagAAPairs` (28) is what let "a theme derived from any
|
|
31
34
|
* prompt clears the AA matrix by construction" ship false a third time. The
|
|
@@ -40,7 +43,7 @@ function withAlpha(hex, alpha) {
|
|
|
40
43
|
* `accentLightness` there for why the fix has to be upstream of this loop
|
|
41
44
|
* rather than another escalating target inside it).
|
|
42
45
|
*/
|
|
43
|
-
const AA_MATRIX = [...wcagAAPairs, ...componentLabelPairs];
|
|
46
|
+
const AA_MATRIX = [...wcagAAPairs, ...componentLabelPairs, ...nonTextPairs];
|
|
44
47
|
const BASE_TARGET = 4.5;
|
|
45
48
|
const TARGET_ESCALATION = 0.1;
|
|
46
49
|
const MAX_OUTER_ATTEMPTS = 40;
|
package/dist/generate/palette.js
CHANGED
|
@@ -26,7 +26,7 @@ const ACCENT_L = 0.55;
|
|
|
26
26
|
* renders white-on-accent at **4.4979:1** — under AA, and the exact failure
|
|
27
27
|
* the prompt "bold calm app" produced. It cleared CI only because `derive.ts`
|
|
28
28
|
* checked the 28-pair `wcagAAPairs` while `scripts/build.ts` gates committed
|
|
29
|
-
* themes on
|
|
29
|
+
* themes on the full matrix.
|
|
30
30
|
*
|
|
31
31
|
* So the accent is darkened — never lightened — to the lightest value that
|
|
32
32
|
* still carries a white label at `LABEL_TARGET`, a deliberate margin above
|
|
@@ -45,7 +45,7 @@ const ACCENT_L = 0.55;
|
|
|
45
45
|
* `fillDanger`, `fgStaticBlack` on `fillWarning`) sit on fixed hues and were
|
|
46
46
|
* measured at 5.16 / 5.21 / 6.94:1 worst-case across the same sweep, so they
|
|
47
47
|
* need no equivalent treatment — but the sweep in
|
|
48
|
-
* `__tests__/generate-derive.test.ts` gates
|
|
48
|
+
* `__tests__/generate-derive.test.ts` gates the full matrix, not just this one.
|
|
49
49
|
*/
|
|
50
50
|
const LABEL_TARGET = 4.65;
|
|
51
51
|
function accentLightness(c, h) {
|
package/dist/light.css
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
--psi-border-faint: oklch(from var(--psi-fg-primary) l c h / 0.08);
|
|
41
41
|
--psi-border-neutral: oklch(from var(--psi-fg-primary) l c h / 0.15);
|
|
42
42
|
--psi-border-strong: oklch(from var(--psi-fg-primary) l c h / 0.3);
|
|
43
|
+
--psi-border-control: oklch(from var(--psi-fg-primary) l c h / 0.55);
|
|
43
44
|
--psi-border-focus: var(--psi-fg-accent);
|
|
44
45
|
--psi-scrim-soft: oklch(from var(--psi-bg-primary) l c h / 0.25);
|
|
45
46
|
--psi-scrim-medium: oklch(from var(--psi-bg-primary) l c h / 0.35);
|
package/dist/resolved/acme.json
CHANGED
|
@@ -431,6 +431,20 @@
|
|
|
431
431
|
"border"
|
|
432
432
|
]
|
|
433
433
|
},
|
|
434
|
+
"borderControl": {
|
|
435
|
+
"name": "borderControl",
|
|
436
|
+
"oklch": {
|
|
437
|
+
"l": 0.3,
|
|
438
|
+
"c": 0.03,
|
|
439
|
+
"h": 30,
|
|
440
|
+
"alpha": 0.55
|
|
441
|
+
},
|
|
442
|
+
"hex": "#3c2825",
|
|
443
|
+
"formula": "ref(fgPrimary) → alpha(0.55)",
|
|
444
|
+
"scopes": [
|
|
445
|
+
"border"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
434
448
|
"borderFocus": {
|
|
435
449
|
"name": "borderFocus",
|
|
436
450
|
"oklch": {
|
package/dist/resolved/dark.json
CHANGED
|
@@ -431,6 +431,20 @@
|
|
|
431
431
|
"border"
|
|
432
432
|
]
|
|
433
433
|
},
|
|
434
|
+
"borderControl": {
|
|
435
|
+
"name": "borderControl",
|
|
436
|
+
"oklch": {
|
|
437
|
+
"l": 0.93,
|
|
438
|
+
"c": 0.01,
|
|
439
|
+
"h": 250,
|
|
440
|
+
"alpha": 0.4
|
|
441
|
+
},
|
|
442
|
+
"hex": "#e3e8ee",
|
|
443
|
+
"formula": "ref(fgPrimary) → alpha(0.4)",
|
|
444
|
+
"scopes": [
|
|
445
|
+
"border"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
434
448
|
"borderFocus": {
|
|
435
449
|
"name": "borderFocus",
|
|
436
450
|
"oklch": {
|
package/dist/resolved/ember.json
CHANGED
|
@@ -431,6 +431,20 @@
|
|
|
431
431
|
"border"
|
|
432
432
|
]
|
|
433
433
|
},
|
|
434
|
+
"borderControl": {
|
|
435
|
+
"name": "borderControl",
|
|
436
|
+
"oklch": {
|
|
437
|
+
"l": 0.949,
|
|
438
|
+
"c": 0.011,
|
|
439
|
+
"h": 72,
|
|
440
|
+
"alpha": 0.4
|
|
441
|
+
},
|
|
442
|
+
"hex": "#f3ede6",
|
|
443
|
+
"formula": "ref(fgPrimary) → alpha(0.4)",
|
|
444
|
+
"scopes": [
|
|
445
|
+
"border"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
434
448
|
"borderFocus": {
|
|
435
449
|
"name": "borderFocus",
|
|
436
450
|
"oklch": {
|
package/dist/resolved/light.json
CHANGED
|
@@ -431,6 +431,20 @@
|
|
|
431
431
|
"border"
|
|
432
432
|
]
|
|
433
433
|
},
|
|
434
|
+
"borderControl": {
|
|
435
|
+
"name": "borderControl",
|
|
436
|
+
"oklch": {
|
|
437
|
+
"l": 0.3,
|
|
438
|
+
"c": 0.03,
|
|
439
|
+
"h": 250,
|
|
440
|
+
"alpha": 0.55
|
|
441
|
+
},
|
|
442
|
+
"hex": "#222f3c",
|
|
443
|
+
"formula": "ref(fgPrimary) → alpha(0.55)",
|
|
444
|
+
"scopes": [
|
|
445
|
+
"border"
|
|
446
|
+
]
|
|
447
|
+
},
|
|
434
448
|
"borderFocus": {
|
|
435
449
|
"name": "borderFocus",
|
|
436
450
|
"oklch": {
|
package/dist/scope-map.json
CHANGED
|
@@ -173,6 +173,9 @@
|
|
|
173
173
|
"border-strong": [
|
|
174
174
|
"border"
|
|
175
175
|
],
|
|
176
|
+
"border-control": [
|
|
177
|
+
"border"
|
|
178
|
+
],
|
|
176
179
|
"border-focus": [
|
|
177
180
|
"border"
|
|
178
181
|
],
|
|
@@ -349,6 +352,9 @@
|
|
|
349
352
|
"checkbox-box-border-checked": [
|
|
350
353
|
"border"
|
|
351
354
|
],
|
|
355
|
+
"checkbox-box-border-error": [
|
|
356
|
+
"border"
|
|
357
|
+
],
|
|
352
358
|
"checkbox-check-fg": [
|
|
353
359
|
"text"
|
|
354
360
|
],
|
|
@@ -484,6 +490,27 @@
|
|
|
484
490
|
"panel-border": [
|
|
485
491
|
"border"
|
|
486
492
|
],
|
|
493
|
+
"radio-group-fg": [
|
|
494
|
+
"text"
|
|
495
|
+
],
|
|
496
|
+
"radio-group-circle-bg": [
|
|
497
|
+
"surface"
|
|
498
|
+
],
|
|
499
|
+
"radio-group-circle-border": [
|
|
500
|
+
"border"
|
|
501
|
+
],
|
|
502
|
+
"radio-group-circle-bg-checked": [
|
|
503
|
+
"surface"
|
|
504
|
+
],
|
|
505
|
+
"radio-group-circle-border-checked": [
|
|
506
|
+
"border"
|
|
507
|
+
],
|
|
508
|
+
"radio-group-circle-border-error": [
|
|
509
|
+
"border"
|
|
510
|
+
],
|
|
511
|
+
"radio-group-dot-fg": [
|
|
512
|
+
"text"
|
|
513
|
+
],
|
|
487
514
|
"select-bg": [
|
|
488
515
|
"surface"
|
|
489
516
|
],
|
|
@@ -655,6 +682,30 @@
|
|
|
655
682
|
"tag-danger-subtle-fg": [
|
|
656
683
|
"text"
|
|
657
684
|
],
|
|
685
|
+
"textarea-bg": [
|
|
686
|
+
"surface"
|
|
687
|
+
],
|
|
688
|
+
"textarea-fg": [
|
|
689
|
+
"text"
|
|
690
|
+
],
|
|
691
|
+
"textarea-border": [
|
|
692
|
+
"border"
|
|
693
|
+
],
|
|
694
|
+
"textarea-border-hover": [
|
|
695
|
+
"border"
|
|
696
|
+
],
|
|
697
|
+
"textarea-border-error": [
|
|
698
|
+
"border"
|
|
699
|
+
],
|
|
700
|
+
"textarea-counter-fg": [
|
|
701
|
+
"text"
|
|
702
|
+
],
|
|
703
|
+
"text-fg": [
|
|
704
|
+
"text"
|
|
705
|
+
],
|
|
706
|
+
"text-fg-secondary": [
|
|
707
|
+
"text"
|
|
708
|
+
],
|
|
658
709
|
"toast-bg": [
|
|
659
710
|
"surface"
|
|
660
711
|
],
|
package/dist/theme/gate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { validate } from "../dsl/validator.js";
|
|
2
2
|
import { resolve } from "../dsl/resolver.js";
|
|
3
3
|
import { gamutWarnings } from "../gamut.js";
|
|
4
|
-
import { checkContrast, wcagAAPairs, componentLabelPairs } from "../contrast-matrix.js";
|
|
4
|
+
import { checkContrast, wcagAAPairs, componentLabelPairs, nonTextPairs } from "../contrast-matrix.js";
|
|
5
5
|
import { checkScopes, checkOverrideScopes } from "../scope-gate.js";
|
|
6
6
|
import { componentVars } from "../components/registry.js";
|
|
7
7
|
/** Thrown when a theme fails the WCAG AA contrast gate or the D46 scope gate. */
|
|
@@ -32,7 +32,7 @@ export function gateTheme(name, theme, palette, slots, componentOverrides = {})
|
|
|
32
32
|
validate(theme, slots);
|
|
33
33
|
const resolved = resolve(theme, palette, slots);
|
|
34
34
|
const warnings = gamutWarnings(resolved, theme, palette, slots);
|
|
35
|
-
const contrastFailures = checkContrast(resolved, [...wcagAAPairs, ...componentLabelPairs]).filter((r) => !r.pass);
|
|
35
|
+
const contrastFailures = checkContrast(resolved, [...wcagAAPairs, ...componentLabelPairs, ...nonTextPairs]).filter((r) => !r.pass);
|
|
36
36
|
const scopeViolations = [
|
|
37
37
|
...checkScopes(componentVars, theme),
|
|
38
38
|
...checkOverrideScopes(componentOverrides, componentVars, theme),
|
package/dist/themes/dark.js
CHANGED
|
@@ -41,6 +41,10 @@ export const darkTheme = {
|
|
|
41
41
|
borderFaint: token({ from: ref.fgPrimary, alpha: 0.08, scopes: ["border"] }),
|
|
42
42
|
borderNeutral: token({ from: ref.fgPrimary, alpha: 0.15, scopes: ["border"] }),
|
|
43
43
|
borderStrong: token({ from: ref.fgPrimary, alpha: 0.3, scopes: ["border"] }),
|
|
44
|
+
/** D106: the outline that alone identifies an unchecked checkbox or radio,
|
|
45
|
+
* so WCAG 1.4.11 asks 3:1 for it (`nonTextPairs`). borderNeutral measured
|
|
46
|
+
* 1.32-1.48. Alpha chosen by sweep over every derivable theme. */
|
|
47
|
+
borderControl: token({ from: ref.fgPrimary, alpha: 0.4, scopes: ["border"] }),
|
|
44
48
|
borderFocus: token({ from: ref.fgAccent, scopes: ["border"] }),
|
|
45
49
|
// Scrims (D32): page-canvas surfaces at alpha — never text pairs, so they
|
|
46
50
|
// carry no AA gate entries.
|
package/dist/themes/light.js
CHANGED
|
@@ -44,6 +44,10 @@ export const lightTheme = {
|
|
|
44
44
|
borderFaint: token({ from: ref.fgPrimary, alpha: 0.08, scopes: ["border"] }),
|
|
45
45
|
borderNeutral: token({ from: ref.fgPrimary, alpha: 0.15, scopes: ["border"] }),
|
|
46
46
|
borderStrong: token({ from: ref.fgPrimary, alpha: 0.3, scopes: ["border"] }),
|
|
47
|
+
/** D106: the outline that alone identifies an unchecked checkbox or radio,
|
|
48
|
+
* so WCAG 1.4.11 asks 3:1 for it (`nonTextPairs`). borderNeutral measured
|
|
49
|
+
* 1.32-1.48. Alpha chosen by sweep over every derivable theme. */
|
|
50
|
+
borderControl: token({ from: ref.fgPrimary, alpha: 0.55, scopes: ["border"] }),
|
|
47
51
|
borderFocus: token({ from: ref.fgAccent, scopes: ["border"] }),
|
|
48
52
|
// Scrims (D32): page-canvas surfaces at alpha — never text pairs, so they
|
|
49
53
|
// carry no AA gate entries.
|
package/dist/types/index.d.ts
CHANGED
package/llms.txt
CHANGED
|
@@ -39,6 +39,7 @@ OKLCH formula-based design tokens. Code is the source of truth; all artifacts ge
|
|
|
39
39
|
## Scrims and borders
|
|
40
40
|
- --psi-scrim-soft/medium/heavy: bgPrimary-derived translucent overlays (0.25/0.35/0.82 alpha) for text-over-media legibility.
|
|
41
41
|
- --psi-border-faint: low-contrast hairline border (fgPrimary at 0.08 alpha), used by Card/NavBar and other bordered surfaces.
|
|
42
|
+
- --psi-border-control: the outline that alone identifies an unchecked Checkbox or Radio (fgPrimary at 0.55 alpha in light-based themes, 0.40 in dark-based), gated at 3:1 on bgPrimary and bgSecondary by `nonTextPairs` (WCAG 1.4.11, D106). Bind it, not --psi-border-neutral (1.32-1.48:1), for any control identified by its outline alone. A consumer theme built with buildThemeCss is held to the same 3:1.
|
|
42
43
|
|
|
43
44
|
## Media tint (--psi-media-tint, .psi-media-tint)
|
|
44
45
|
- --psi-media-tint is a brand-defined filter chain (D35); apply the .psi-media-tint utility class to media elements to get a tinted-at-rest, true-color-on-hover/focus treatment, transitioning over --psi-duration-450 / --psi-ease-soft.
|