@askrjs/themes 0.0.28 → 0.0.29

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/THEMING.md CHANGED
@@ -996,6 +996,32 @@ All official themes are tested for WCAG AA compliance:
996
996
 
997
997
  The automated test suite (`tests/unit/contrast.test.ts`) validates these pairs across all themes in both light and dark modes.
998
998
 
999
+ The default light and dark modes additionally publish this canonical semantic
1000
+ pairing matrix. Ratios apply to browser-resolved colors after alpha and element
1001
+ opacity are composited:
1002
+
1003
+ | Semantic pairing | Minimum |
1004
+ | --------------------------------------------------------------------------------------- | ------: |
1005
+ | Ordinary or subtle text on its documented surface | 4.5:1 |
1006
+ | Strong boundaries, warning UI, and info UI on a surface | 3:1 |
1007
+ | The shared focus indicator on page, surface, muted, raised, overlay, and primary layers | 3:1 |
1008
+ | Disabled control fill against the page | 1.5:1 |
1009
+ | Disabled control boundary against the page when the fill is below 1.5:1 | 3:1 |
1010
+
1011
+ ### Forced Colors
1012
+
1013
+ The default stylesheet includes a `forced-colors: active` layer. It uses
1014
+ system `Canvas`, `CanvasText`, `ButtonText`, `GrayText`, `Highlight`, and
1015
+ `HighlightText` colors for control boundaries, disabled states, overlay
1016
+ separation, and focus. Native system-color adjustment remains enabled; do not
1017
+ add `forced-color-adjust: none` unless a specific semantic image would
1018
+ otherwise lose meaning and the exception is documented and tested.
1019
+
1020
+ Normal browser CI resolves every canonical pairing in light and dark mode.
1021
+ Chromium CI also runs a real forced-colors context, while the stylesheet and
1022
+ template parity test protects the same contract for engines that cannot
1023
+ emulate it.
1024
+
999
1025
  ---
1000
1026
 
1001
1027
  ## Implementation Note
@@ -163,16 +163,16 @@
163
163
  --ak-dark-color-primary-ink: oklch(98.5% 0 0);
164
164
  --ak-dark-color-text: oklch(98.5% 0 0);
165
165
  --ak-dark-color-text-muted: oklch(70.8% 0 0);
166
- --ak-dark-color-text-subtle: oklch(55.6% 0 0);
166
+ --ak-dark-color-text-subtle: oklch(74% 0 0);
167
167
  --ak-dark-color-text-inverse: oklch(20.5% 0 0);
168
168
  --ak-dark-color-bg: oklch(14.5% 0 0);
169
169
  --ak-dark-color-surface: oklch(20.5% 0 0);
170
170
  --ak-dark-color-surface-muted: oklch(26.9% 0 0);
171
- --ak-dark-color-surface-raised: oklch(20.5% 0 0);
172
- --ak-dark-color-surface-overlay: oklch(20.5% 0 0);
171
+ --ak-dark-color-surface-raised: oklch(23.5% 0 0);
172
+ --ak-dark-color-surface-overlay: oklch(26.9% 0 0);
173
173
  --ak-dark-color-border-subtle: oklch(26.9% 0 0);
174
174
  --ak-dark-color-border: oklch(100% 0 0 / .1);
175
- --ak-dark-color-border-strong: oklch(43.9% 0 0);
175
+ --ak-dark-color-border-strong: oklch(54% 0 0);
176
176
  --ak-dark-color-success: #4ade80;
177
177
  --ak-dark-color-success-soft: #052e16;
178
178
  --ak-dark-color-success-ink: #bbf7d0;
@@ -187,11 +187,11 @@
187
187
  --ak-dark-color-info-ink: #bae6fd;
188
188
  --ak-dark-color-link: var(--ak-dark-color-text);
189
189
  --ak-dark-color-link-hover: color-mix(in oklch, var(--ak-dark-color-link) 88%, transparent);
190
- --ak-dark-color-focus-ring: oklch(55.6% 0 0 / .5);
191
- --ak-dark-color-disabled-bg: oklch(26.9% 0 0);
190
+ --ak-dark-color-focus-ring: oklch(60% 0 0);
191
+ --ak-dark-color-disabled-bg: oklch(32% 0 0);
192
192
  --ak-dark-color-disabled-surface: oklch(20.5% 0 0);
193
- --ak-dark-color-disabled-border: oklch(100% 0 0 / .1);
194
- --ak-dark-color-disabled-text: oklch(70.8% 0 0);
193
+ --ak-dark-color-disabled-border: oklch(54% 0 0);
194
+ --ak-dark-color-disabled-text: oklch(74% 0 0);
195
195
  --ak-dark-color-selected: oklch(26.9% 0 0);
196
196
  --ak-dark-color-selected-border: oklch(100% 0 0 / .1);
197
197
  --ak-dark-color-hover: oklch(26.9% 0 0);
@@ -211,35 +211,35 @@
211
211
  --ak-color-primary-ink: oklch(20.5% 0 0);
212
212
  --ak-color-text: oklch(14.5% 0 0);
213
213
  --ak-color-text-muted: oklch(55.6% 0 0);
214
- --ak-color-text-subtle: oklch(70.8% 0 0);
214
+ --ak-color-text-subtle: oklch(54% 0 0);
215
215
  --ak-color-text-inverse: oklch(98.5% 0 0);
216
216
  --ak-color-bg: oklch(100% 0 0);
217
- --ak-color-surface: oklch(100% 0 0);
218
- --ak-color-surface-muted: oklch(97% 0 0);
219
- --ak-color-surface-raised: oklch(100% 0 0);
220
- --ak-color-surface-overlay: oklch(100% 0 0);
217
+ --ak-color-surface: oklch(98.5% 0 0);
218
+ --ak-color-surface-muted: oklch(95% 0 0);
219
+ --ak-color-surface-raised: oklch(97% 0 0);
220
+ --ak-color-surface-overlay: oklch(94% 0 0);
221
221
  --ak-color-border-subtle: oklch(92.2% 0 0);
222
222
  --ak-color-border: oklch(92.2% 0 0);
223
- --ak-color-border-strong: oklch(70.8% 0 0);
223
+ --ak-color-border-strong: oklch(62% 0 0);
224
224
  --ak-color-success: #16a34a;
225
225
  --ak-color-success-soft: #16a34a24;
226
226
  --ak-color-success-ink: #166534;
227
- --ak-color-warning: #f59e0b;
227
+ --ak-color-warning: #b45309;
228
228
  --ak-color-warning-soft: #f59e0b29;
229
229
  --ak-color-warning-ink: #854d0e;
230
230
  --ak-color-danger: oklch(57.7% .245 27.325);
231
231
  --ak-color-danger-soft: oklch(57.7% .245 27.325 / .2);
232
232
  --ak-color-danger-ink: #991b1b;
233
- --ak-color-info: #0ea5e9;
233
+ --ak-color-info: #0369a1;
234
234
  --ak-color-info-soft: #0ea5e929;
235
235
  --ak-color-info-ink: #0c4a6e;
236
236
  --ak-color-link: var(--ak-color-primary);
237
237
  --ak-color-link-hover: color-mix(in oklch, var(--ak-color-link) 88%, transparent);
238
- --ak-color-focus-ring: oklch(70.8% 0 0 / .5);
239
- --ak-color-disabled-bg: oklch(97% 0 0);
238
+ --ak-color-focus-ring: oklch(58% 0 0);
239
+ --ak-color-disabled-bg: oklch(90% 0 0);
240
240
  --ak-color-disabled-surface: oklch(100% 0 0);
241
- --ak-color-disabled-border: oklch(92.2% 0 0);
242
- --ak-color-disabled-text: oklch(55.6% 0 0);
241
+ --ak-color-disabled-border: oklch(62% 0 0);
242
+ --ak-color-disabled-text: oklch(50% 0 0);
243
243
  --ak-color-selected: oklch(97% 0 0);
244
244
  --ak-color-selected-border: oklch(92.2% 0 0);
245
245
  --ak-color-hover: oklch(97% 0 0);
@@ -1857,7 +1857,7 @@
1857
1857
  }
1858
1858
 
1859
1859
  :where([data-slot="theme-toggle-content"]) {
1860
- --_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-icon-size, 1em));
1860
+ --_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-font-size-sm));
1861
1861
  flex: none;
1862
1862
  justify-content: center;
1863
1863
  align-items: center;
@@ -2105,6 +2105,12 @@
2105
2105
  padding-inline: 0;
2106
2106
  }
2107
2107
 
2108
+ :where(.btn, .btn-close, [data-slot="button"]):focus-visible {
2109
+ border-color: var(--ak-color-ring);
2110
+ box-shadow: 0 0 0 var(--ak-focus-ring-width) var(--ak-color-focus-ring);
2111
+ outline: none;
2112
+ }
2113
+
2108
2114
  :where(.btn-group, [data-slot="button-group"]) {
2109
2115
  align-items: stretch;
2110
2116
  gap: 0;
@@ -2410,11 +2416,12 @@
2410
2416
 
2411
2417
  :where(.input[data-disabled], .input:disabled, [data-slot="input"][data-disabled]) {
2412
2418
  background: var(--ak-color-disabled-bg);
2413
- border-color: var(--ak-color-input);
2419
+ border-color: var(--ak-color-disabled-border);
2420
+ color: var(--ak-color-disabled-text);
2414
2421
  box-shadow: none;
2415
2422
  pointer-events: none;
2416
2423
  cursor: not-allowed;
2417
- opacity: .5;
2424
+ opacity: 1;
2418
2425
  }
2419
2426
 
2420
2427
  :where(.input-sm, [data-slot="input"][data-size="sm"]) {
@@ -4805,15 +4812,16 @@
4805
4812
  color: var(--ak-color-text);
4806
4813
  }
4807
4814
 
4808
- :where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg), :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible > :where([data-slot="icon"], svg) {
4815
+ :where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg), :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) > :where([data-slot="icon"], svg) {
4809
4816
  color: currentColor;
4810
4817
  }
4811
4818
 
4812
- :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible {
4819
+ :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) {
4820
+ outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
4821
+ outline-offset: calc(-1 * var(--ak-focus-ring-width));
4813
4822
  background: var(--ak-color-hover);
4814
4823
  color: var(--ak-color-text);
4815
4824
  box-shadow: none;
4816
- outline: none;
4817
4825
  }
4818
4826
 
4819
4827
  :where(.dropdown-item, [data-slot="dropdown-item"])[data-disabled] {
@@ -5036,15 +5044,16 @@
5036
5044
  color: var(--ak-color-text);
5037
5045
  }
5038
5046
 
5039
- :where([data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-item"]:focus-visible > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:focus-visible > :where([data-slot="icon"], svg)) {
5047
+ :where([data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-item"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg), [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg)) {
5040
5048
  color: currentColor;
5041
5049
  }
5042
5050
 
5043
- :where([data-slot="menubar-item"]:focus-visible, [data-slot="menubar-sub-trigger"]:focus-visible) {
5051
+ :where([data-slot="menubar-item"]:is(:focus, :focus-visible), [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)) {
5052
+ outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
5053
+ outline-offset: calc(-1 * var(--ak-focus-ring-width));
5044
5054
  background: var(--ak-color-hover);
5045
5055
  color: var(--ak-color-text);
5046
5056
  box-shadow: none;
5047
- outline: none;
5048
5057
  }
5049
5058
 
5050
5059
  :where([data-slot="menubar-item"][data-disabled], [data-slot="menubar-sub-trigger"][data-disabled]) {
@@ -5365,7 +5374,7 @@
5365
5374
  }
5366
5375
 
5367
5376
  :where([data-slot="dialog-content"], [data-slot="sheet-content"]) {
5368
- --_dialog-surface: var(--ak-color-bg);
5377
+ --_dialog-surface: var(--ak-color-surface-raised);
5369
5378
  --_dialog-border: var(--ak-color-border);
5370
5379
  gap: var(--ak-space-md);
5371
5380
  box-sizing: border-box;
@@ -5935,3 +5944,49 @@
5935
5944
  animation: none;
5936
5945
  }
5937
5946
  }
5947
+
5948
+ :where([data-slot="button"][data-disabled], [data-slot="input"][data-disabled], [data-slot="textarea"][data-disabled], [data-slot="select-trigger"][data-disabled], [data-slot="checkbox"][data-disabled], [data-slot="radio-group-item"][data-disabled], [data-slot="switch"][data-disabled]) {
5949
+ background: var(--ak-color-disabled-bg);
5950
+ border-color: var(--ak-color-disabled-border);
5951
+ color: var(--ak-color-disabled-text);
5952
+ box-shadow: none;
5953
+ opacity: 1;
5954
+ }
5955
+
5956
+ @media (forced-colors: active) {
5957
+ :where([data-slot="button"], [data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"], [data-slot="checkbox"], [data-slot="radio-group-item"], [data-slot="switch"], [data-slot="dropdown-trigger"], [tabindex]:not([tabindex="-1"]), [role="menuitem"]):focus-visible {
5958
+ outline-offset: 2px;
5959
+ box-shadow: none;
5960
+ outline: 2px solid highlight;
5961
+ }
5962
+
5963
+ :where([data-slot="button"], [data-slot="input"], [data-slot="textarea"], [data-slot="select-trigger"], [data-slot="checkbox"], [data-slot="radio-group-item"], [data-slot="switch"], [data-slot="dropdown-content"], [data-slot="menubar-content"]) {
5964
+ border-color: buttontext;
5965
+ }
5966
+
5967
+ :where([data-disabled]) {
5968
+ color: graytext;
5969
+ opacity: 1;
5970
+ border-color: graytext;
5971
+ }
5972
+
5973
+ :where([data-slot="dialog-content"], [data-slot="sheet-content"], [data-slot="popover-content"], [data-slot="dropdown-content"], [data-slot="menubar-content"]) {
5974
+ color: canvastext;
5975
+ box-shadow: none;
5976
+ background: canvas;
5977
+ border: 2px solid canvastext;
5978
+ }
5979
+
5980
+ :where([data-slot="dropdown-item"]:is(:focus, :focus-visible), [data-slot="menubar-item"]:is(:focus, :focus-visible), [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)) {
5981
+ outline-offset: -2px;
5982
+ color: highlighttext;
5983
+ background: highlight;
5984
+ outline: 2px solid highlight;
5985
+ }
5986
+
5987
+ :where([data-slot="dialog-overlay"]) {
5988
+ opacity: .75;
5989
+ backdrop-filter: none;
5990
+ background: canvas;
5991
+ }
5992
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/themes",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Default theme tokens, styles, and component presets for Askr apps.",
5
5
  "keywords": [
6
6
  "askr",
@@ -101,10 +101,11 @@
101
101
  "bench:tier2": "cross-env NODE_ENV=production vp test bench --run --reporter=default --config vitest.bench.tier2.config.ts",
102
102
  "bench:tier3": "cross-env NODE_ENV=production vp test bench --run --reporter=default --config vitest.bench.tier3.config.ts",
103
103
  "bench:tier4": "cross-env NODE_ENV=production vp test bench --run --reporter=default --config vitest.bench.tier4.config.ts --mode production",
104
- "test": "npm run test:unit && npm run test:checks && npm run test:jsdom && npm run test:browser",
104
+ "test": "npm run test:unit && npm run test:checks && npm run test:jsdom && npm run test:browser && npm run test:forced-colors",
105
105
  "test:unit": "vp test run -c vitest.test.unit.config.ts",
106
106
  "test:jsdom": "cross-env NODE_ENV=production vp test run -c vitest.test.jsdom.config.ts",
107
107
  "test:browser": "vp test run -c vitest.test.browser.config.ts --mode production",
108
+ "test:forced-colors": "vp test run -c vitest.test.forced-colors.config.ts --mode production",
108
109
  "test:publint": "publint",
109
110
  "test:installed": "node tests/installed-types.js",
110
111
  "fmt": "vp fmt .",
@@ -119,8 +120,8 @@
119
120
  "test:checks": "vp test run -c vitest.test.checks.config.ts"
120
121
  },
121
122
  "devDependencies": {
122
- "@askrjs/askr": ">=0.0.88 <0.1.0",
123
- "@askrjs/ui": ">=0.0.26 <0.1.0",
123
+ "@askrjs/askr": ">=0.0.92 <0.1.0",
124
+ "@askrjs/ui": ">=0.0.30 <0.1.0",
124
125
  "@askrjs/vite": ">=0.0.12 <0.1.0",
125
126
  "@tsdown/css": "0.22.14",
126
127
  "@types/node": "^26.2.0",
@@ -135,8 +136,8 @@
135
136
  "vite-plus": "^0.2.8"
136
137
  },
137
138
  "peerDependencies": {
138
- "@askrjs/askr": ">=0.0.88 <0.1.0",
139
- "@askrjs/ui": ">=0.0.26 <0.1.0"
139
+ "@askrjs/askr": ">=0.0.92 <0.1.0",
140
+ "@askrjs/ui": ">=0.0.30 <0.1.0"
140
141
  },
141
142
  "engines": {
142
143
  "node": ">=24.0.0"
@@ -53,3 +53,4 @@
53
53
  @import "./styles/overlays/popover.css";
54
54
  @import "./styles/overlays/hover-card.css";
55
55
  @import "./styles/overlays/tooltip.css";
56
+ @import "./styles/base/accessibility.css";
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  :where([data-slot="theme-toggle-content"]) {
40
- --_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-icon-size, 1em));
40
+ --_ak-theme-toggle-icon-size: var(--ak-theme-toggle-icon-size, var(--ak-font-size-sm));
41
41
  display: inline-flex;
42
42
  align-items: center;
43
43
  justify-content: center;
@@ -288,3 +288,10 @@
288
288
  min-inline-size: var(--_button-height);
289
289
  padding-inline: 0;
290
290
  }
291
+
292
+ /* Keep the focus contract last so variant box-shadow resets cannot hide it. */
293
+ :where(.btn, .btn-close, [data-slot="button"]):focus-visible {
294
+ outline: none;
295
+ border-color: var(--ak-color-ring);
296
+ box-shadow: 0 0 0 var(--ak-focus-ring-width) var(--ak-color-focus-ring);
297
+ }
@@ -0,0 +1,88 @@
1
+ /*
2
+ * Cross-component accessibility states live at the end of the default-theme
3
+ * cascade so new controls inherit the same perceivability contract.
4
+ */
5
+ :where(
6
+ [data-slot="button"][data-disabled],
7
+ [data-slot="input"][data-disabled],
8
+ [data-slot="textarea"][data-disabled],
9
+ [data-slot="select-trigger"][data-disabled],
10
+ [data-slot="checkbox"][data-disabled],
11
+ [data-slot="radio-group-item"][data-disabled],
12
+ [data-slot="switch"][data-disabled]
13
+ ) {
14
+ background: var(--ak-color-disabled-bg);
15
+ border-color: var(--ak-color-disabled-border);
16
+ color: var(--ak-color-disabled-text);
17
+ box-shadow: none;
18
+ opacity: 1;
19
+ }
20
+
21
+ @media (forced-colors: active) {
22
+ :where(
23
+ [data-slot="button"],
24
+ [data-slot="input"],
25
+ [data-slot="textarea"],
26
+ [data-slot="select-trigger"],
27
+ [data-slot="checkbox"],
28
+ [data-slot="radio-group-item"],
29
+ [data-slot="switch"],
30
+ [data-slot="dropdown-trigger"],
31
+ [tabindex]:not([tabindex="-1"]),
32
+ [role="menuitem"]
33
+ ):focus-visible {
34
+ outline: 2px solid Highlight;
35
+ outline-offset: 2px;
36
+ box-shadow: none;
37
+ }
38
+
39
+ :where(
40
+ [data-slot="button"],
41
+ [data-slot="input"],
42
+ [data-slot="textarea"],
43
+ [data-slot="select-trigger"],
44
+ [data-slot="checkbox"],
45
+ [data-slot="radio-group-item"],
46
+ [data-slot="switch"],
47
+ [data-slot="dropdown-content"],
48
+ [data-slot="menubar-content"]
49
+ ) {
50
+ border-color: ButtonText;
51
+ }
52
+
53
+ :where([data-disabled]) {
54
+ border-color: GrayText;
55
+ color: GrayText;
56
+ opacity: 1;
57
+ }
58
+
59
+ :where(
60
+ [data-slot="dialog-content"],
61
+ [data-slot="sheet-content"],
62
+ [data-slot="popover-content"],
63
+ [data-slot="dropdown-content"],
64
+ [data-slot="menubar-content"]
65
+ ) {
66
+ border: 2px solid CanvasText;
67
+ background: Canvas;
68
+ color: CanvasText;
69
+ box-shadow: none;
70
+ }
71
+
72
+ :where(
73
+ [data-slot="dropdown-item"]:is(:focus, :focus-visible),
74
+ [data-slot="menubar-item"]:is(:focus, :focus-visible),
75
+ [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
76
+ ) {
77
+ outline: 2px solid Highlight;
78
+ outline-offset: -2px;
79
+ background: Highlight;
80
+ color: HighlightText;
81
+ }
82
+
83
+ :where([data-slot="dialog-overlay"]) {
84
+ background: Canvas;
85
+ opacity: 0.75;
86
+ backdrop-filter: none;
87
+ }
88
+ }
@@ -44,11 +44,12 @@
44
44
 
45
45
  :where(.input[data-disabled], .input:disabled, [data-slot="input"][data-disabled]) {
46
46
  background: var(--ak-color-disabled-bg);
47
- border-color: var(--ak-color-input);
47
+ border-color: var(--ak-color-disabled-border);
48
+ color: var(--ak-color-disabled-text);
48
49
  box-shadow: none;
49
50
  pointer-events: none;
50
51
  cursor: not-allowed;
51
- opacity: 0.5;
52
+ opacity: 1;
52
53
  }
53
54
 
54
55
  :where(.input-sm, [data-slot="input"][data-size="sm"]) {
@@ -53,7 +53,7 @@
53
53
  }
54
54
 
55
55
  :where([data-slot="dialog-content"], [data-slot="sheet-content"]) {
56
- --_dialog-surface: var(--ak-color-bg);
56
+ --_dialog-surface: var(--ak-color-surface-raised);
57
57
  --_dialog-border: var(--ak-color-border);
58
58
 
59
59
  display: grid;
@@ -158,13 +158,14 @@
158
158
  }
159
159
 
160
160
  :where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg),
161
- :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible
161
+ :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible)
162
162
  > :where([data-slot="icon"], svg) {
163
163
  color: currentColor;
164
164
  }
165
165
 
166
- :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible {
167
- outline: none;
166
+ :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) {
167
+ outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
168
+ outline-offset: calc(-1 * var(--ak-focus-ring-width));
168
169
  background: var(--ak-color-hover);
169
170
  color: var(--ak-color-text);
170
171
  box-shadow: none;
@@ -165,15 +165,19 @@
165
165
 
166
166
  :where(
167
167
  [data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg),
168
- [data-slot="menubar-item"]:focus-visible > :where([data-slot="icon"], svg),
168
+ [data-slot="menubar-item"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg),
169
169
  [data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg),
170
- [data-slot="menubar-sub-trigger"]:focus-visible > :where([data-slot="icon"], svg)
170
+ [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg)
171
171
  ) {
172
172
  color: currentColor;
173
173
  }
174
174
 
175
- :where([data-slot="menubar-item"]:focus-visible, [data-slot="menubar-sub-trigger"]:focus-visible) {
176
- outline: none;
175
+ :where(
176
+ [data-slot="menubar-item"]:is(:focus, :focus-visible),
177
+ [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
178
+ ) {
179
+ outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
180
+ outline-offset: calc(-1 * var(--ak-focus-ring-width));
177
181
  background: var(--ak-color-hover);
178
182
  color: var(--ak-color-text);
179
183
  box-shadow: none;
@@ -178,18 +178,18 @@
178
178
 
179
179
  --ak-dark-color-text: oklch(0.985 0 0);
180
180
  --ak-dark-color-text-muted: oklch(0.708 0 0);
181
- --ak-dark-color-text-subtle: oklch(0.556 0 0);
181
+ --ak-dark-color-text-subtle: oklch(0.74 0 0);
182
182
  --ak-dark-color-text-inverse: oklch(0.205 0 0);
183
183
 
184
184
  --ak-dark-color-bg: oklch(0.145 0 0);
185
185
  --ak-dark-color-surface: oklch(0.205 0 0);
186
186
  --ak-dark-color-surface-muted: oklch(0.269 0 0);
187
- --ak-dark-color-surface-raised: oklch(0.205 0 0);
188
- --ak-dark-color-surface-overlay: oklch(0.205 0 0);
187
+ --ak-dark-color-surface-raised: oklch(0.235 0 0);
188
+ --ak-dark-color-surface-overlay: oklch(0.269 0 0);
189
189
 
190
190
  --ak-dark-color-border-subtle: oklch(0.269 0 0);
191
191
  --ak-dark-color-border: oklch(1 0 0 / 10%);
192
- --ak-dark-color-border-strong: oklch(0.439 0 0);
192
+ --ak-dark-color-border-strong: oklch(0.54 0 0);
193
193
 
194
194
  --ak-dark-color-success: #4ade80;
195
195
  --ak-dark-color-success-soft: #052e16;
@@ -206,11 +206,11 @@
206
206
 
207
207
  --ak-dark-color-link: var(--ak-dark-color-text);
208
208
  --ak-dark-color-link-hover: color-mix(in oklch, var(--ak-dark-color-link) 88%, transparent);
209
- --ak-dark-color-focus-ring: oklch(0.556 0 0 / 0.5);
210
- --ak-dark-color-disabled-bg: oklch(0.269 0 0);
209
+ --ak-dark-color-focus-ring: oklch(0.6 0 0);
210
+ --ak-dark-color-disabled-bg: oklch(0.32 0 0);
211
211
  --ak-dark-color-disabled-surface: oklch(0.205 0 0);
212
- --ak-dark-color-disabled-border: oklch(1 0 0 / 10%);
213
- --ak-dark-color-disabled-text: oklch(0.708 0 0);
212
+ --ak-dark-color-disabled-border: oklch(0.54 0 0);
213
+ --ak-dark-color-disabled-text: oklch(0.74 0 0);
214
214
  --ak-dark-color-selected: oklch(0.269 0 0);
215
215
  --ak-dark-color-selected-border: oklch(1 0 0 / 10%);
216
216
  --ak-dark-color-hover: oklch(0.269 0 0);
@@ -233,39 +233,39 @@
233
233
 
234
234
  --ak-color-text: oklch(0.145 0 0);
235
235
  --ak-color-text-muted: oklch(0.556 0 0);
236
- --ak-color-text-subtle: oklch(0.708 0 0);
236
+ --ak-color-text-subtle: oklch(0.54 0 0);
237
237
  --ak-color-text-inverse: oklch(0.985 0 0);
238
238
 
239
239
  --ak-color-bg: oklch(1 0 0);
240
- --ak-color-surface: oklch(1 0 0);
241
- --ak-color-surface-muted: oklch(0.97 0 0);
242
- --ak-color-surface-raised: oklch(1 0 0);
243
- --ak-color-surface-overlay: oklch(1 0 0);
240
+ --ak-color-surface: oklch(0.985 0 0);
241
+ --ak-color-surface-muted: oklch(0.95 0 0);
242
+ --ak-color-surface-raised: oklch(0.97 0 0);
243
+ --ak-color-surface-overlay: oklch(0.94 0 0);
244
244
 
245
245
  --ak-color-border-subtle: oklch(0.922 0 0);
246
246
  --ak-color-border: oklch(0.922 0 0);
247
- --ak-color-border-strong: oklch(0.708 0 0);
247
+ --ak-color-border-strong: oklch(0.62 0 0);
248
248
 
249
249
  --ak-color-success: #16a34a;
250
250
  --ak-color-success-soft: rgba(22, 163, 74, 0.14);
251
251
  --ak-color-success-ink: #166534;
252
- --ak-color-warning: #f59e0b;
252
+ --ak-color-warning: #b45309;
253
253
  --ak-color-warning-soft: rgba(245, 158, 11, 0.16);
254
254
  --ak-color-warning-ink: #854d0e;
255
255
  --ak-color-danger: oklch(0.577 0.245 27.325);
256
256
  --ak-color-danger-soft: oklch(0.577 0.245 27.325 / 0.2);
257
257
  --ak-color-danger-ink: #991b1b;
258
- --ak-color-info: #0ea5e9;
258
+ --ak-color-info: #0369a1;
259
259
  --ak-color-info-soft: rgba(14, 165, 233, 0.16);
260
260
  --ak-color-info-ink: #0c4a6e;
261
261
 
262
262
  --ak-color-link: var(--ak-color-primary);
263
263
  --ak-color-link-hover: color-mix(in oklch, var(--ak-color-link) 88%, transparent);
264
- --ak-color-focus-ring: oklch(0.708 0 0 / 0.5);
265
- --ak-color-disabled-bg: oklch(0.97 0 0);
264
+ --ak-color-focus-ring: oklch(0.58 0 0);
265
+ --ak-color-disabled-bg: oklch(0.9 0 0);
266
266
  --ak-color-disabled-surface: oklch(1 0 0);
267
- --ak-color-disabled-border: oklch(0.922 0 0);
268
- --ak-color-disabled-text: oklch(0.556 0 0);
267
+ --ak-color-disabled-border: oklch(0.62 0 0);
268
+ --ak-color-disabled-text: oklch(0.5 0 0);
269
269
  --ak-color-selected: oklch(0.97 0 0);
270
270
  --ak-color-selected-border: oklch(0.922 0 0);
271
271
  --ak-color-hover: oklch(0.97 0 0);
@@ -53,3 +53,4 @@
53
53
  @import "./styles/overlays/popover.css";
54
54
  @import "./styles/overlays/hover-card.css";
55
55
  @import "./styles/overlays/tooltip.css";
56
+ @import "./styles/base/accessibility.css";
@@ -288,3 +288,10 @@
288
288
  min-inline-size: var(--_button-height);
289
289
  padding-inline: 0;
290
290
  }
291
+
292
+ /* Keep the focus contract last so variant box-shadow resets cannot hide it. */
293
+ :where(.btn, .btn-close, [data-slot="button"]):focus-visible {
294
+ outline: none;
295
+ border-color: var(--ak-color-ring);
296
+ box-shadow: 0 0 0 var(--ak-focus-ring-width) var(--ak-color-focus-ring);
297
+ }
@@ -0,0 +1,88 @@
1
+ /*
2
+ * Cross-component accessibility states live at the end of the default-theme
3
+ * cascade so new controls inherit the same perceivability contract.
4
+ */
5
+ :where(
6
+ [data-slot="button"][data-disabled],
7
+ [data-slot="input"][data-disabled],
8
+ [data-slot="textarea"][data-disabled],
9
+ [data-slot="select-trigger"][data-disabled],
10
+ [data-slot="checkbox"][data-disabled],
11
+ [data-slot="radio-group-item"][data-disabled],
12
+ [data-slot="switch"][data-disabled]
13
+ ) {
14
+ background: var(--ak-color-disabled-bg);
15
+ border-color: var(--ak-color-disabled-border);
16
+ color: var(--ak-color-disabled-text);
17
+ box-shadow: none;
18
+ opacity: 1;
19
+ }
20
+
21
+ @media (forced-colors: active) {
22
+ :where(
23
+ [data-slot="button"],
24
+ [data-slot="input"],
25
+ [data-slot="textarea"],
26
+ [data-slot="select-trigger"],
27
+ [data-slot="checkbox"],
28
+ [data-slot="radio-group-item"],
29
+ [data-slot="switch"],
30
+ [data-slot="dropdown-trigger"],
31
+ [tabindex]:not([tabindex="-1"]),
32
+ [role="menuitem"]
33
+ ):focus-visible {
34
+ outline: 2px solid Highlight;
35
+ outline-offset: 2px;
36
+ box-shadow: none;
37
+ }
38
+
39
+ :where(
40
+ [data-slot="button"],
41
+ [data-slot="input"],
42
+ [data-slot="textarea"],
43
+ [data-slot="select-trigger"],
44
+ [data-slot="checkbox"],
45
+ [data-slot="radio-group-item"],
46
+ [data-slot="switch"],
47
+ [data-slot="dropdown-content"],
48
+ [data-slot="menubar-content"]
49
+ ) {
50
+ border-color: ButtonText;
51
+ }
52
+
53
+ :where([data-disabled]) {
54
+ border-color: GrayText;
55
+ color: GrayText;
56
+ opacity: 1;
57
+ }
58
+
59
+ :where(
60
+ [data-slot="dialog-content"],
61
+ [data-slot="sheet-content"],
62
+ [data-slot="popover-content"],
63
+ [data-slot="dropdown-content"],
64
+ [data-slot="menubar-content"]
65
+ ) {
66
+ border: 2px solid CanvasText;
67
+ background: Canvas;
68
+ color: CanvasText;
69
+ box-shadow: none;
70
+ }
71
+
72
+ :where(
73
+ [data-slot="dropdown-item"]:is(:focus, :focus-visible),
74
+ [data-slot="menubar-item"]:is(:focus, :focus-visible),
75
+ [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
76
+ ) {
77
+ outline: 2px solid Highlight;
78
+ outline-offset: -2px;
79
+ background: Highlight;
80
+ color: HighlightText;
81
+ }
82
+
83
+ :where([data-slot="dialog-overlay"]) {
84
+ background: Canvas;
85
+ opacity: 0.75;
86
+ backdrop-filter: none;
87
+ }
88
+ }
@@ -44,11 +44,12 @@
44
44
 
45
45
  :where(.input[data-disabled], .input:disabled, [data-slot="input"][data-disabled]) {
46
46
  background: var(--ak-color-disabled-bg);
47
- border-color: var(--ak-color-input);
47
+ border-color: var(--ak-color-disabled-border);
48
+ color: var(--ak-color-disabled-text);
48
49
  box-shadow: none;
49
50
  pointer-events: none;
50
51
  cursor: not-allowed;
51
- opacity: 0.5;
52
+ opacity: 1;
52
53
  }
53
54
 
54
55
  :where(.input-sm, [data-slot="input"][data-size="sm"]) {
@@ -53,7 +53,7 @@
53
53
  }
54
54
 
55
55
  :where([data-slot="dialog-content"], [data-slot="sheet-content"]) {
56
- --_dialog-surface: var(--ak-color-bg);
56
+ --_dialog-surface: var(--ak-color-surface-raised);
57
57
  --_dialog-border: var(--ak-color-border);
58
58
 
59
59
  display: grid;
@@ -158,13 +158,14 @@
158
158
  }
159
159
 
160
160
  :where(.dropdown-item, [data-slot="dropdown-item"]):hover > :where([data-slot="icon"], svg),
161
- :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible
161
+ :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible)
162
162
  > :where([data-slot="icon"], svg) {
163
163
  color: currentColor;
164
164
  }
165
165
 
166
- :where(.dropdown-item, [data-slot="dropdown-item"]):focus-visible {
167
- outline: none;
166
+ :where(.dropdown-item, [data-slot="dropdown-item"]):is(:focus, :focus-visible) {
167
+ outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
168
+ outline-offset: calc(-1 * var(--ak-focus-ring-width));
168
169
  background: var(--ak-color-hover);
169
170
  color: var(--ak-color-text);
170
171
  box-shadow: none;
@@ -165,15 +165,19 @@
165
165
 
166
166
  :where(
167
167
  [data-slot="menubar-item"]:hover > :where([data-slot="icon"], svg),
168
- [data-slot="menubar-item"]:focus-visible > :where([data-slot="icon"], svg),
168
+ [data-slot="menubar-item"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg),
169
169
  [data-slot="menubar-sub-trigger"]:hover > :where([data-slot="icon"], svg),
170
- [data-slot="menubar-sub-trigger"]:focus-visible > :where([data-slot="icon"], svg)
170
+ [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible) > :where([data-slot="icon"], svg)
171
171
  ) {
172
172
  color: currentColor;
173
173
  }
174
174
 
175
- :where([data-slot="menubar-item"]:focus-visible, [data-slot="menubar-sub-trigger"]:focus-visible) {
176
- outline: none;
175
+ :where(
176
+ [data-slot="menubar-item"]:is(:focus, :focus-visible),
177
+ [data-slot="menubar-sub-trigger"]:is(:focus, :focus-visible)
178
+ ) {
179
+ outline: var(--ak-focus-ring-width) solid var(--ak-color-focus-ring);
180
+ outline-offset: calc(-1 * var(--ak-focus-ring-width));
177
181
  background: var(--ak-color-hover);
178
182
  color: var(--ak-color-text);
179
183
  box-shadow: none;
@@ -178,18 +178,18 @@
178
178
 
179
179
  --ak-dark-color-text: oklch(0.985 0 0);
180
180
  --ak-dark-color-text-muted: oklch(0.708 0 0);
181
- --ak-dark-color-text-subtle: oklch(0.556 0 0);
181
+ --ak-dark-color-text-subtle: oklch(0.74 0 0);
182
182
  --ak-dark-color-text-inverse: oklch(0.205 0 0);
183
183
 
184
184
  --ak-dark-color-bg: oklch(0.145 0 0);
185
185
  --ak-dark-color-surface: oklch(0.205 0 0);
186
186
  --ak-dark-color-surface-muted: oklch(0.269 0 0);
187
- --ak-dark-color-surface-raised: oklch(0.205 0 0);
188
- --ak-dark-color-surface-overlay: oklch(0.205 0 0);
187
+ --ak-dark-color-surface-raised: oklch(0.235 0 0);
188
+ --ak-dark-color-surface-overlay: oklch(0.269 0 0);
189
189
 
190
190
  --ak-dark-color-border-subtle: oklch(0.269 0 0);
191
191
  --ak-dark-color-border: oklch(1 0 0 / 10%);
192
- --ak-dark-color-border-strong: oklch(0.439 0 0);
192
+ --ak-dark-color-border-strong: oklch(0.54 0 0);
193
193
 
194
194
  --ak-dark-color-success: #4ade80;
195
195
  --ak-dark-color-success-soft: #052e16;
@@ -206,11 +206,11 @@
206
206
 
207
207
  --ak-dark-color-link: var(--ak-dark-color-text);
208
208
  --ak-dark-color-link-hover: color-mix(in oklch, var(--ak-dark-color-link) 88%, transparent);
209
- --ak-dark-color-focus-ring: oklch(0.556 0 0 / 0.5);
210
- --ak-dark-color-disabled-bg: oklch(0.269 0 0);
209
+ --ak-dark-color-focus-ring: oklch(0.6 0 0);
210
+ --ak-dark-color-disabled-bg: oklch(0.32 0 0);
211
211
  --ak-dark-color-disabled-surface: oklch(0.205 0 0);
212
- --ak-dark-color-disabled-border: oklch(1 0 0 / 10%);
213
- --ak-dark-color-disabled-text: oklch(0.708 0 0);
212
+ --ak-dark-color-disabled-border: oklch(0.54 0 0);
213
+ --ak-dark-color-disabled-text: oklch(0.74 0 0);
214
214
  --ak-dark-color-selected: oklch(0.269 0 0);
215
215
  --ak-dark-color-selected-border: oklch(1 0 0 / 10%);
216
216
  --ak-dark-color-hover: oklch(0.269 0 0);
@@ -233,39 +233,39 @@
233
233
 
234
234
  --ak-color-text: oklch(0.145 0 0);
235
235
  --ak-color-text-muted: oklch(0.556 0 0);
236
- --ak-color-text-subtle: oklch(0.708 0 0);
236
+ --ak-color-text-subtle: oklch(0.54 0 0);
237
237
  --ak-color-text-inverse: oklch(0.985 0 0);
238
238
 
239
239
  --ak-color-bg: oklch(1 0 0);
240
- --ak-color-surface: oklch(1 0 0);
241
- --ak-color-surface-muted: oklch(0.97 0 0);
242
- --ak-color-surface-raised: oklch(1 0 0);
243
- --ak-color-surface-overlay: oklch(1 0 0);
240
+ --ak-color-surface: oklch(0.985 0 0);
241
+ --ak-color-surface-muted: oklch(0.95 0 0);
242
+ --ak-color-surface-raised: oklch(0.97 0 0);
243
+ --ak-color-surface-overlay: oklch(0.94 0 0);
244
244
 
245
245
  --ak-color-border-subtle: oklch(0.922 0 0);
246
246
  --ak-color-border: oklch(0.922 0 0);
247
- --ak-color-border-strong: oklch(0.708 0 0);
247
+ --ak-color-border-strong: oklch(0.62 0 0);
248
248
 
249
249
  --ak-color-success: #16a34a;
250
250
  --ak-color-success-soft: rgba(22, 163, 74, 0.14);
251
251
  --ak-color-success-ink: #166534;
252
- --ak-color-warning: #f59e0b;
252
+ --ak-color-warning: #b45309;
253
253
  --ak-color-warning-soft: rgba(245, 158, 11, 0.16);
254
254
  --ak-color-warning-ink: #854d0e;
255
255
  --ak-color-danger: oklch(0.577 0.245 27.325);
256
256
  --ak-color-danger-soft: oklch(0.577 0.245 27.325 / 0.2);
257
257
  --ak-color-danger-ink: #991b1b;
258
- --ak-color-info: #0ea5e9;
258
+ --ak-color-info: #0369a1;
259
259
  --ak-color-info-soft: rgba(14, 165, 233, 0.16);
260
260
  --ak-color-info-ink: #0c4a6e;
261
261
 
262
262
  --ak-color-link: var(--ak-color-primary);
263
263
  --ak-color-link-hover: color-mix(in oklch, var(--ak-color-link) 88%, transparent);
264
- --ak-color-focus-ring: oklch(0.708 0 0 / 0.5);
265
- --ak-color-disabled-bg: oklch(0.97 0 0);
264
+ --ak-color-focus-ring: oklch(0.58 0 0);
265
+ --ak-color-disabled-bg: oklch(0.9 0 0);
266
266
  --ak-color-disabled-surface: oklch(1 0 0);
267
- --ak-color-disabled-border: oklch(0.922 0 0);
268
- --ak-color-disabled-text: oklch(0.556 0 0);
267
+ --ak-color-disabled-border: oklch(0.62 0 0);
268
+ --ak-color-disabled-text: oklch(0.5 0 0);
269
269
  --ak-color-selected: oklch(0.97 0 0);
270
270
  --ak-color-selected-border: oklch(0.922 0 0);
271
271
  --ak-color-hover: oklch(0.97 0 0);