@apliteni/apliteni-ui 0.36.0 → 0.37.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.
@@ -14,7 +14,7 @@
14
14
  --footer-title: var(--strong);
15
15
  --footer-text: var(--text);
16
16
 
17
- background: var(--footer-surface);
17
+ background: var(--footer-surface); /* ring-gap: inherit — transparent footer inherits; the app variant composes its opaque surface. */
18
18
  border-top: 1px solid var(--footer-border);
19
19
  color: var(--footer-text);
20
20
  font-family: var(--font-sans);
@@ -55,7 +55,7 @@
55
55
  border-radius: 10px;
56
56
  border: 1px solid var(--footer-border);
57
57
  color: var(--footer-link);
58
- background: var(--surface);
58
+ background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
59
59
  transition:
60
60
  color var(--dur-fast) var(--ease),
61
61
  border-color var(--dur-fast) var(--ease),
@@ -110,7 +110,7 @@
110
110
 
111
111
  /* app — compact, in-product: surface background + tighter padding. */
112
112
  .ui-footer--app {
113
- --footer-surface: var(--surface);
113
+ --footer-surface: var(--surface); --ring-gap: var(--footer-surface);
114
114
  border-top: 1px solid var(--footer-border);
115
115
  }
116
116
  .ui-footer--app .ui-footer__in { max-width: none; padding: 12px clamp(14px, 2.4vw, 22px); }
@@ -23,7 +23,7 @@
23
23
  font-family: var(--font-sans);
24
24
  font-size: var(--text-base);
25
25
  color: var(--strong);
26
- background: var(--surface-2);
26
+ background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
27
27
  border: 1px solid var(--border);
28
28
  border-radius: var(--radius-md);
29
29
  padding: 12px 15px;
@@ -36,10 +36,10 @@
36
36
  .ui-textarea:hover,
37
37
  .ui-select:hover { border-color: var(--border-strong); }
38
38
 
39
- .ui-input:focus,
40
- .ui-textarea:focus,
41
- .ui-select:focus {
42
- outline: none;
39
+ .ui-input:focus-visible,
40
+ .ui-textarea:focus-visible,
41
+ .ui-select:focus-visible {
42
+ outline: 2px solid transparent;
43
43
  border-color: var(--accent);
44
44
  box-shadow: var(--ring);
45
45
  }
@@ -57,7 +57,7 @@
57
57
  .ui-input:disabled,
58
58
  .ui-select:disabled,
59
59
  .ui-textarea:disabled {
60
- background: var(--disabled-surface);
60
+ background: var(--disabled-surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
61
61
  border-color: var(--disabled-border);
62
62
  /* muted-ink: state — unavailable control */ color: var(--disabled-ink);
63
63
  cursor: not-allowed;
@@ -65,7 +65,7 @@
65
65
 
66
66
  .ui-input.is-invalid,
67
67
  .ui-textarea.is-invalid { border-color: var(--pink); }
68
- .ui-input.is-invalid:focus { box-shadow: 0 0 0 3px var(--glow-pink); }
68
+ /* Invalid fields keep the shared focus indicator; their border carries the error. */
69
69
 
70
70
  /* Input with a leading icon / adornment */
71
71
  .ui-input-group { position: relative; display: flex; align-items: center; }
@@ -117,7 +117,7 @@
117
117
  margin-top: 1px;
118
118
  border: 1.5px solid var(--border-strong);
119
119
  border-radius: var(--radius-xs);
120
- background: var(--surface-2);
120
+ background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
121
121
  cursor: pointer;
122
122
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
123
123
  position: relative;
@@ -159,7 +159,7 @@
159
159
  border-radius: 50%;
160
160
  background: var(--accent-contrast);
161
161
  }
162
- .ui-check input:focus-visible { outline: none; box-shadow: var(--ring); }
162
+ .ui-check input:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
163
163
 
164
164
  /* Toggle switch */
165
165
  .ui-switch {
@@ -175,7 +175,7 @@
175
175
  .ui-switch__track {
176
176
  position: absolute;
177
177
  inset: 0;
178
- background: var(--surface-3);
178
+ background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
179
179
  border-radius: var(--radius-pill);
180
180
  cursor: pointer;
181
181
  transition: background var(--dur-med) var(--ease);
@@ -201,5 +201,5 @@
201
201
  .ui-switch input:checked + .ui-switch__track::before {
202
202
  transform: translateX(calc(var(--sw-w) - var(--sw-h)));
203
203
  }
204
- .ui-switch input:focus-visible + .ui-switch__track { box-shadow: var(--ring); }
204
+ .ui-switch input:focus-visible + .ui-switch__track { outline: 2px solid transparent; box-shadow: var(--ring); }
205
205
  .ui-switch input:disabled + .ui-switch__track { opacity: 0.5; cursor: not-allowed; }
@@ -13,7 +13,7 @@
13
13
  display: grid;
14
14
  grid-template-columns: max-content 1fr;
15
15
  min-height: calc(100vh - var(--ui-app-top, 0px));
16
- background: var(--bg);
16
+ background: var(--bg); /* ring-gap: inherit — page background inherits root overrides. */
17
17
  color: var(--text);
18
18
  font-family: var(--font-sans);
19
19
  /* The rail's width, and the one property the fold moves: the nav's own column
@@ -39,7 +39,7 @@
39
39
  gap: var(--space-5);
40
40
  padding: var(--space-5) var(--space-4);
41
41
  border-right: var(--ui-rail-rule) solid var(--border);
42
- background: var(--surface-2);
42
+ background: var(--surface-2); --ring-gap: var(--surface-2);
43
43
  position: sticky;
44
44
  top: var(--ui-app-top, 0px);
45
45
  height: calc(100vh - var(--ui-app-top, 0px));
@@ -63,7 +63,7 @@
63
63
  hovering a row do nothing. The danger row and the current row are left out by
64
64
  name, because this rule outranks the states nav.css writes for them.
65
65
  why: CONTRIBUTING.md#a-rule-that-outranks-another-cancels-every-state-that-other-one-writes */
66
- .ui-app__rail .ui-nav__item:not(.is-danger):not(.is-active):hover { background: var(--surface-3); }
66
+ .ui-app__rail .ui-nav__item:not(.is-danger):not(.is-active):hover { background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
67
67
  .ui-app__brand {
68
68
  display: flex;
69
69
  align-items: center;
@@ -109,7 +109,7 @@
109
109
  smallest of three weak signals doing the most work, so it grows with the row. */
110
110
  /* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
111
111
  the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar —
112
- stories/apps/shell-states.test.js:1400 `a resting glyph is dimmer but still legible`. */
112
+ stories/apps/shell-states.test.js:1403 `a resting glyph is dimmer but still legible`. */
113
113
  .ui-app__rail .ui-nav__ic svg { opacity: 0.66; }
114
114
  .ui-app__rail .ui-nav__item.is-active .ui-nav__ic svg,
115
115
  .ui-app__rail .ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: currentColor; } /* motion: still — the glyph brightens with its row, whose own highlight already transitions */
@@ -153,7 +153,7 @@
153
153
  width: var(--ui-app-av); height: var(--ui-app-av); flex: none;
154
154
  margin-inline-start: calc((var(--ui-nav-strip) - var(--ui-app-av)) / 2);
155
155
  border-radius: var(--radius-pill);
156
- background: var(--surface-3);
156
+ background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
157
157
  color: var(--strong);
158
158
  display: grid; place-items: center;
159
159
  font-size: calc(var(--text-xs) + 1px);
@@ -187,7 +187,7 @@
187
187
  font-weight: inherit;
188
188
  letter-spacing: normal;
189
189
  }
190
- .ui-app .ui-app__user-trigger:hover { background: var(--surface-3); }
190
+ .ui-app .ui-app__user-trigger:hover { background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
191
191
  .ui-app__user-trigger .ui-app__who { flex: 1 1 auto; text-align: left; }
192
192
  /* The panel is portalled onto <body>, so nothing here may hang off .ui-app: by the
193
193
  time it is open it is no longer inside the rail that drew it.
@@ -269,7 +269,7 @@
269
269
  why: docs/specification.md#elevation */
270
270
  :where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
271
271
  :where(.ui-app.is-collapsed) .ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who,
272
- .ui-app__rail .ui-app__fold:is(:hover, :focus-visible) > .ui-nav__label { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--border-strong); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface-3); color: var(--strong); opacity: 1; pointer-events: none; }
272
+ .ui-app__rail .ui-app__fold:is(:hover, :focus-visible) > .ui-nav__label { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--border-strong); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); opacity: 1; pointer-events: none; }
273
273
  @supports (anchor-name: --a) and (anchor-scope: --a) {
274
274
  .ui-app.is-collapsed,
275
275
  .ui-app:has(.ui-app__fold) { anchor-scope: --ui-rail; }
@@ -318,7 +318,7 @@
318
318
  height: var(--ui-app-band);
319
319
  flex: none;
320
320
  padding-inline: clamp(24px, 4vw, 56px);
321
- background: var(--bg);
321
+ background: var(--bg); /* ring-gap: inherit — page background inherits root overrides. */
322
322
  border-bottom: 1px solid var(--border);
323
323
  /* Sticky for the reason the rail beside it is: chrome that scrolls away is chrome
324
324
  you scroll back up for. */
@@ -362,7 +362,7 @@
362
362
  Artur chose this over three alternatives on #318; the survey of what seven
363
363
  other systems do is docs/reviews/318-search-field.md.
364
364
  why: docs/specification.md#elevation */
365
- background: var(--surface);
365
+ background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
366
366
  border: 1px solid var(--border);
367
367
  border-radius: var(--radius-md);
368
368
  cursor: pointer;
@@ -373,7 +373,7 @@
373
373
  without `.ui-btn`, so base.css's shared rule never reached it and focus fell
374
374
  through to the browser's own square outline, drawn around a 12px radius.
375
375
  why: docs/specification.md#the-focus-ring */
376
- .ui-app__search:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
376
+ .ui-app__search:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-color: var(--accent); }
377
377
  /* The same glyph the palette draws in its own search box, at the same box and the
378
378
  same stroke: the trigger and the thing it opens are not two search marks.
379
379
  src/styles/command-palette.css:107 `.ui-cmdk__search-ic svg` */
@@ -467,7 +467,7 @@
467
467
  .ui-app__rail .ui-nav__item,
468
468
  .ui-app__rail .ui-app__user-trigger { min-height: 44px; }
469
469
  .ui-app__rail .ui-nav__item:is(:hover, :focus-visible) > .ui-nav__label,
470
- .ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--border-strong); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface-3); color: var(--strong); opacity: 1; pointer-events: none; }
470
+ .ui-app__rail .ui-app__user-trigger:is(:hover, :focus-visible) > .ui-app__who { position: fixed; left: calc(var(--ui-rail-w) + var(--space-2)); z-index: var(--z-dropdown); width: max-content; max-width: 15rem; margin-block: -7px; padding: 6px 10px; border: 1px solid var(--border-strong); box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop); border-radius: var(--radius-xs); background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); opacity: 1; pointer-events: none; }
471
471
  @supports (anchor-name: --a) and (anchor-scope: --a) {
472
472
  .ui-app { anchor-scope: --ui-rail; }
473
473
  .ui-app__rail { anchor-name: --ui-rail; }
@@ -501,7 +501,7 @@
501
501
  max-width: var(--panel-md);
502
502
  /* Floats over a spotlit page, so it takes the floating step and the hairline.
503
503
  why: docs/specification.md#elevation */
504
- background: var(--bg-elevated);
504
+ background: var(--bg-elevated); --ring-gap: var(--bg-elevated);
505
505
  border: 1px solid var(--border);
506
506
  border-radius: 22px;
507
507
  padding: 38px 36px 32px;
@@ -523,7 +523,7 @@
523
523
  width: 38px; height: 38px; flex: none;
524
524
  border-radius: 11px;
525
525
  display: grid; place-items: center;
526
- background: var(--glow-purple);
526
+ background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
527
527
  color: var(--accent);
528
528
  }
529
529
  .ui-scope__icon svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 1.8; }
@@ -544,7 +544,7 @@
544
544
  font-size: calc(var(--text-xs) + 1px);
545
545
  letter-spacing: 0.06em;
546
546
  color: var(--purple-mid);
547
- background: var(--glow-purple);
547
+ background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
548
548
  border-radius: 999px;
549
549
  padding: 6px 14px;
550
550
  margin-bottom: 26px;
@@ -589,7 +589,7 @@
589
589
  width: 46px; height: 46px;
590
590
  border-radius: 13px;
591
591
  display: grid; place-items: center;
592
- background: var(--glow-purple);
592
+ background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
593
593
  color: var(--accent);
594
594
  margin-bottom: 16px;
595
595
  }
@@ -99,7 +99,7 @@
99
99
  display: grid;
100
100
  place-items: center;
101
101
  /* muted-ink: glyph — permission seal */ color: var(--dim);
102
- background: color-mix(in srgb, var(--text) 7%, transparent);
102
+ background: color-mix(in srgb, var(--text) 7%, transparent); /* ring-gap: inherit — decorative paint has no nested focus target. */
103
103
  }
104
104
  .ui-denied__seal svg {
105
105
  width: 20px;
@@ -81,7 +81,7 @@
81
81
  .m-skeleton {
82
82
  position: relative;
83
83
  overflow: hidden;
84
- background: color-mix(in srgb, var(--text) 8%, transparent);
84
+ background: color-mix(in srgb, var(--text) 8%, transparent); /* ring-gap: inherit — loading placeholder has no nested focus target. */
85
85
  border-radius: var(--radius-sm, 8px);
86
86
  }
87
87
  .m-skeleton::after {
@@ -69,9 +69,9 @@
69
69
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
70
70
  }
71
71
  .ui-nav .ui-nav__item { color: var(--text); } /* (0,2,0): a host sheet's a:link is (0,1,1) — why: docs/specification.md#the-page-shell */
72
- .ui-nav__item:hover { background: var(--surface-2); color: var(--strong); }
72
+ .ui-nav__item:hover { background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ color: var(--strong); }
73
73
  .ui-nav__item.is-active {
74
- background: var(--surface-3);
74
+ background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
75
75
  color: var(--strong);
76
76
  font-weight: 500;
77
77
  }
@@ -88,8 +88,8 @@
88
88
  background: var(--accent);
89
89
  transform: translateY(-50%); /* motion: still — a mark on the current row, whose own highlight already transitions; this centres it, it does not travel */
90
90
  }
91
- .ui-nav__item:focus-visible { outline: none; box-shadow: var(--ring); }
92
- .ui-nav__item.is-active:focus-visible { box-shadow: var(--ring); }
91
+ .ui-nav__item:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
92
+ .ui-nav__item.is-active:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
93
93
 
94
94
  .ui-nav__ic { display: inline-flex; flex: none; }
95
95
  .ui-nav__ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.2; opacity: 0.72; }
@@ -108,10 +108,10 @@
108
108
  /* Destructive row (sign out, revoke). Body ink at rest, --pink once a pointer or
109
109
  the keyboard reaches it — as .ui-btn--danger and .ui-dropdown__item do. */
110
110
  .ui-nav__item.is-danger { color: var(--text); }
111
- .ui-nav__item.is-danger:hover { background: var(--glow-pink); color: var(--pink); }
111
+ .ui-nav__item.is-danger:hover { background: var(--glow-pink); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ color: var(--pink); }
112
112
  /* Tabbing onto it is the same "about to click this" the pointer means. The ring
113
113
  says where you are; only this says what the row does. */
114
- .ui-nav__item.is-danger:focus-visible { background: var(--glow-pink); color: var(--pink); }
114
+ .ui-nav__item.is-danger:focus-visible { background: var(--glow-pink); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ color: var(--pink); }
115
115
  /* An unavailable row uses disabled ink directly; opacity would also fade its
116
116
  background and pull its label toward the rail behind it (#220). */
117
117
  .ui-nav__item.is-disabled { /* muted-ink: state — unavailable control */ color: var(--disabled-ink); cursor: default; pointer-events: none; }
@@ -124,7 +124,7 @@
124
124
  rules so it cannot out-rank them — sign out is never the current row, but a
125
125
  rule that could take its wash would be one to argue with later. */
126
126
  .ui-nav__item.is-active:not(.is-danger):hover {
127
- background: color-mix(in srgb, var(--text) 10%, var(--surface-3));
127
+ background: color-mix(in srgb, var(--text) 10%, var(--surface-3)); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
128
128
  }
129
129
 
130
130
  /* Per-item badge / counter */
@@ -140,17 +140,17 @@
140
140
  font-size: var(--text-xs);
141
141
  font-weight: 600;
142
142
  line-height: 1;
143
- background: var(--surface-3);
143
+ background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
144
144
  color: var(--text);
145
145
  /* The count fades with the words. It is never lost: sidebarNav() spells it
146
146
  into the row's aria-label, so "Pending 3" is what a reader hears at either
147
147
  width. */
148
148
  transition: opacity var(--dur-fast) var(--ease);
149
149
  }
150
- .ui-nav__badge.is-accent { background: var(--glow-purple); color: var(--accent); }
151
- .ui-nav__badge.is-live { background: var(--chip-success-fill); color: var(--chip-success-ink); }
152
- .ui-nav__badge.is-danger { background: var(--glow-pink); color: var(--pink); }
153
- .ui-nav__item.is-active .ui-nav__badge.is-neutral { background: var(--surface-3); color: var(--strong); }
150
+ .ui-nav__badge.is-accent { background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ color: var(--accent); }
151
+ .ui-nav__badge.is-live { background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--chip-success-ink); }
152
+ .ui-nav__badge.is-danger { background: var(--glow-pink); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ color: var(--pink); }
153
+ .ui-nav__item.is-active .ui-nav__badge.is-neutral { background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); }
154
154
  /* Same shape, one row up: on an active row the accent counter takes the row's
155
155
  OWN surface instead of laying its wash over it. An active row is already
156
156
  painted --surface-3, so --glow-purple there is two tints stacked. Every
@@ -160,7 +160,7 @@
160
160
  it, the largest that clears being 0.06 in dark Nebula and 0.02 in light
161
161
  Emerald. So the accent wash is painted on a base surface, never a raised one.
162
162
  See #157 and docs/specification.md#colour-and-contrast. */
163
- .ui-nav__item.is-active .ui-nav__badge.is-accent { background: var(--surface-3); color: var(--accent); }
163
+ .ui-nav__item.is-active .ui-nav__badge.is-accent { background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--accent); }
164
164
 
165
165
  /* Collapsible group */
166
166
  .ui-nav__group { display: flex; flex-direction: column; }
@@ -275,7 +275,7 @@
275
275
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
276
276
  }
277
277
  .ui-nav__tab:hover { color: var(--strong); }
278
- .ui-nav__tab:focus-visible { outline: none; box-shadow: var(--ring); border-radius: 8px; }
278
+ .ui-nav__tab:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-radius: 8px; }
279
279
  .ui-nav__tab.is-active { color: var(--strong); }
280
280
 
281
281
  /* underline variant */
@@ -294,9 +294,9 @@
294
294
 
295
295
  /* pill variant */
296
296
  .ui-nav--tabs.is-pill .ui-nav__tab { border-radius: var(--radius-pill); padding: 8px 15px; }
297
- .ui-nav--tabs.is-pill .ui-nav__tab:hover { background: var(--surface-2); }
297
+ .ui-nav--tabs.is-pill .ui-nav__tab:hover { background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
298
298
  .ui-nav--tabs.is-pill .ui-nav__tab.is-active {
299
- background: var(--glow-purple);
299
+ background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
300
300
  color: var(--accent);
301
301
  }
302
302
 
@@ -329,7 +329,7 @@
329
329
  does above: a linked crumb is an <a>, and a host stylesheet's `a:link` is
330
330
  (0,1,1), which outranks a bare (0,1,0) class. */
331
331
  .ui-nav .ui-nav__crumb { color: var(--text); }
332
- a.ui-nav__crumb:hover { color: var(--strong); background: var(--surface-2); }
333
- a.ui-nav__crumb:focus-visible { outline: none; box-shadow: var(--ring); }
332
+ a.ui-nav__crumb:hover { color: var(--strong); background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
333
+ a.ui-nav__crumb:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
334
334
  .ui-nav__crumb.is-current { color: var(--strong); font-weight: 500; }
335
335
  .ui-nav__crumb .ui-nav__ic svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.5; }
@@ -69,7 +69,7 @@
69
69
  rule about an ENABLED control out of the disabled-rule sweep in
70
70
  stories/guidelines/accessibility-floor.test.js, which reads selector text. */
71
71
  .ui-pager__page.is-current:not([disabled]) {
72
- background: var(--surface-3);
72
+ background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
73
73
  border-color: var(--border);
74
74
  color: var(--strong);
75
75
  font-weight: var(--weight-semibold);
@@ -5,7 +5,7 @@
5
5
 
6
6
  .ui-seg {
7
7
  display: inline-flex;
8
- background: var(--surface-2);
8
+ background: var(--surface-2); --ring-gap: var(--surface-2);
9
9
  border-radius: var(--radius-2xl);
10
10
  padding: 3px;
11
11
  gap: 2px;
@@ -54,4 +54,4 @@
54
54
  .ui-accent-picker button.is-active {
55
55
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--swatch, var(--accent));
56
56
  }
57
- .ui-accent-picker button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--swatch, var(--accent)); }
57
+ .ui-accent-picker button:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
@@ -19,7 +19,7 @@
19
19
  position: relative;
20
20
  overflow: hidden;
21
21
  border-radius: 20px;
22
- background: var(--bg-elevated); /* the floating step carries it. #295 */
22
+ background: var(--bg-elevated); --ring-gap: var(--bg-elevated); /* Elevated surface for the confirmation and its actions. */
23
23
  border: 1px solid var(--border);
24
24
  isolation: isolate;
25
25
  }
@@ -82,7 +82,7 @@
82
82
  }
83
83
  .ui-sx--split .ui-sx__visual {
84
84
  display: grid; place-items: center; padding: 40px 24px;
85
- background: color-mix(in srgb, var(--green) 8%, transparent);
85
+ background: color-mix(in srgb, var(--green) 8%, transparent); /* ring-gap: inherit — decorative illustration has no nested focus target. */
86
86
  border-right: 1px solid var(--border);
87
87
  }
88
88
  .ui-sx--split .ui-sx__check { width: 120px; height: 120px; }
@@ -160,7 +160,7 @@
160
160
  .ui-sx--hero .ui-sx__count { justify-content: center; }
161
161
  .ui-sx__count-ring {
162
162
  flex: none; width: 16px; height: 16px; border-radius: 50%;
163
- background: conic-gradient(var(--green) var(--sx-a, 360deg), var(--surface-2) 0);
163
+ background: conic-gradient(var(--green) var(--sx-a, 360deg), var(--surface-2) 0); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
164
164
  mask: radial-gradient(circle, transparent 5px, #000 6px);
165
165
  -webkit-mask: radial-gradient(circle, transparent 5px, #000 6px);
166
166
  animation: ui-sx-sweep var(--sx-secs, 5s) linear forwards; /* motion: ambient — the caller sets the countdown; the ring just spends it */
@@ -176,7 +176,7 @@
176
176
  .ui-sx__tick { animation: none; stroke-dashoffset: 0; }
177
177
  .ui-sx__ring { display: none; }
178
178
  .ui-sx__confetti { display: none; }
179
- .ui-sx__count-ring { animation: none; background: var(--surface-2); }
179
+ .ui-sx__count-ring { animation: none; background: var(--surface-2); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
180
180
  }
181
181
 
182
182
  /* ---- Narrow — split stacks -------------------------------------------- */
@@ -42,7 +42,7 @@
42
42
  border-spacing: 0;
43
43
  padding: 0 var(--space-2);
44
44
  }
45
- .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td { background: var(--surface-2); }
45
+ .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td { background: var(--surface-2); --ring-gap: var(--surface-2); }
46
46
  .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td:first-child {
47
47
  border-top-left-radius: var(--radius-sm); border-bottom-left-radius: var(--radius-sm); }
48
48
  .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td:last-child {
@@ -63,7 +63,7 @@
63
63
 
64
64
  .ui-table--zebra th { border-bottom-width: 2px; }
65
65
  .ui-table--zebra td { border-bottom: 0; }
66
- .ui-table--zebra tbody tr:nth-child(even) td { background: var(--surface-2); }
66
+ .ui-table--zebra tbody tr:nth-child(even) td { background: var(--surface-2); --ring-gap: var(--surface-2); }
67
67
  /* The stripe stays full-bleed (the #71 inset above is hover only), but it only reads right
68
68
  * if the end cells are inset from the tint's own edge, and `.ui-table td` sets
69
69
  * `padding-left: 0`. Only `--dense` supplied that inset, so `--zebra` without `--dense` —
@@ -74,7 +74,7 @@
74
74
  .ui-table--zebra th:last-child,
75
75
  .ui-table--zebra td:last-child { padding-right: var(--space-3); }
76
76
  .ui-table--zebra.ui-table--hover tbody tr:hover td,
77
- .ui-table--zebra tbody tr:hover td { background: color-mix(in srgb, var(--accent) 8%, transparent); }
77
+ .ui-table--zebra tbody tr:hover td { --ring-gap: inherit; background: color-mix(in srgb, var(--accent) 8%, transparent); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
78
78
 
79
79
  .ui-table__num { text-align: right; font-variant-numeric: tabular-nums; }
80
80
  .ui-table th.ui-table__num { text-align: right; }
@@ -22,7 +22,7 @@
22
22
  }
23
23
  .ui-tabs__tab:hover { color: var(--strong); }
24
24
  .ui-tabs__tab.is-active { color: var(--strong); border-bottom-color: var(--accent); }
25
- .ui-tabs__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 7px 7px 0 0; }
25
+ .ui-tabs__tab:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-radius: 7px 7px 0 0; }
26
26
 
27
27
  .ui-tabs__panels { padding-top: 20px; }
28
28
  .ui-tabs__panel[hidden] { display: none; }
@@ -31,4 +31,4 @@
31
31
  .ui-tabs__panel.is-entering { animation: m-fade var(--dur-fast) var(--ease-out) both; }
32
32
  /* The panel is focusable (tabindex="0", see tabs.js) so a keyboard user can
33
33
  reach text-only content. Show the ring when they land on it. */
34
- .ui-tabs__panel:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 8px; }
34
+ .ui-tabs__panel:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); border-radius: 8px; }
@@ -25,7 +25,7 @@
25
25
  width: max-content;
26
26
  max-width: var(--prose-caption);
27
27
  padding: var(--space-2) var(--space-3);
28
- background: var(--ui-tip-bg);
28
+ background: var(--ui-tip-bg); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
29
29
  /* The two-step edge and the drop: --border-strong outside, --border one pixel
30
30
  inside it, then the broad faint drop. why: docs/specification.md#elevation */
31
31
  border: 1px solid var(--border-strong);
@@ -9,7 +9,7 @@
9
9
  height: 52px;
10
10
  display: flex;
11
11
  align-items: center;
12
- background: var(--bg);
12
+ background: var(--bg); /* ring-gap: inherit — page background inherits root overrides. */
13
13
  border-bottom: 1px solid var(--border);
14
14
  }
15
15
  .topbar__in {
@@ -48,7 +48,7 @@
48
48
  display: none;
49
49
  align-items: center;
50
50
  gap: 2px;
51
- background: var(--surface-2);
51
+ background: var(--surface-2); --ring-gap: var(--surface-2);
52
52
  border: 1px solid var(--border);
53
53
  border-radius: 24px;
54
54
  padding: 3px;
@@ -66,7 +66,7 @@
66
66
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
67
67
  }
68
68
  .dtsw a:hover { color: var(--strong); }
69
- .dtsw a.cur { color: var(--strong); background: var(--surface); }
69
+ .dtsw a.cur { color: var(--strong); background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
70
70
 
71
71
  /* Theme toggle — a single icon-only switch (no label). */
72
72
  .toggle {
@@ -75,7 +75,7 @@
75
75
  width: 34px;
76
76
  height: 34px;
77
77
  cursor: pointer;
78
- background: var(--surface);
78
+ background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
79
79
  color: var(--text);
80
80
  border: 1px solid var(--border);
81
81
  border-radius: 10px;
@@ -100,7 +100,7 @@
100
100
  font-size: calc(var(--text-sm) - 0.5px);
101
101
  font-weight: 500;
102
102
  color: var(--text);
103
- background: var(--surface);
103
+ background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
104
104
  border: 1px solid var(--border);
105
105
  border-radius: 24px;
106
106
  padding: 7px 11px 7px 13px;
@@ -122,7 +122,7 @@
122
122
  top: calc(100% + 9px);
123
123
  left: 0;
124
124
  min-width: 266px;
125
- background: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
125
+ background: var(--bg-elevated); --ring-gap: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
126
126
  /* The two-step edge and the drop: --border-strong outside, --border one pixel
127
127
  inside it, then the broad faint drop. why: docs/specification.md#elevation */
128
128
  border: 1px solid var(--border-strong);
@@ -182,7 +182,7 @@
182
182
  /* The menu is raised, so a hovered row takes the step ABOVE it — see
183
183
  src/styles/dropdown.css:155 `.ui-dropdown__item:hover`.
184
184
  why: docs/specification.md#elevation */
185
- .vopt:hover { background: var(--surface-3); }
185
+ .vopt:hover { background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
186
186
  .vopt .vname { font-family: var(--font-sans); font-size: calc(var(--text-sm) - 0.5px); color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
187
187
  .vopt .vmeta { font-size: var(--text-xs); color: var(--text); margin-top: 2px; }
188
188
  .vopt .vbadge {
@@ -195,8 +195,8 @@
195
195
  flex: none;
196
196
  margin-top: 1px;
197
197
  }
198
- .vbadge.live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
199
- .vbadge.arch { /* muted-ink: state — archived version */ color: var(--muted); background: var(--surface-3); }
198
+ .vbadge.live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
199
+ .vbadge.arch { /* muted-ink: state — archived version */ color: var(--muted); background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
200
200
  /* The switcher keeps its glyph and drops its label where the shell folds — the
201
201
  same step the rail folds at: docs/specification.md#breakpoints. */
202
202
  @media (max-width: 720px) { .vsw__btn .lbl { display: none; } }
@@ -225,7 +225,7 @@
225
225
  position: absolute;
226
226
  right: 0; top: 44px;
227
227
  width: 252px;
228
- background: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
228
+ background: var(--bg-elevated); --ring-gap: var(--bg-elevated); /* the floating step — see dropdown.css. #295 */
229
229
  /* The two-step edge and the drop: --border-strong outside, --border one pixel
230
230
  inside it, then the broad faint drop. why: docs/specification.md#elevation */
231
231
  border: 1px solid var(--border-strong);
@@ -258,7 +258,7 @@
258
258
  .amenu .ahead .anm { color: var(--strong); font-size: var(--text-sm); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
259
259
  .amenu .ahead .aem { color: var(--text); font-size: calc(var(--text-xs) + 0.5px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
260
260
  .amenu a { display: flex; align-items: center; gap: 11px; padding: 10px 15px; color: var(--text); text-decoration: none; font-family: var(--font-sans); font-size: var(--text-sm); cursor: pointer; }
261
- .amenu a:hover { background: var(--surface-3); }
261
+ .amenu a:hover { background: var(--surface-3); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
262
262
  .amenu a.cur { color: var(--accent); }
263
263
  .amenu a svg { width: 19px; height: 19px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; opacity: 0.85; }
264
264
  .amenu .asep { height: 1px; background: var(--border); margin: 5px 0; }
@@ -172,7 +172,7 @@
172
172
  --disabled-ink-bare: #a39eb7;
173
173
 
174
174
  /* Lifted from #9b5dff, which cleared the flat surfaces and failed on its own wash over
175
- a card; the value was already in the ramp. --ring is var(--accent) and follows by
175
+ a card; the value was already in the ramp. --ring-color is var(--accent) and follows by
176
176
  reference. Held by stories/accent-contrast.test.js. #157.
177
177
  why: docs/specification.md#colour-and-contrast */
178
178
  --accent: #b479ff;
@@ -229,12 +229,12 @@
229
229
  --shadow-lg: 0 0 #0000;
230
230
  --shadow-seg: 0 0 #0000;
231
231
  --shadow-card: 0 0 #0000;
232
- /* THE ONLY --ring in the kit — light and every data-accent sub-theme declare an
233
- --accent and inherit this line. Opaque because a translucent ring is a glow, and the
234
- eight rgba() literals it replaced all missed WCAG 1.4.11's 3:1 (#218). Held by
235
- stories/guidelines/accessibility-floor.test.js.
236
- why: docs/specification.md#the-focus-ring */
237
- --ring: 0 0 0 3px var(--accent);
232
+ /* G2: the band carries contrast; the halo decorates it. why: docs/specification.md#the-focus-ring */
233
+ --ring-width: 2px;
234
+ --ring-color: var(--accent);
235
+ --ring-gap-width: 1px;
236
+ --ring-gap: var(--bg);
237
+ --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);
238
238
 
239
239
  /* The ink a cast shadow is made of, and the specular white that reads as gloss on a
240
240
  raised surface. Effects mix these instead of writing rgba(). */
@@ -405,3 +405,45 @@
405
405
  --prose-body: 62ch;
406
406
  --prose-dense: 72ch;
407
407
  }
408
+
409
+ /* Recompose only at painted containers; controls inherit the outside gap.
410
+ * One recipe keeps surface propagation cheap and consistent.
411
+ * why: docs/specification.md#the-focus-ring */
412
+ .amenu,
413
+ .dtsw,
414
+ .rx-modal,
415
+ .ui-app__rail,
416
+ .ui-auth__card,
417
+ .ui-callout,
418
+ .ui-card,
419
+ .ui-card--accent,
420
+ .ui-card--live,
421
+ .ui-cmdk__panel,
422
+ .ui-code,
423
+ .ui-confirm__panel,
424
+ .ui-drawer__footer,
425
+ .ui-drawer__panel,
426
+ .ui-dropdown__panel,
427
+ .ui-fbcomposer,
428
+ .ui-footer--app,
429
+ .ui-seg,
430
+ .ui-snippet,
431
+ .ui-snippet--reveal,
432
+ .ui-sx,
433
+ .ui-table--hover:not(.ui-table--zebra) tbody tr:hover td,
434
+ .ui-table--zebra tbody tr:nth-child(even) td,
435
+ .ui-table--zebra tbody tr:hover td,
436
+ .ui-table--zebra.ui-table--hover tbody tr:hover td,
437
+ .ui-toast--outline,
438
+ .ui-toast--solid,
439
+ .vsw__menu {
440
+ --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);
441
+ }
442
+
443
+ /* A container focused itself paints outside its own surface; descendants only
444
+ * inherit its inside gap again after focus moves to them. */
445
+ .ui-card:focus-visible,
446
+ .ui-cmdk__panel:focus-visible,
447
+ .ui-confirm__panel:focus-visible,
448
+ .ui-drawer__panel:focus-visible,
449
+ .rx-modal:focus-visible { --ring-gap: inherit; }