@lyeve-labs/ui-kit 0.45.0 → 0.46.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.
@@ -40,6 +40,15 @@ export const FIELD_LABEL = 'text-sm font-medium text-fg';
40
40
  export const FIELD_HINT = 'text-xs text-faint';
41
41
  /** Error text below a control. Replaces the hint rather than stacking with it. */
42
42
  export const FIELD_ERROR = 'text-xs text-danger';
43
+ /**
44
+ * The depth every field shares: an inner shadow along the top edge that sinks
45
+ * it into the surface, and the cyan glow around the focus ring. Both are
46
+ * shadows, so the field keeps its height and its border, and the ring stays
47
+ * the indicator that clears 3:1. The glow appears with the ring rather than
48
+ * easing in, because a focus indicator that fades is late for the key press
49
+ * that moved it.
50
+ */
51
+ const CONTROL_DEPTH = 'inset-shadow-well focus-visible:shadow-glow';
43
52
  /**
44
53
  * Everything a single-line control needs except its border color.
45
54
  *
@@ -58,11 +67,13 @@ export const FIELD_ERROR = 'text-xs text-danger';
58
67
  */
59
68
  export const CONTROL_BASE = 'w-full h-control rounded-lg bg-surface-2 border px-3 text-sm text-fg ' +
60
69
  'placeholder:text-faint transition-colors outline-none ' +
70
+ `${CONTROL_DEPTH} ` +
61
71
  'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand ' +
62
72
  'disabled:opacity-50 disabled:cursor-not-allowed';
63
73
  /** As CONTROL_BASE, for controls whose height is driven by their content. */
64
74
  export const CONTROL_MULTILINE = 'w-full min-h-control rounded-lg bg-surface-2 border px-3 py-2 text-sm text-fg ' +
65
75
  'placeholder:text-faint transition-colors outline-none ' +
76
+ `${CONTROL_DEPTH} ` +
66
77
  'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand ' +
67
78
  'disabled:opacity-50 disabled:cursor-not-allowed';
68
79
  /**
@@ -111,7 +122,7 @@ export function describedBy(id, error, hint) {
111
122
  * held the caret.
112
123
  */
113
124
  export const CONTROL_SEGMENTED = 'inline-flex h-control items-center gap-0.5 rounded-lg bg-surface-2 border px-3 ' +
114
- 'text-sm text-fg transition-colors';
125
+ 'text-sm text-fg transition-colors inset-shadow-well has-[:focus-visible]:shadow-glow';
115
126
  /** As controlBorder, for a wrapper that is only ever focused through a child. */
116
127
  export function segmentedBorder(error) {
117
128
  return error
@@ -91,8 +91,15 @@ export declare const PAGE_STACK = "flex flex-col gap-section";
91
91
  * children flush to the border. `overflow-hidden` is deliberately absent: the
92
92
  * focus ring sits 2px outside the element it belongs to, so a clipping surface
93
93
  * crops the ring of every button inside it down to whichever edge fits.
94
+ *
95
+ * The depth is a few percent of light at the top, a 1px light along the top
96
+ * edge and a soft drop under the card. The light is a translucent background
97
+ * image laid over the background color, so a caller who passes its own bg-*
98
+ * keeps that color under it. The rest are shadows, so the card is the same
99
+ * size with or without them and the 1px border stays the boundary a forced
100
+ * colors scheme keeps.
94
101
  */
95
- export declare const CARD_SURFACE = "bg-surface border border-line rounded-xl";
102
+ export declare const CARD_SURFACE = "bg-surface bg-card border border-line rounded-card shadow-card inset-shadow-highlight";
96
103
  /**
97
104
  * The card surface in the danger tone, for a group of destructive actions.
98
105
  *
@@ -101,7 +108,7 @@ export declare const CARD_SURFACE = "bg-surface border border-line rounded-xl";
101
108
  * percent keeps the controls of a form inside it on a ground they were drawn
102
109
  * for. The contrast suite reads the share from this string.
103
110
  */
104
- export declare const PANEL_DANGER_SURFACE = "bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-xl";
111
+ export declare const PANEL_DANGER_SURFACE = "bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-card shadow-card inset-shadow-highlight";
105
112
  /**
106
113
  * Card padding by name.
107
114
  *
@@ -181,8 +188,20 @@ export declare function sectionHeading(level: 2 | 3, variant?: SectionVariant):
181
188
  *
182
189
  * `border-e` and not `border-r`: the shell is the one place a right-to-left
183
190
  * locale flips, and a physical border leaves the rule on the wrong edge.
191
+ *
192
+ * The sidebar runs from the surface at the top a step toward ink at the foot,
193
+ * and its edge is a rule that fades in and out at its ends, drawn over a
194
+ * transparent border. The border keeps the column's width, and a forced
195
+ * colors scheme, which draws no gradient, paints that border in the system
196
+ * text color, so the edge is still there.
184
197
  */
185
- export declare const APP_SIDEBAR = "h-full shrink-0 flex-col border-e border-line bg-surface transition-[width] duration-base ease-move";
198
+ export declare const APP_SIDEBAR: string;
199
+ /**
200
+ * The faded edge of the sidebar, a 1px line laid over its transparent
201
+ * border. It sets a width of its own, so it stays out of APP_SIDEBAR, which
202
+ * holds no width at all.
203
+ */
204
+ export declare const APP_SIDEBAR_EDGE = "after:pointer-events-none after:absolute after:inset-y-0 after:-end-px after:w-px after:bg-rule-y";
186
205
  /**
187
206
  * The sidebar's two widths. Expanded is the 224px column every authed screen
188
207
  * had. The rail is the 56px icon column the theme had named and nothing used:
@@ -201,8 +220,14 @@ export declare const APP_SIDEBAR_RAIL = "w-nav-rail";
201
220
  /**
202
221
  * The header bar. Its gutter is the page gutter, so the section name in the
203
222
  * header sits directly above the page title under it rather than 8px off.
223
+ *
224
+ * The surface is translucent, so the light at the top of the canvas carries
225
+ * up under it and the header reads as part of the page rather than a lid on
226
+ * it. Its rule fades out toward the end edge and stays solid where it meets
227
+ * the brand row's, so the two read as one line across the top. The rule sits
228
+ * over a transparent border, for the same reason as the sidebar's.
204
229
  */
205
- export declare const APP_HEADER = "flex h-header shrink-0 items-center gap-2 border-b border-line bg-surface px-page-x";
230
+ export declare const APP_HEADER: string;
206
231
  /**
207
232
  * The sidebar's brand row. Exactly as tall as the header beside it: the two
208
233
  * meet at the top left corner and a 4px disagreement there reads as a seam.
@@ -102,8 +102,15 @@ export const PAGE_STACK = 'flex flex-col gap-section';
102
102
  * children flush to the border. `overflow-hidden` is deliberately absent: the
103
103
  * focus ring sits 2px outside the element it belongs to, so a clipping surface
104
104
  * crops the ring of every button inside it down to whichever edge fits.
105
+ *
106
+ * The depth is a few percent of light at the top, a 1px light along the top
107
+ * edge and a soft drop under the card. The light is a translucent background
108
+ * image laid over the background color, so a caller who passes its own bg-*
109
+ * keeps that color under it. The rest are shadows, so the card is the same
110
+ * size with or without them and the 1px border stays the boundary a forced
111
+ * colors scheme keeps.
105
112
  */
106
- export const CARD_SURFACE = 'bg-surface border border-line rounded-xl';
113
+ export const CARD_SURFACE = 'bg-surface bg-card border border-line rounded-card shadow-card inset-shadow-highlight';
107
114
  /**
108
115
  * The card surface in the danger tone, for a group of destructive actions.
109
116
  *
@@ -112,7 +119,7 @@ export const CARD_SURFACE = 'bg-surface border border-line rounded-xl';
112
119
  * percent keeps the controls of a form inside it on a ground they were drawn
113
120
  * for. The contrast suite reads the share from this string.
114
121
  */
115
- export const PANEL_DANGER_SURFACE = 'bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-xl';
122
+ export const PANEL_DANGER_SURFACE = 'bg-[color-mix(in_srgb,var(--color-danger)_5%,var(--color-surface))] border border-danger/40 rounded-card shadow-card inset-shadow-highlight';
116
123
  /**
117
124
  * Card padding by name.
118
125
  *
@@ -188,8 +195,12 @@ export const MODAL_PAD = 'px-card py-card-sm';
188
195
  export function sectionHeading(level, variant = 'default') {
189
196
  // The eyebrow reads the same at both levels. It is a label for the band under
190
197
  // it rather than a title, so it does not take the level's size at all.
198
+ //
199
+ // It is muted and semibold rather than faint and medium. A band label in the
200
+ // faintest gray read as metadata beside the stronger page title, so it
201
+ // stepped up once in weight and once in color.
191
202
  if (variant === 'eyebrow')
192
- return 'text-xs font-medium uppercase tracking-wide text-faint';
203
+ return 'text-xs font-semibold uppercase tracking-wider text-muted';
193
204
  // Level 2 is the brand ramp's H3, which the guideline describes as the card
194
205
  // and group head this is. Its leading and tracking come with it: a --text-*
195
206
  // token sets font-size and nothing else, so the ratio has to be asked for by
@@ -215,8 +226,21 @@ export function sectionHeading(level, variant = 'default') {
215
226
  *
216
227
  * `border-e` and not `border-r`: the shell is the one place a right-to-left
217
228
  * locale flips, and a physical border leaves the rule on the wrong edge.
229
+ *
230
+ * The sidebar runs from the surface at the top a step toward ink at the foot,
231
+ * and its edge is a rule that fades in and out at its ends, drawn over a
232
+ * transparent border. The border keeps the column's width, and a forced
233
+ * colors scheme, which draws no gradient, paints that border in the system
234
+ * text color, so the edge is still there.
218
235
  */
219
- export const APP_SIDEBAR = 'h-full shrink-0 flex-col border-e border-line bg-surface transition-[width] duration-base ease-move';
236
+ export const APP_SIDEBAR = 'relative h-full shrink-0 flex-col border-e border-transparent bg-surface bg-sidebar ' +
237
+ 'transition-[width] duration-base ease-move';
238
+ /**
239
+ * The faded edge of the sidebar, a 1px line laid over its transparent
240
+ * border. It sets a width of its own, so it stays out of APP_SIDEBAR, which
241
+ * holds no width at all.
242
+ */
243
+ export const APP_SIDEBAR_EDGE = 'after:pointer-events-none after:absolute after:inset-y-0 after:-end-px after:w-px after:bg-rule-y';
220
244
  /**
221
245
  * The sidebar's two widths. Expanded is the 224px column every authed screen
222
246
  * had. The rail is the 56px icon column the theme had named and nothing used:
@@ -235,8 +259,16 @@ export const APP_SIDEBAR_RAIL = 'w-nav-rail';
235
259
  /**
236
260
  * The header bar. Its gutter is the page gutter, so the section name in the
237
261
  * header sits directly above the page title under it rather than 8px off.
262
+ *
263
+ * The surface is translucent, so the light at the top of the canvas carries
264
+ * up under it and the header reads as part of the page rather than a lid on
265
+ * it. Its rule fades out toward the end edge and stays solid where it meets
266
+ * the brand row's, so the two read as one line across the top. The rule sits
267
+ * over a transparent border, for the same reason as the sidebar's.
238
268
  */
239
- export const APP_HEADER = 'flex h-header shrink-0 items-center gap-2 border-b border-line bg-surface px-page-x';
269
+ export const APP_HEADER = 'relative flex h-header shrink-0 items-center gap-2 border-b border-transparent bg-surface/70 px-page-x ' +
270
+ 'after:pointer-events-none after:absolute after:inset-x-0 after:-bottom-px after:h-px after:bg-rule-end ' +
271
+ 'rtl:after:-scale-x-100';
240
272
  /**
241
273
  * The sidebar's brand row. Exactly as tall as the header beside it: the two
242
274
  * meet at the top left corner and a 4px disagreement there reads as a seam.
@@ -52,8 +52,11 @@
52
52
  *
53
53
  * No vertical offset and no side. Both belong to placePanel, which measures
54
54
  * the room and puts the panel above the anchor when there is none below.
55
+ *
56
+ * shadow-popover is the rung above a card, so a menu opened from a card reads
57
+ * as lifted off it rather than as part of it.
55
58
  */
56
- export declare const PANEL_SURFACE = "absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-2xl";
59
+ export declare const PANEL_SURFACE = "absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-popover inset-shadow-highlight";
57
60
  /**
58
61
  * The scrolling region inside it.
59
62
  *
@@ -53,8 +53,11 @@ import { TOUCH_GROW } from './touch.js';
53
53
  *
54
54
  * No vertical offset and no side. Both belong to placePanel, which measures
55
55
  * the room and puts the panel above the anchor when there is none below.
56
+ *
57
+ * shadow-popover is the rung above a card, so a menu opened from a card reads
58
+ * as lifted off it rather than as part of it.
56
59
  */
57
- export const PANEL_SURFACE = 'absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-2xl';
60
+ export const PANEL_SURFACE = 'absolute z-dropdown overflow-hidden rounded-xl border border-line-strong bg-surface text-fg shadow-popover inset-shadow-highlight';
58
61
  /**
59
62
  * The scrolling region inside it.
60
63
  *
@@ -91,6 +91,124 @@
91
91
  --radius-lg: 12px;
92
92
  --radius-xl: 16px;
93
93
  --radius-2xl: 24px;
94
+ /* A role rather than a rung: the card, the table frame, the stat, the empty
95
+ * state and the dialog share it, so the surfaces that sit side by side on a
96
+ * page agree. One step past xl. The 2xl rung read as a toy at console
97
+ * density, where a card holds a 14px table rather than a hero. */
98
+ --radius-card: 20px;
99
+
100
+ /* -- Depth: what lifts a surface off the page ------------------- *
101
+ * Three colors and a short ladder of shadows built from them. The
102
+ * geometry is shared and the colors invert with the theme, so the
103
+ * light palette gets the same layering without a second ladder:
104
+ * in dark a surface is lit from above by a faint white line along
105
+ * its top edge, and in light the white line disappears into the
106
+ * white card and the hairline under it does the separating.
107
+ *
108
+ * highlight the 1px line along a raised surface's top edge
109
+ * shade every drop shadow, and the inner shadow of a well
110
+ * glow the cyan halo around focus and the primary action
111
+ *
112
+ * Each is a shadow or a background image and never a border, so no
113
+ * surface changes size when it gains or loses one, and a forced
114
+ * colors scheme drops all of them without losing a boundary: every
115
+ * surface keeps its 1px border. */
116
+ --color-highlight: rgb(255 255 255 / 0.12);
117
+ --color-shade: rgb(2 6 14 / 0.5);
118
+ --color-glow: rgb(0 212 255 / 0.16);
119
+ /* The pool of light under the primary button, its own fill color. A token
120
+ * of its own so paper and a forced colors scheme can switch it off without
121
+ * touching the brand, which print turns black. */
122
+ --color-halo: var(--color-brand);
123
+ /* The place you are: a table's head row, a hovered row and the current nav
124
+ * row. Brand mixed into the surface rather than laid over it, so it is one
125
+ * opaque color the contrast suite can measure. Five percent, because a
126
+ * violet badge in a hovered row lays its own tint over this one, and at
127
+ * eight its label fell to 4.35:1. */
128
+ --color-tint: color-mix(in srgb, var(--color-brand) 5%, var(--color-surface));
129
+
130
+ /* The ramp, from a control to a dialog. Each rung casts further than the
131
+ * one below it, so a menu reads above the card it opened from and a dialog
132
+ * above both. A raised control sits closest. */
133
+ --shadow-raised: 0 1px 2px 0 var(--color-shade);
134
+ --shadow-card: 0 1px 2px 0 var(--color-shade), 0 8px 24px -12px var(--color-shade);
135
+ --shadow-popover: 0 4px 8px -2px var(--color-shade), 0 16px 32px -8px var(--color-shade);
136
+ --shadow-overlay: 0 12px 24px -6px var(--color-shade), 0 32px 64px -16px var(--color-shade);
137
+ /* A tight ring that fills the 2px gap between a control and its focus
138
+ * outline, and a soft halo past it. On a field it sits outside the border. */
139
+ --shadow-glow: 0 0 0 4px var(--color-glow), 0 0 20px 2px var(--color-glow);
140
+ /* The primary button glows at rest, a pool of its own color under it, and
141
+ * the pool grows under the pointer. Brand at a share small enough that the
142
+ * fill still clears 3:1 against the brightest part of its own halo. */
143
+ --shadow-primary:
144
+ 0 1px 2px 0 var(--color-shade), 0 6px 18px -6px color-mix(in srgb, var(--color-halo) 30%, transparent);
145
+ --shadow-primary-hover:
146
+ 0 1px 2px 0 var(--color-shade), 0 0 0 3px var(--color-glow),
147
+ 0 10px 28px -6px color-mix(in srgb, var(--color-halo) 40%, transparent);
148
+ /* A few pixels of light around a mark, such as the current nav row's bar. */
149
+ --shadow-mark: 0 0 8px 1px var(--color-glow);
150
+
151
+ /* Inner shadows compose with the drop shadows above, because Tailwind keeps
152
+ * them in a variable of their own: a card is shadow-card and
153
+ * inset-shadow-highlight together. highlight is a 1px line with no blur,
154
+ * so it lights the top edge and never sits under a line of text. bevel is
155
+ * the brighter edge a filled
156
+ * button carries on its own color, so it does not change with the theme.
157
+ * well sinks a field into the surface it sits on, at half the shade, so the
158
+ * field's border still clears 3:1 against the darkest band inside it. */
159
+ --inset-shadow-highlight: inset 0 1px 0 0 var(--color-highlight);
160
+ --inset-shadow-bevel: inset 0 1px 0 0 rgb(255 255 255 / 0.22);
161
+ --inset-shadow-well: inset 0 1px 2px 0 color-mix(in srgb, var(--color-shade) 50%, transparent);
162
+
163
+ /* Two gradients and no more. hero is the wash behind a hero or an empty
164
+ * state: a tenth of violet mixed into the page's own ink, lit from the top
165
+ * center and falling off to plain ink, so text on it is measured against
166
+ * both ends. accent is cyan into violet for a mark a few
167
+ * pixels wide: the active tab and nav indicator and a progress fill. It never
168
+ * sits behind text. accent-y is the same mark standing upright. */
169
+ --background-image-hero: radial-gradient(
170
+ ellipse 90% 80% at 50% 0%,
171
+ color-mix(in srgb, var(--color-violet) 10%, var(--color-ink)) 0%,
172
+ var(--color-ink) 100%
173
+ );
174
+ --background-image-accent: linear-gradient(90deg, var(--color-brand), var(--color-violet));
175
+ --background-image-accent-y: linear-gradient(180deg, var(--color-brand), var(--color-violet));
176
+
177
+ /* The surfaces' own light, each redeclared for the light palette.
178
+ *
179
+ * canvas cyan and violet pooled at the top of the app shell's content,
180
+ * gone within 400px, behind the page header
181
+ * sidebar the surface at the top, a step toward ink at the foot
182
+ * card a few percent of light at a card's top, fading to nothing,
183
+ * laid over the card's own background color so a caller's
184
+ * bg-* still shows through it
185
+ * lift white falling off down a filled button, over its own color, so
186
+ * the hover and held colors still show through it
187
+ * rule-y a vertical rule that fades in and out at its ends
188
+ * rule-end a horizontal rule that fades out toward its end edge
189
+ *
190
+ * Every one of them has text on it or beside it, so the contrast suite
191
+ * measures each text token at each end. The canvas pools are as strong as
192
+ * they can be while brand-deep and faint still clear 4.5:1 where the two
193
+ * overlap. */
194
+ --background-image-canvas:
195
+ radial-gradient(70% 400px at 15% 0, color-mix(in srgb, var(--color-brand) 6%, transparent), transparent),
196
+ radial-gradient(60% 400px at 85% 0, color-mix(in srgb, var(--color-violet) 7%, transparent), transparent);
197
+ --background-image-sidebar: linear-gradient(
198
+ 180deg,
199
+ var(--color-surface),
200
+ color-mix(in srgb, var(--color-ink) 60%, var(--color-surface))
201
+ );
202
+ --background-image-card: linear-gradient(180deg, rgb(255 255 255 / 0.04), transparent);
203
+ --background-image-lift: linear-gradient(180deg, rgb(255 255 255 / 0.18), transparent 70%);
204
+ --background-image-rule-y: linear-gradient(
205
+ 180deg,
206
+ transparent,
207
+ var(--color-line) 15%,
208
+ var(--color-line) 85%,
209
+ transparent
210
+ );
211
+ --background-image-rule-end: linear-gradient(90deg, var(--color-line) 50%, transparent);
94
212
 
95
213
  /* -- Elevation: the stacking order of everything that floats --- *
96
214
  * Every floating surface in the kit carried a bare z-50, so which
@@ -217,6 +335,10 @@
217
335
  --leading-body: 1.55;
218
336
  --leading-caption: 1.5;
219
337
  --leading-mono: 1.5;
338
+ /* The console's page title: 32px on a 38px line. H2's 1.15 is drawn for a
339
+ * marketing headline set in a column, and at 32px over a 14px page it sat
340
+ * the description too close under the title. */
341
+ --leading-title: 1.1875;
220
342
 
221
343
  --tracking-display: -0.04em;
222
344
  --tracking-h1: -0.025em;
@@ -424,6 +546,28 @@ html[data-theme='light'] {
424
546
  --color-success: #126c34;
425
547
  --color-warn: #904207;
426
548
  --color-danger: #ab1a1a;
549
+
550
+ /* Depth translated rather than copied. A white top line is invisible on a
551
+ * white card, so the highlight only brightens surface-2, and the shade is a
552
+ * cool gray at a sixth of the dark one's weight: in light a shadow
553
+ * separates, it does not darken. The glow is the mark's #0099c4 face, since
554
+ * the deep brand reads as a gray halo on white. */
555
+ --color-highlight: rgb(255 255 255 / 0.8);
556
+ --color-shade: rgb(16 24 40 / 0.08);
557
+ --color-glow: rgb(0 153 196 / 0.22);
558
+
559
+ /* A cool tint at the top of the page rather than a glow: on white, light
560
+ * cannot be added, so the wash darkens a little toward the brand. The card's
561
+ * layer darkens toward the page's ink at the foot, the same lit top,
562
+ * reached from the other side. */
563
+ --background-image-canvas:
564
+ radial-gradient(70% 400px at 15% 0, color-mix(in srgb, var(--color-brand) 6%, transparent), transparent),
565
+ radial-gradient(60% 400px at 85% 0, color-mix(in srgb, var(--color-violet) 5%, transparent), transparent);
566
+ --background-image-card: linear-gradient(
567
+ 180deg,
568
+ transparent,
569
+ color-mix(in srgb, var(--color-ink) 70%, transparent)
570
+ );
427
571
  }
428
572
 
429
573
  /*
@@ -437,6 +581,38 @@ html[data-theme='light'] {
437
581
  --color-mark-ink: CanvasText;
438
582
  --color-mark-foot: GrayText;
439
583
  --color-mark-step: GrayText;
584
+ /* The browser drops box shadows and gradients here already. Naming it
585
+ * keeps a browser that does not from painting the cyan halo or a wash
586
+ * that no longer matches the forced canvas. */
587
+ --color-glow: transparent;
588
+ --color-halo: transparent;
589
+ --background-image-hero: none;
590
+ --background-image-accent: none;
591
+ --background-image-accent-y: none;
592
+ --background-image-canvas: none;
593
+ --background-image-sidebar: none;
594
+ --background-image-card: none;
595
+ --background-image-lift: none;
596
+ --background-image-rule-y: none;
597
+ --background-image-rule-end: none;
598
+ }
599
+
600
+ /*
601
+ * A field, a menu row and a nav row show focus with a ring, and a ring is a
602
+ * box shadow, which a forced colors scheme removes. The reader was left with
603
+ * no focus indicator on exactly the controls typed into. An outline in the
604
+ * system highlight takes its place. Unlayered, so it wins over the
605
+ * outline-none those controls carry. A control that draws its ring inset
606
+ * does so because a scrolling box would clip anything outside it, so its
607
+ * outline sits inside too.
608
+ */
609
+ :focus-visible {
610
+ outline: 2px solid Highlight;
611
+ outline-offset: 2px;
612
+ }
613
+
614
+ [class*='focus-visible:ring-inset']:focus-visible {
615
+ outline-offset: -2px;
440
616
  }
441
617
  }
442
618
 
@@ -512,6 +688,18 @@ html[data-theme='light'] {
512
688
  outline-offset: 2px;
513
689
  }
514
690
 
691
+ /*
692
+ * The glow around the ring. It is a shadow, so the ring above stays the
693
+ * indicator that clears 3:1 and the glow only adds to it. Any box shadow a
694
+ * component states wins over this one, which is why a raised button and a
695
+ * field ask for the glow by name as well. A surface focused only from script
696
+ * (tabindex -1: a dialog, the main region after the skip link) is not a
697
+ * control and takes no halo.
698
+ */
699
+ :focus-visible:not([tabindex='-1']) {
700
+ box-shadow: var(--shadow-glow);
701
+ }
702
+
515
703
  :focus:not(:focus-visible) {
516
704
  outline: none;
517
705
  }
@@ -614,6 +802,21 @@ html[data-theme='light'] {
614
802
  --color-success: #000000;
615
803
  --color-warn: #000000;
616
804
  --color-danger: #000000;
805
+ /* Paper has no light source, and a printer renders a soft shadow as a gray
806
+ * smear around every card. */
807
+ --color-highlight: transparent;
808
+ --color-shade: transparent;
809
+ --color-glow: transparent;
810
+ --color-halo: transparent;
811
+ --background-image-hero: none;
812
+ --background-image-accent: none;
813
+ --background-image-accent-y: none;
814
+ --background-image-canvas: none;
815
+ --background-image-sidebar: none;
816
+ --background-image-card: none;
817
+ --background-image-lift: none;
818
+ --background-image-rule-y: none;
819
+ --background-image-rule-end: none;
617
820
  color-scheme: light;
618
821
  }
619
822
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyeve-labs/ui-kit",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "A clean, accessible, palette-aware Svelte 5 component library. The design system behind LyEve.",
5
5
  "license": "MIT",
6
6
  "author": "LyEve Labs <hello@lyeve.com>",