@handamade/psi-tokens 0.26.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.js +3 -1
- package/dist/components/radio-group.vars.css +2 -1
- package/dist/components/registry.js +2 -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.css +11 -2
- 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 +15 -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);
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
export const radioGroupVars = {
|
|
6
6
|
fg: "var(--psi-fg-primary)",
|
|
7
7
|
"circle-bg": "var(--psi-bg-primary)",
|
|
8
|
-
"circle-border": "var(--psi-border-
|
|
8
|
+
"circle-border": "var(--psi-border-control)",
|
|
9
9
|
"circle-bg-checked": "var(--psi-fill-accent)",
|
|
10
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)",
|
|
11
13
|
"dot-fg": "var(--psi-fg-on-accent)",
|
|
12
14
|
"focus-ring": "var(--psi-border-focus)",
|
|
13
15
|
};
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
:where(:root, [data-psi-theme]) {
|
|
3
3
|
--psi-radio-group-fg: var(--psi-fg-primary);
|
|
4
4
|
--psi-radio-group-circle-bg: var(--psi-bg-primary);
|
|
5
|
-
--psi-radio-group-circle-border: var(--psi-border-
|
|
5
|
+
--psi-radio-group-circle-border: var(--psi-border-control);
|
|
6
6
|
--psi-radio-group-circle-bg-checked: var(--psi-fill-accent);
|
|
7
7
|
--psi-radio-group-circle-border-checked: var(--psi-fill-accent);
|
|
8
|
+
--psi-radio-group-circle-border-error: var(--psi-fg-danger);
|
|
8
9
|
--psi-radio-group-dot-fg: var(--psi-fg-on-accent);
|
|
9
10
|
--psi-radio-group-focus-ring: var(--psi-border-focus);
|
|
10
11
|
}
|
|
@@ -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";
|
|
@@ -57,6 +58,7 @@ export const componentVars = {
|
|
|
57
58
|
tag: tagVars,
|
|
58
59
|
textarea: textareaVars,
|
|
59
60
|
toolbar: toolbarVars,
|
|
61
|
+
text: textVars,
|
|
60
62
|
toast: toastVars,
|
|
61
63
|
tooltip: tooltipVars,
|
|
62
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
|
+
};
|
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);
|
|
@@ -261,9 +262,10 @@
|
|
|
261
262
|
:where(:root, [data-psi-theme]) {
|
|
262
263
|
--psi-radio-group-fg: var(--psi-fg-primary);
|
|
263
264
|
--psi-radio-group-circle-bg: var(--psi-bg-primary);
|
|
264
|
-
--psi-radio-group-circle-border: var(--psi-border-
|
|
265
|
+
--psi-radio-group-circle-border: var(--psi-border-control);
|
|
265
266
|
--psi-radio-group-circle-bg-checked: var(--psi-fill-accent);
|
|
266
267
|
--psi-radio-group-circle-border-checked: var(--psi-fill-accent);
|
|
268
|
+
--psi-radio-group-circle-border-error: var(--psi-fg-danger);
|
|
267
269
|
--psi-radio-group-dot-fg: var(--psi-fg-on-accent);
|
|
268
270
|
--psi-radio-group-focus-ring: var(--psi-border-focus);
|
|
269
271
|
}
|
|
@@ -446,6 +448,13 @@
|
|
|
446
448
|
}
|
|
447
449
|
}
|
|
448
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
|
+
|
|
449
458
|
@layer psi.components {
|
|
450
459
|
:where(:root, [data-psi-theme]) {
|
|
451
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
|
],
|
|
@@ -499,6 +505,9 @@
|
|
|
499
505
|
"radio-group-circle-border-checked": [
|
|
500
506
|
"border"
|
|
501
507
|
],
|
|
508
|
+
"radio-group-circle-border-error": [
|
|
509
|
+
"border"
|
|
510
|
+
],
|
|
502
511
|
"radio-group-dot-fg": [
|
|
503
512
|
"text"
|
|
504
513
|
],
|
|
@@ -691,6 +700,12 @@
|
|
|
691
700
|
"textarea-counter-fg": [
|
|
692
701
|
"text"
|
|
693
702
|
],
|
|
703
|
+
"text-fg": [
|
|
704
|
+
"text"
|
|
705
|
+
],
|
|
706
|
+
"text-fg-secondary": [
|
|
707
|
+
"text"
|
|
708
|
+
],
|
|
694
709
|
"toast-bg": [
|
|
695
710
|
"surface"
|
|
696
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.
|