@apliteni/apliteni-ui 0.94.0 → 0.95.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.
@@ -46,11 +46,11 @@
46
46
 
47
47
  **Rule:** Keep a focus indicator at least 3:1 against every ground it reaches.
48
48
 
49
- **Do:** The solid band, with a surface-coloured gap around an accent-filled control.
49
+ **Do:** One solid accent band, drawn 1px off the control it marks.
50
50
 
51
- **Don't:** The halo on its own, with no solid band inside it to measure.
51
+ **Don't:** A glow on its own, with no solid band inside it to measure.
52
52
 
53
- **Except:** The band must clear the bar against the gap and halo pixels too, not only against flat ground.
53
+ **Except:** The band must clear the bar against the surface its offset leaves showing, not only flat ground.
54
54
 
55
55
  ## Disabled control legibility
56
56
 
@@ -6,13 +6,13 @@
6
6
 
7
7
  **Rule:** Give every focusable control the same `--ring`, and show it only on `:focus-visible`.
8
8
 
9
- **Why:** One solid band and halo keep focus consistent while the browser decides when to show it.
9
+ **Why:** One solid band keeps focus consistent while the browser decides when to show it.
10
10
 
11
- **Do:** Apply `--ring` to both the button and input on keyboard focus.
11
+ **Do:** Apply `--ring`, with `--ring-offset`, to both the button and input on keyboard focus.
12
12
 
13
13
  **Don't:** Give the input a separate muted outline while the button uses `--ring`.
14
14
 
15
- **Except:** A scroll region inside a surface takes `--ring-scroll`: the same 1px gap and 2px band, drawn inward, without the halo. `--ring` is drawn for a 32px control, where the halo is a glint; around a 400px region it spreads 14px past the band and lights the surface instead of the box that scrolls. A box that is itself the outermost surface — a panel, a card — keeps `--ring`. Text-entry controls may match `:focus-visible` on mouse focus. Surface backgrounds set the gap colour and recompose `--ring`; adjust width and colour at the root.
15
+ **Except:** A scroll region inside a surface takes `--ring-scroll`: the same band drawn inward, with `--ring-scroll-offset`. An inset shadow paints under a box's own children, so a table scrolling under one erases the band; an outline survives it. A box that is itself the outermost surface — a panel, a card — keeps `--ring`. Text-entry controls may match `:focus-visible` on mouse focus. Both bands are declared once at the root and recomposed nowhere, because an outline leaves its offset unpainted. Adjust width and colour there.
16
16
 
17
17
  ## Busy controls
18
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.94.0",
3
+ "version": "0.95.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
@@ -207,7 +207,6 @@
207
207
  display: flex;
208
208
  flex-direction: column;
209
209
  background: var(--bg-elevated);
210
- --ring-gap: var(--bg-elevated);
211
210
  border: 0;
212
211
  box-shadow: var(--elev-drop);
213
212
  border-radius: var(--radius-md);
@@ -275,13 +274,13 @@
275
274
  color: inherit;
276
275
  }
277
276
  .rx-sort:focus-visible {
278
- outline: 2px solid transparent;
279
- box-shadow: var(--ring);
277
+ outline: var(--ring);
278
+ outline-offset: var(--ring-offset);
280
279
  border-radius: 4px;
281
280
  }
282
281
  .ui-table__selection input[type=checkbox]:focus-visible {
283
- outline: 2px solid transparent;
284
- box-shadow: var(--ring);
282
+ outline: var(--ring);
283
+ outline-offset: var(--ring-offset);
285
284
  border-radius: var(--radius-xs);
286
285
  }
287
286
  .rx-caret {
@@ -384,7 +383,8 @@
384
383
  color: var(--strong);
385
384
  }
386
385
  .ui-datepicker__opt:focus-visible {
387
- outline: 2px solid transparent;
386
+ outline: var(--ring);
387
+ outline-offset: var(--ring-offset);
388
388
  }
389
389
  .ui-datepicker__opt.is-inside {
390
390
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-elevated));
@@ -394,7 +394,7 @@
394
394
  border-color: var(--accent-strong);
395
395
  color: var(--accent-contrast);
396
396
  }
397
- .ui-datepicker__opt.is-selected:hover {
397
+ .ui-datepicker__opt.is-selected:hover:not(:focus-visible) {
398
398
  outline-color: transparent;
399
399
  background: var(--accent-strong);
400
400
  color: var(--accent-contrast);
@@ -402,12 +402,15 @@
402
402
  .ui-datepicker__opt.is-today {
403
403
  border-color: var(--accent);
404
404
  }
405
- .ui-datepicker__opt.is-today:hover {
405
+ .ui-datepicker__opt.is-today:hover:not(:focus-visible) {
406
406
  outline-color: transparent;
407
407
  }
408
408
  .ui-datepicker__opt.is-selected.is-today {
409
409
  border-color: var(--accent-contrast);
410
410
  }
411
+ .ui-datepicker__opt.is-today:focus-visible {
412
+ border-color: transparent;
413
+ }
411
414
  .ui-datepicker__opt.is-disabled {
412
415
  color: var(--disabled-ink-bare);
413
416
  cursor: not-allowed;
@@ -416,11 +419,13 @@
416
419
  text-decoration: line-through;
417
420
  }
418
421
  .ui-datepicker__opt.is-disabled:hover {
419
- outline-color: transparent;
420
422
  background: transparent;
421
423
  border-color: transparent;
422
424
  color: var(--disabled-ink-bare);
423
425
  }
426
+ .ui-datepicker__opt.is-disabled:hover:not(:focus-visible) {
427
+ outline-color: transparent;
428
+ }
424
429
  .ui-datepicker__opt.is-disabled.is-selected,
425
430
  .ui-datepicker__opt.is-disabled.is-selected:hover,
426
431
  .ui-datepicker__opt.is-disabled.is-inside,
@@ -645,11 +650,13 @@
645
650
  .ui-file:hover {
646
651
  border-color: var(--field-edge-hover);
647
652
  }
648
- .ui-file:focus-within,
649
653
  .ui-file.is-dragging {
650
654
  border-color: var(--accent);
651
- box-shadow: var(--ring);
652
- outline: 2px solid transparent;
655
+ }
656
+ .ui-file:focus-within,
657
+ .ui-file.is-dragging {
658
+ outline: var(--ring);
659
+ outline-offset: var(--ring-offset);
653
660
  }
654
661
  .ui-file.is-invalid {
655
662
  border-color: var(--pink);
@@ -31,8 +31,8 @@
31
31
  (0,2,0) wins without !important — the argument `.ui-nav .ui-nav__item` makes
32
32
  in nav.css. */
33
33
  .ui-back[href] { color: var(--text); }
34
- .ui-back[href]:hover { color: var(--strong); background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
35
- .ui-back:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
34
+ .ui-back[href]:hover { color: var(--strong); background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
35
+ .ui-back:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
36
36
 
37
37
  /* 16px, with the stroke raised to match: the glyph set draws at 1.7 on a 24-unit
38
38
  box, which is 1.13 CSS px at this size and under the kit's 1.5 for a stroked
@@ -15,24 +15,24 @@
15
15
  padding: 3px 9px;
16
16
  border-radius: var(--radius-pill);
17
17
  color: var(--text);
18
- background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
18
+ background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */
19
19
  white-space: nowrap;
20
20
  }
21
21
  /* Status chips take the --chip-* pairs, so the light app's solid fills live in
22
22
  tokens.css rather than in a theme override down here: on white the glow-tints
23
23
  wash out and the ink has to deepen to clear AA, and that is a token decision. */
24
- .ui-badge--live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
25
- .ui-badge--soon { color: var(--accent); background: var(--surface); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
26
- .ui-badge--info { color: var(--chip-info-ink); background: var(--chip-info-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
24
+ .ui-badge--live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
25
+ .ui-badge--soon { color: var(--accent); background: var(--surface); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
26
+ .ui-badge--info { color: var(--chip-info-ink); background: var(--chip-info-fill); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
27
27
  .ui-badge--warn { color: var(--chip-warn-ink); background: var(--chip-warn-fill); }
28
- .ui-badge--danger { color: var(--chip-danger-ink); background: var(--chip-danger-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
29
- .ui-badge--archive { /* muted-ink: state — archived record */ color: var(--muted); background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
28
+ .ui-badge--danger { color: var(--chip-danger-ink); background: var(--chip-danger-fill); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
29
+ .ui-badge--archive { /* muted-ink: state — archived record */ color: var(--muted); background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
30
30
 
31
31
  /* Semantic status variants — for record/row status in data tables
32
32
  (paid / verified / pending / failed …). Same chip, mapped to meaning. */
33
- .ui-badge--success { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
33
+ .ui-badge--success { color: var(--chip-success-ink); background: var(--chip-success-fill); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
34
34
  .ui-badge--pending { color: var(--chip-warn-ink); background: var(--chip-warn-fill); }
35
- .ui-badge--neutral { color: var(--text); background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
35
+ .ui-badge--neutral { color: var(--text); background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
36
36
 
37
37
  /* Pill: same shape, sentence case, a touch larger — a metadata chip */
38
38
  .ui-pill {
@@ -42,7 +42,7 @@
42
42
  /* rank: chip */
43
43
  font-size: var(--text-xs);
44
44
  color: var(--text);
45
- background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
45
+ background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */
46
46
  border-radius: var(--radius-pill);
47
47
  padding: 4px 11px;
48
48
  font-weight: var(--weight-semibold);
@@ -50,8 +50,8 @@
50
50
  /* The live pill is the same status paint as the live badge, so it takes the
51
51
  same pair. On its own it was --green on --glow-green, which is 3.97:1 over
52
52
  white — under AA, while the badge beside it cleared at 4.64:1. See issue #131. */
53
- .ui-pill--live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
54
- .ui-pill--soon { color: var(--accent); background: var(--surface); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
53
+ .ui-pill--live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
54
+ .ui-pill--soon { color: var(--accent); background: var(--surface); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
55
55
 
56
56
  /* Status dot */
57
57
  .ui-dot {
@@ -15,7 +15,7 @@ html {
15
15
  }
16
16
 
17
17
  body {
18
- background: var(--bg); /* ring-gap: inherit — page background inherits root overrides. */
18
+ background: var(--bg); /* code-bg: inherit — page background inherits root overrides. */
19
19
  color: var(--text);
20
20
  font-family: var(--font-sans);
21
21
  font-weight: var(--weight-normal);
@@ -27,7 +27,7 @@ body {
27
27
  }
28
28
 
29
29
  ::selection {
30
- background: color-mix(in srgb, var(--accent) 30%, transparent); /* ring-gap: inherit — selection paint is not a focus container. */
30
+ background: color-mix(in srgb, var(--accent) 30%, transparent); /* code-bg: inherit — selection paint is not a focus container. */
31
31
  color: var(--strong);
32
32
  }
33
33
 
@@ -87,7 +87,7 @@ strong {
87
87
 
88
88
  /* Spotlight — a soft radial from the top-centre (auth cards, focused heroes). */
89
89
  .ui-bg-spotlight::before {
90
- background: radial-gradient(620px 340px at 50% -12%, var(--glow-purple), transparent 70%); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
90
+ background: radial-gradient(620px 340px at 50% -12%, var(--glow-purple), transparent 70%); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
91
91
  }
92
92
  /* Grid — a faint token-coloured grid, masked to fade at the edges. */
93
93
  .ui-bg-grid::before {
@@ -107,7 +107,7 @@ strong {
107
107
  }
108
108
  /* Accent wash — a gentle top-down tint in the current accent. */
109
109
  .ui-bg-wash::before {
110
- background: linear-gradient(180deg, var(--glow-purple), transparent 45%); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
110
+ background: linear-gradient(180deg, var(--glow-purple), transparent 45%); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
111
111
  }
112
112
 
113
113
  /* Layout helpers used by the example apps */
@@ -148,6 +148,6 @@ svg:where(:not([width]):not([height])) {
148
148
  .ui-seg button:focus-visible,
149
149
  .toggle:focus-visible,
150
150
  .ui-switch:focus-visible {
151
- outline: 2px solid transparent;
152
- box-shadow: var(--ring);
151
+ outline: var(--ring);
152
+ outline-offset: var(--ring-offset);
153
153
  }
@@ -22,7 +22,7 @@
22
22
  /* Light needs a stronger line here: --border on this fill measures 1.047:1.
23
23
  why: docs/foundations.md#elevation */
24
24
  border: 1px solid var(--control-edge);
25
- background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
25
+ background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
26
26
  color: var(--text);
27
27
  white-space: nowrap;
28
28
  transition: background var(--dur-fast) var(--ease),
@@ -33,10 +33,14 @@
33
33
  }
34
34
 
35
35
  .ui-btn:hover {
36
- border-color: var(--accent);
37
- background: color-mix(in srgb, var(--accent) 14%, var(--surface)); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
36
+ background: color-mix(in srgb, var(--accent) 14%, var(--surface)); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
38
37
  color: var(--strong);
39
38
  }
39
+ /* The hover edge steps aside under the band: an accent edge one pixel inside an
40
+ accent band is the second band #578 round r34 took off the focus rules, and a
41
+ pointer resting on a focused button would put it back. The fill above is not an
42
+ edge and stays. why: docs/foundations.md#the-focus-ring */
43
+ .ui-btn:hover:not(:focus-visible) { border-color: var(--accent); }
40
44
  .ui-btn:active { transform: translateY(1px); }
41
45
 
42
46
  .ui-btn svg { width: 16px; height: 16px; flex: none; stroke-width: 2.4; }
@@ -61,7 +65,7 @@
61
65
  color: var(--text);
62
66
  }
63
67
  .ui-btn--ghost:hover {
64
- background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
68
+ background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
65
69
  border-color: transparent;
66
70
  color: var(--strong);
67
71
  }
@@ -70,7 +74,7 @@
70
74
  .ui-btn--danger:hover, .ui-btn--danger:focus-visible {
71
75
  color: var(--pink);
72
76
  border-color: var(--pink);
73
- background: color-mix(in srgb, var(--pink) 10%, transparent); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
77
+ background: color-mix(in srgb, var(--pink) 10%, transparent); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
74
78
  }
75
79
 
76
80
  /* -- Sizes -------------------------------------------------------------- */
@@ -95,7 +99,7 @@
95
99
  makes this read as inert; the quiet ink is the rest. #220 */
96
100
  .ui-btn:disabled,
97
101
  .ui-btn[aria-disabled="true"] {
98
- background: var(--disabled-surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
102
+ background: var(--disabled-surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */
99
103
  border-color: var(--disabled-border);
100
104
  /* muted-ink: state — unavailable control */ color: var(--disabled-ink);
101
105
  cursor: not-allowed;
@@ -137,7 +141,7 @@
137
141
  .ui-btn[aria-busy="true"],
138
142
  .ui-btn[aria-busy="true"]:hover,
139
143
  .ui-btn[aria-busy="true"]:active {
140
- background: var(--surface); /* ring-gap: inherit — busy paint retains the button's containing gap. */
144
+ background: var(--surface); /* code-bg: inherit — busy paint retains the button's containing gap. */
141
145
  border-color: var(--border);
142
146
  color: var(--text);
143
147
  filter: none;
@@ -146,35 +150,35 @@
146
150
  .ui-btn[aria-busy="true"].ui-btn--primary,
147
151
  .ui-btn[aria-busy="true"].ui-btn--primary:hover,
148
152
  .ui-btn[aria-busy="true"].ui-btn--primary:active {
149
- background: var(--accent-strong); /* ring-gap: inherit — busy paint retains the button's containing gap. */
153
+ background: var(--accent-strong); /* code-bg: inherit — busy paint retains the button's containing gap. */
150
154
  border-color: var(--accent-strong);
151
155
  color: var(--accent-contrast);
152
156
  }
153
157
  .ui-btn[aria-busy="true"].ui-btn--ghost,
154
158
  .ui-btn[aria-busy="true"].ui-btn--ghost:hover,
155
159
  .ui-btn[aria-busy="true"].ui-btn--ghost:active {
156
- background: transparent; /* ring-gap: inherit — busy paint retains the button's containing gap. */
160
+ background: transparent; /* code-bg: inherit — busy paint retains the button's containing gap. */
157
161
  border-color: transparent;
158
162
  color: var(--text);
159
163
  }
160
164
  .ui-btn[aria-busy="true"].ui-btn--danger,
161
165
  .ui-btn[aria-busy="true"].ui-btn--danger:hover,
162
166
  .ui-btn[aria-busy="true"].ui-btn--danger:active {
163
- background: var(--surface); /* ring-gap: inherit — busy paint retains the button's containing gap. */
167
+ background: var(--surface); /* code-bg: inherit — busy paint retains the button's containing gap. */
164
168
  border-color: var(--border);
165
169
  color: var(--text);
166
170
  }
167
171
  /* A caller can mark a control both busy and unavailable. The explicit marker
168
172
  keeps that ordinary disabled state from inheriting the busy variant paint. */
169
173
  .ui-btn[aria-busy="true"][data-btn-disabled] {
170
- background: var(--disabled-surface); /* ring-gap: inherit — busy+disabled keeps its containing gap. */
174
+ background: var(--disabled-surface); /* code-bg: inherit — busy+disabled keeps its containing gap. */
171
175
  border-color: var(--disabled-border);
172
176
  /* muted-ink: state — busy+disabled control */ color: var(--disabled-ink);
173
177
  cursor: not-allowed;
174
178
  pointer-events: none;
175
179
  }
176
180
  .ui-btn--ghost[aria-busy="true"][data-btn-disabled] {
177
- background: transparent; /* ring-gap: inherit — busy+disabled keeps its containing gap. */
181
+ background: transparent; /* code-bg: inherit — busy+disabled keeps its containing gap. */
178
182
  border-color: transparent;
179
183
  /* muted-ink: state — busy+disabled ghost */ color: var(--disabled-ink-bare);
180
184
  }
@@ -182,7 +186,6 @@
182
186
  .ui-btn__label {
183
187
  grid-area: 1 / 1;
184
188
  transform: translateY(0);
185
-
186
189
  }
187
190
  .ui-btn[data-btn-ready] .ui-btn__label { animation: ui-btn-label-in var(--dur-med) var(--ease-out) both; }
188
191
  .ui-btn[aria-busy="true"] .ui-btn__label {
@@ -7,7 +7,7 @@
7
7
  gap: 12px;
8
8
  padding: 15px 17px;
9
9
  border-radius: var(--radius-md);
10
- background: var(--surface); --ring-gap: var(--surface);
10
+ background: var(--surface);
11
11
  font-size: calc(var(--text-sm) + 0.5px);
12
12
  line-height: 1.55;
13
13
  color: var(--text);
@@ -19,14 +19,14 @@
19
19
  .ui-callout b { color: var(--text); font-weight: var(--weight-semibold); }
20
20
  .ui-callout code { font-family: var(--font-mono); font-size: 0.92em; color: var(--text); }
21
21
 
22
- .ui-callout--info { --ring-gap: inherit; background: var(--glow-cyan); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
22
+ .ui-callout--info { background: var(--glow-cyan); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
23
23
  .ui-callout--info .ui-callout__icon { color: var(--chip-info-ink); } /* the chip inks, not the raw signals: in light the raw signal cleared the graphic bar by a tenth on its own wash and read as a smudge, and a stroke is read the way text is. The two are the same value in dark by construction, so only light moves. #206 */
24
- .ui-callout--success { --ring-gap: inherit; background: var(--glow-green); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
24
+ .ui-callout--success { background: var(--glow-green); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
25
25
  .ui-callout--success .ui-callout__icon { color: var(--chip-success-ink); }
26
26
  .ui-callout--success a { /* muted-ink: state — opaque accent/text blend keeps links above 4.5:1 on the success wash. */ color: color-mix(in srgb, var(--accent) 95%, var(--text)); }
27
- .ui-callout--warn { --ring-gap: inherit; background: color-mix(in srgb, var(--amber) 13%, transparent); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
27
+ .ui-callout--warn { background: color-mix(in srgb, var(--amber) 13%, transparent); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
28
28
  .ui-callout--warn .ui-callout__icon { color: var(--chip-warn-ink); }
29
- .ui-callout--danger { --ring-gap: inherit; background: var(--glow-pink); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
29
+ .ui-callout--danger { background: var(--glow-pink); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */ }
30
30
  .ui-callout--danger .ui-callout__icon { color: var(--chip-danger-ink); }
31
31
 
32
32
  /* ----------------------------------------------------------------------------
@@ -45,7 +45,7 @@
45
45
  width: 100%;
46
46
  max-width: var(--panel-md);
47
47
  overflow: hidden; /* clips the timer bar to the rounded corners */
48
- background: var(--surface); --ring-gap: var(--surface);
48
+ background: var(--surface);
49
49
  border: 1px solid var(--float-edge);
50
50
  box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
51
51
  animation: ui-toast-in var(--dur-med) var(--ease-out) both;
@@ -66,8 +66,8 @@
66
66
 
67
67
  /* Tone ink keeps actions readable on the neutral surface. */
68
68
  .ui-toast__action { flex: none; align-self: center; background: none; border: 0; padding: 4px 9px; border-radius: var(--radius-xs); font: inherit; font-weight: var(--weight-medium); color: var(--toast-action-ink); cursor: pointer; }
69
- .ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
70
- .ui-toast__action:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
69
+ .ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
70
+ .ui-toast__action:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
71
71
 
72
72
  .ui-toast__close { flex: none; align-self: flex-start; background: none; border: 0; /* muted-ink: glyph — dismiss icon */ color: var(--muted); cursor: pointer; padding: 2px; display: grid; place-items: center; position: relative; font: inherit; }
73
73
  .ui-toast__close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.5; }
@@ -91,14 +91,14 @@
91
91
  transform: translate(-50%, -50%);
92
92
  }
93
93
  .ui-toast__close:hover { color: var(--text); }
94
- .ui-toast__close:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-radius: var(--radius-xs); }
94
+ .ui-toast__close:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); border-radius: var(--radius-xs); }
95
95
 
96
96
  /* auto-dismiss progress bar — .is-running is added by wireToastStack() */
97
97
  .ui-toast__timer { position: absolute; left: 0; bottom: 0; height: 3px; width: 100%; background: var(--toast-accent); opacity: 0.85; transform-origin: left; }
98
98
  .ui-toast__timer.is-running { animation: ui-toast-timer var(--toast-dur, 5s) linear forwards; /* motion: ambient — the bar spends the toast's dismiss timer, which the caller sets */ }
99
99
 
100
100
  /* Legacy style classes share the neutral card. */
101
- .ui-toast--soft, .ui-toast--solid, .ui-toast--outline { background: var(--surface); --ring-gap: var(--surface); color: var(--text); }
101
+ .ui-toast--soft, .ui-toast--solid, .ui-toast--outline { background: var(--surface); color: var(--text); }
102
102
 
103
103
  /* compact — single line, no body text */
104
104
  .ui-toast--compact { align-items: center; }
@@ -121,7 +121,7 @@
121
121
  border-radius: var(--radius-lg);
122
122
  padding: 30px;
123
123
  text-align: center;
124
- background: var(--glow-green); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
124
+ background: var(--glow-green); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
125
125
  }
126
126
  .ui-success__check {
127
127
  --sx-green: var(--chip-success-ink);
@@ -8,7 +8,7 @@
8
8
  instead — a 22px penumbra off a 10px offset, the width Artur's reference uses on a
9
9
  card. Chosen by him on #490 round t3. why: docs/foundations.md#elevation */
10
10
  .ui-card {
11
- background: var(--surface); --ring-gap: var(--surface);
11
+ background: var(--surface);
12
12
  border: 1px solid var(--card-edge);
13
13
  box-shadow: var(--elev-rest);
14
14
  border-radius: var(--radius-xl);
@@ -46,11 +46,11 @@
46
46
  works in dark sinks a light card below the page. Each variant edges itself in its own
47
47
  colour. why: docs/foundations.md#elevation */
48
48
  .ui-card--accent {
49
- background: color-mix(in srgb, var(--accent) var(--card-tint), var(--surface)); --ring-gap: color-mix(in srgb, var(--accent) var(--card-tint), var(--surface));
49
+ background: color-mix(in srgb, var(--accent) var(--card-tint), var(--surface));
50
50
  border-color: color-mix(in srgb, var(--accent) 22%, transparent);
51
51
  }
52
52
  .ui-card--live {
53
- background: color-mix(in srgb, var(--green) var(--card-tint), var(--surface)); --ring-gap: color-mix(in srgb, var(--green) var(--card-tint), var(--surface));
53
+ background: color-mix(in srgb, var(--green) var(--card-tint), var(--surface));
54
54
  border-color: color-mix(in srgb, var(--green) 22%, transparent);
55
55
  }
56
56
 
@@ -68,7 +68,7 @@
68
68
  /* The lift alone: the card's own rung is what says it is raised, and hover is not a
69
69
  change of level. why: docs/foundations.md#elevation */
70
70
  .ui-card--interactive:hover { transform: translateY(-2px); }
71
- .ui-card--interactive:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
71
+ .ui-card--interactive:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
72
72
 
73
73
  /* card() emits the title as an h2, so the text face is named here rather than
74
74
  left to the element. why: docs/foundations.md#labels-and-titles */
@@ -111,7 +111,7 @@
111
111
  border-radius: var(--radius-md);
112
112
  display: grid;
113
113
  place-items: center;
114
- background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
114
+ background: var(--glow-purple); /* code-bg: inherit — a wash paints over a ground and behind a chip, so the opaque ground below it is the step. */
115
115
  color: var(--accent);
116
116
  flex: none;
117
117
  }
@@ -4,7 +4,7 @@
4
4
  * ========================================================================== */
5
5
 
6
6
  .ui-snippet {
7
- background: var(--surface); --ring-gap: var(--surface);
7
+ background: var(--surface);
8
8
  border-radius: var(--radius-md);
9
9
  overflow: hidden;
10
10
  }
@@ -34,22 +34,12 @@
34
34
  without its radius draws a square ring that cuts across the card and squares
35
35
  its corners. The card carries the ring instead, so it follows --radius-md and
36
36
  survives `overflow: hidden`, which clips descendants and not the card's own
37
- shadow. --ring-gap comes from the page here, matching the container rule in
38
- tokens.css: a ring outside its own surface cannot use that surface as its gap.
39
- The card also keeps a transparent outline, which forced colors repaints as the
40
- focus signal once box-shadow is gone; the block below is why only one box has
41
- one there. See stories/snippet-focus.test.js. */
42
- .ui-snippet pre:focus-visible { box-shadow: none; }
43
- .ui-snippet:has(pre:focus-visible) { outline: 2px solid transparent; --ring-gap: inherit; box-shadow: var(--ring); }
44
-
45
- /* Forced colors drops box-shadow, so both boxes fall back to their outlines and
46
- the system repaints the pre's transparent one as the square this rule set out
47
- to remove — with the card's rounded one 2px further out, two indicators for one
48
- focus. The card's outline is the one that survives, so the pre keeps none.
49
- Emulated in stories/snippet-focus.test.js and captured in Chromium. #474 */
50
- @media (forced-colors: active) {
51
- .ui-snippet pre:focus-visible { outline: none; }
52
- }
37
+ outline. The pre takes none, in every colour mode: the ring is a real outline
38
+ since #578, so `outline: none` here is what forced colors reads too, and the
39
+ @media block that used to say so is gone with the transparent stand-in it was
40
+ written for. See stories/snippet-focus.test.js. */
41
+ .ui-snippet pre:focus-visible { outline: none; }
42
+ .ui-snippet:has(pre:focus-visible) { outline: var(--ring); outline-offset: var(--ring-offset); }
53
43
 
54
44
  .ui-snippet__copy {
55
45
  background: none;
@@ -74,7 +64,7 @@
74
64
  }
75
65
  .ui-snippet__copy:hover { color: var(--accent); }
76
66
  .ui-snippet--reveal .ui-snippet__copy:hover { color: var(--strong); }
77
- .ui-snippet__copy:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-radius: var(--radius-xs); }
67
+ .ui-snippet__copy:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); border-radius: var(--radius-xs); }
78
68
  .ui-snippet__copy svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2.8; }
79
69
 
80
70
  /* Shell token highlighting (matches viz/account.mjs classes) */
@@ -86,7 +76,7 @@
86
76
 
87
77
  /* Reveal: a live secret/token, tinted green */
88
78
  .ui-snippet--reveal {
89
- background: color-mix(in srgb, var(--green) 10%, var(--surface)); --ring-gap: color-mix(in srgb, var(--green) 10%, var(--surface));
79
+ background: color-mix(in srgb, var(--green) 10%, var(--surface));
90
80
  }
91
81
  .ui-snippet--reveal .ui-snippet__bar > span { color: var(--chip-success-ink); }
92
82
  .ui-snippet--reveal pre { color: var(--chip-success-ink); word-break: break-all; white-space: pre-wrap; }
@@ -95,17 +85,16 @@
95
85
  identifier keeps its chip on the page and inside a card, with no edge of its own. The
96
86
  container hands the surface over in --code-bg; see src/tokens/tokens.css. #537
97
87
 
98
- The chip READS --code-bg and never declares it, which is why its gap can match its paint
99
- and its ring is composed here instead of in the shared recipe: a chip that re-pointed the
100
- hand-off would hand the page to itself and paint the page everywhere. Backticks become a
101
- chip wherever the kit renders prose (fmt() in site/changelog.mjs), so a link inside one is
102
- ordinary markup and gets the gap of the surface it is actually on. */
88
+ The chip READS --code-bg and never declares it: a chip that re-pointed the hand-off would
89
+ hand the page to itself and paint the page everywhere. Backticks become a chip wherever
90
+ the kit renders prose (fmt() in site/changelog.mjs), so a link inside one is ordinary
91
+ markup, and the ring it draws needs nothing from the chip now that an outline leaves its
92
+ 1px of offset unpainted. */
103
93
  .ui-code {
104
94
  font-family: var(--font-mono);
105
95
  font-size: 0.9em;
106
- background: var(--code-bg); --ring-gap: var(--code-bg);
96
+ background: var(--code-bg);
107
97
  padding: 2px 6px;
108
98
  border-radius: var(--radius-xs);
109
99
  color: var(--text);
110
- --ring: 0 0 0 var(--ring-gap-width) var(--ring-gap), 0 0 0 calc(var(--ring-gap-width) + var(--ring-width)) var(--ring-color), 0 0 12px 2px color-mix(in srgb, var(--ring-color) 45%, transparent);
111
100
  }
@@ -68,7 +68,7 @@
68
68
  display: flex;
69
69
  flex-direction: column;
70
70
  font-family: var(--font-sans);
71
- background: var(--cmdk-surface); --ring-gap: var(--cmdk-surface);
71
+ background: var(--cmdk-surface);
72
72
  /* The two-step edge and the drop, through the level's own names: in DARK that is
73
73
  --border-strong outside and --border one pixel inside it, then the drop; in LIGHT
74
74
  both lines are transparent and the drop carries the whole separation.
@@ -84,11 +84,10 @@
84
84
  transform var(--dur-med) var(--ease);
85
85
  }
86
86
  .ui-cmdk.is-open .ui-cmdk__panel { opacity: 1; transform: translate(-50%, 0); }
87
- /* The ring is COMPOSED with the floating treatment, not written over it: a
88
- box-shadow list replaces the whole list, so `box-shadow: var(--ring)` alone
89
- took the panel's edge and its drop off for as long as it held focus.
87
+ /* The ring leaves the panel's own elevation alone: it is an outline since #578, so
88
+ the edge and the drop no longer have to be restated here to survive focus.
90
89
  why: docs/foundations.md#elevation */
91
- .ui-cmdk__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring), inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop); }
90
+ .ui-cmdk__panel:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
92
91
 
93
92
  /* -- The text box --------------------------------------------------------- */
94
93
 
@@ -126,7 +125,7 @@
126
125
  background: none;
127
126
  border: 0;
128
127
  /* The one control in the kit that removes the indicator and puts no
129
- box-shadow: var(--ring) back, against base.css. It is the only tab stop in
128
+ outline: var(--ring) back, against base.css. It is the only tab stop in
130
129
  the dialog and it holds focus from the frame it opens, so a ring would be
131
130
  painted the whole time the palette is up and would mark nothing; the panel
132
131
  already carries one, and the caret says where the typing goes. */
@@ -188,7 +187,11 @@
188
187
  background: none;
189
188
  }
190
189
  .ui-cmdk__item[hidden] { display: none; } /* motion: still — a row the query no longer answers is gone on the next keystroke */
191
- .ui-cmdk__item.is-active { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
190
+ /* The row Enter would run: its fill, and no edge. The edge was an outline marking a
191
+ selection, which is what the focus band is for (#578 r34). --surface-2, not
192
+ --surface: the panel is --bg-elevated, which --surface matches exactly in light, so
193
+ the edge had been the whole mark there. 1.183:1 in light, 1.234:1 in dark. */
194
+ .ui-cmdk__item.is-active { background: var(--surface-2); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
192
195
  .ui-cmdk__item.is-active .ui-cmdk__label { color: var(--strong); }
193
196
 
194
197
  .ui-cmdk__ic {
@@ -268,7 +271,7 @@
268
271
  border-radius: var(--radius-xs);
269
272
  /* Key caps use an edge on the card surface, keeping the shortcut readable.
270
273
  why: docs/foundations.md#elevation */
271
- background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
274
+ background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */
272
275
  font-family: var(--font-sans);
273
276
  font-size: var(--text-xs);
274
277
  line-height: 1.5;
@@ -311,10 +314,10 @@
311
314
  align-items: center;
312
315
  justify-content: center;
313
316
  border-radius: var(--radius-sm);
314
- background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
317
+ background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */
315
318
  border: 1px solid var(--border);
316
319
  }
317
- .ui-cmdk--roomy .ui-cmdk__item.is-active .ui-cmdk__ic { background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
320
+ .ui-cmdk--roomy .ui-cmdk__item.is-active .ui-cmdk__ic { background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ }
318
321
 
319
322
  /* -- Narrow --------------------------------------------------------------- */
320
323
  /* At the kit's narrowest step the palette is the page: full width, off the top