@webority/theme 0.37.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,22 +1,20 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "Webority shared design system — Bootstrap 5.3 SCSS overrides, --wui-* tokens, and shared component CSS. The single source of brand chrome for React portals and Razor sites.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {
7
7
  "access": "public",
8
8
  "registry": "https://registry.npmjs.org/"
9
9
  },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/Webority/webority-ui.git",
13
- "directory": "packages/theme"
14
- },
15
10
  "files": [
16
11
  "index.scss",
17
12
  "scss",
18
13
  "fonts",
19
- "dist"
14
+ "dist",
15
+ "!dist/**/__tests__",
16
+ "!dist/**/*.test.*",
17
+ "!dist/**/*.map"
20
18
  ],
21
19
  "exports": {
22
20
  ".": {
@@ -82,7 +82,7 @@
82
82
 
83
83
  .btn-danger {
84
84
  // White-on-red destructive look via the --wui-danger-strong token: white fails AA on the
85
- // brighter danger fills (#F8285A → 3.86:1, #E82646 → 4.38:1), so the button uses the
85
+ // brighter danger fill (#E82646 → 4.38:1), so the button uses the
86
86
  // deepened fill (5.80:1) that carries white cleanly. Token-driven so a rebrand tracks it;
87
87
  // the brighter --wui-danger stays for badges/dots that never carry white text.
88
88
  --bs-btn-color: #fff;
package/scss/_tokens.scss CHANGED
@@ -148,16 +148,16 @@
148
148
  // Shared by the portal (aliased to --color-*) and marketing alerts/badges.
149
149
  --wui-info-text: #1460ba;
150
150
  --wui-info-active: #056EE9;
151
- --wui-info: #1B84FF;
151
+ --wui-info: #056EE9;
152
152
  --wui-info-50: #8DC2FF;
153
153
  --wui-info-30: #BBDBFF;
154
154
  --wui-info-20: #D1E6FF;
155
155
  --wui-info-10: #E9F3FF;
156
156
  --wui-info-5: #F3F9FF;
157
157
 
158
- // `-text` variants exist because the status colours are FILLS. #F8285A and #04B440
158
+ // `-text` variants exist because the status colours are FILLS. #E82646 and #04B440
159
159
  // are correct on a button or a badge, and both fail AA as text on a light surface
160
- // (3.6:1 and 2.6:1) — which is how a validation error message, of all things, came
160
+ // (4.4:1 and 2.8:1) — which is how a validation error message, of all things, came
161
161
  // to be unreadable. Darkening the base tokens would change every red button and
162
162
  // green badge in the system, so text gets its own value and the fills keep theirs.
163
163
  --wui-success-text: #0e7531;
@@ -167,7 +167,7 @@
167
167
  // the other tokens so buttons/badges keep the vibrant fill.
168
168
  --wui-success-strong: #0e7531;
169
169
  --wui-success-active: #04B440;
170
- --wui-success: #17C653;
170
+ --wui-success: #04B440;
171
171
  --wui-success-50: #8BE3A9;
172
172
  --wui-success-30: #BAEECC;
173
173
  --wui-success-20: #D1F4DD;
@@ -176,7 +176,7 @@
176
176
 
177
177
  --wui-warning-text: #876100;
178
178
  --wui-warning-active: #DFA000;
179
- --wui-warning: #F6B100;
179
+ --wui-warning: #DFA000;
180
180
  --wui-warning-50: #FBD880;
181
181
  --wui-warning-30: #FDE8B3;
182
182
  --wui-warning-20: #FDEFCC;
@@ -196,7 +196,7 @@
196
196
  --wui-danger-strong: #C81340;
197
197
  --wui-danger-strong-hover: #AB1036;
198
198
  --wui-danger-active: #E82646;
199
- --wui-danger: #F8285A;
199
+ --wui-danger: #E82646;
200
200
  --wui-danger-50: #FC94AD;
201
201
  --wui-danger-30: #FDBFCE;
202
202
  --wui-danger-20: #FED4DE;
@@ -343,6 +343,11 @@
343
343
  --wui-z-popover: 1600; // same rung as --wui-z-menu (above modal)
344
344
  --wui-z-tooltip: 1650;
345
345
  --wui-z-toast: 1700;
346
+
347
+ // The weekday every calendar's week starts on: the date picker, the date range and the event
348
+ // calendar. 0 = Sunday, 1 = Monday, through 6 = Saturday. A product sets it once here, a page or
349
+ // a section can set it on an ancestor, and a control's own first-day attribute wins over both.
350
+ --wui-first-day: 1;
346
351
  }
347
352
 
348
353
  // Bootstrap md / xl (not invented px)
@@ -20,6 +20,8 @@ $primary: #4F46E5 !default;
20
20
  // second brand colour should use a custom utility or token — do not repurpose
21
21
  // $secondary without a coordinated migration.
22
22
  $secondary: #4B5675 !default;
23
+ // The status colours match the base --wui-success/-info/-warning/-danger tokens, so a
24
+ // Bootstrap utility (.bg-success, .btn-info, --bs-danger) paints the same fill as the library.
23
25
  $success: #04B440 !default;
24
26
  $info: #056EE9 !default;
25
27
  $warning: #DFA000 !default;
@@ -28,6 +28,10 @@
28
28
  // Ink on a solid status fill (success, warning, danger, info): near-black in both modes, since
29
29
  // white fails AA on the bright fills.
30
30
  --color-on-status-fill: #0F0F0F;
31
+ // Ink on a DEEP status fill (the -text tokens, used as a fill the way primary is): white in
32
+ // light mode, where those shades measure 5.5 to 6.2:1; near-black in dark mode, where -text is
33
+ // the bright fill.
34
+ --color-on-status-strong: #FFFFFF;
31
35
 
32
36
  // Text ramp
33
37
  --color-text: var(--wui-text-heading);
@@ -207,29 +211,33 @@
207
211
  // Dark mode primary text: #fff on #6B83EC measures 3.5:1, below WCAG AA (4.5:1).
208
212
  // Map to a near-black for safe contrast (6.9:1 on --wui-dark-primary) when text sits on primary fill.
209
213
  --color-primary-text: #0F0F0F;
214
+ --color-on-status-strong: #0F0F0F;
210
215
 
211
- // On a dark surface the FILL colours are already the readable ones — the darkened
212
- // `-text` values would sink into the background. So dark mode maps text back to the
213
- // bright tokens rather than inheriting light mode's.
214
- --color-success-text: var(--wui-success);
215
- --color-danger-text: var(--wui-danger);
216
- --color-warning-text: var(--wui-warning);
217
- --color-info-text: var(--wui-info);
216
+ // On a dark surface the darkened `-text` values would sink into the background, so dark
217
+ // mode builds text from the fill instead, lifted with 25% white: the info and danger fills
218
+ // alone reach only 3.1:1 and 3.4:1 on the muted fill, and the lift clears 4.5:1 on every
219
+ // dark surface for all four tones.
220
+ --color-success-text: color-mix(in srgb, var(--wui-success) 75%, white);
221
+ --color-danger-text: color-mix(in srgb, var(--wui-danger) 75%, white);
222
+ --color-warning-text: color-mix(in srgb, var(--wui-warning) 75%, white);
223
+ --color-info-text: color-mix(in srgb, var(--wui-info) 75%, white);
218
224
 
219
225
  // Text ON a soft tint (an alert heading). The bright tone alone falls under WCAG AA on its own
220
226
  // 18% tint over the dark page (info and danger reach only ~3.8:1), so it is lifted with 30%
221
227
  // white: every tone then clears 4.5:1 on the dark page, surface and muted fill.
222
- --color-success-text-on-soft: color-mix(in srgb, var(--wui-success) 70%, white);
223
- --color-danger-text-on-soft: color-mix(in srgb, var(--wui-danger) 70%, white);
224
- --color-warning-text-on-soft: color-mix(in srgb, var(--wui-warning) 70%, white);
225
- --color-info-text-on-soft: color-mix(in srgb, var(--wui-info) 70%, white);
228
+ --color-success-text-on-soft: color-mix(in srgb, var(--wui-success) 65%, white);
229
+ --color-danger-text-on-soft: color-mix(in srgb, var(--wui-danger) 65%, white);
230
+ --color-warning-text-on-soft: color-mix(in srgb, var(--wui-warning) 65%, white);
231
+ --color-info-text-on-soft: color-mix(in srgb, var(--wui-info) 65%, white);
226
232
 
227
233
  // Light mode maps -strong to deep AA fills; on dark surfaces those sink into the page.
228
- // Remap to the bright fills so timeline dots / strong accents stay visible (same as -text).
229
- --color-success-strong: var(--wui-success);
230
- --color-danger-strong: var(--wui-danger);
231
- --color-info-strong: var(--wui-info);
232
- --color-warning-strong: var(--wui-warning);
234
+ // Dark mode uses the lifted -text tone, since -strong is a fill that carries near-black text
235
+ // (progress label, bell count): the bare danger and info fills reach only 4.4:1 and 4.0:1
236
+ // under that ink, the lifted ones 5.7:1 or better.
237
+ --color-success-strong: var(--color-success-text);
238
+ --color-danger-strong: var(--color-danger-text);
239
+ --color-info-strong: var(--color-info-text);
240
+ --color-warning-strong: var(--color-warning-text);
233
241
 
234
242
  --color-success-soft: color-mix(in srgb, var(--wui-success) 18%, transparent);
235
243
  --color-warning-soft: color-mix(in srgb, var(--wui-warning) 18%, transparent);
@@ -701,6 +701,45 @@
701
701
  border-radius: 999px;
702
702
  }
703
703
 
704
+ // A table wider than its scroller fades out at each edge it continues past, so a cut-off column
705
+ // reads as "more this way". scrollEdges.js (ui-elements) keeps the two data-wui-overflow-*
706
+ // attributes current on scroll and resize. The fade is a mask on the scroller, because a shadow
707
+ // paints under the opaque cells. A mask also hides what a descendant floats outside the box, so
708
+ // it stands aside while a menu or picker inside the table is open (an expanded row, which has no
709
+ // popup, keeps it). A pinned first column never
710
+ // fades at the start: it is the part that stays put.
711
+ :is(.table-wrap, .table-responsive, [class*="table-responsive-"]) {
712
+ --wui-overflow-fade-start: 0px;
713
+ --wui-overflow-fade-end: 0px;
714
+ }
715
+ :is(.table-wrap, .table-responsive, [class*="table-responsive-"])[data-wui-overflow-start] {
716
+ --wui-overflow-fade-start: 32px;
717
+ }
718
+ :is(.table-wrap, .table-responsive, [class*="table-responsive-"])[data-wui-overflow-end] {
719
+ --wui-overflow-fade-end: 32px;
720
+ }
721
+ .table-wrap[data-wui-overflow-start]:has(> .wui-table-sticky-first) {
722
+ --wui-overflow-fade-start: 0px;
723
+ }
724
+ :is(.table-wrap, .table-responsive, [class*="table-responsive-"]):is([data-wui-overflow-start], [data-wui-overflow-end]):not(:has([aria-haspopup][aria-expanded="true"], .dropdown-menu.show)) {
725
+ mask-image: linear-gradient(
726
+ 90deg,
727
+ transparent,
728
+ #000 var(--wui-overflow-fade-start),
729
+ #000 calc(100% - var(--wui-overflow-fade-end)),
730
+ transparent
731
+ );
732
+ }
733
+ :is(.table-wrap, .table-responsive, [class*="table-responsive-"]):dir(rtl):is([data-wui-overflow-start], [data-wui-overflow-end]):not(:has([aria-haspopup][aria-expanded="true"], .dropdown-menu.show)) {
734
+ mask-image: linear-gradient(
735
+ 270deg,
736
+ transparent,
737
+ #000 var(--wui-overflow-fade-start),
738
+ #000 calc(100% - var(--wui-overflow-fade-end)),
739
+ transparent
740
+ );
741
+ }
742
+
704
743
  // The identity/name column — a STABLE hook, because position is not one.
705
744
  //
706
745
  // The library owns .wui-select-cell, so a ledger's first cell is the 44px checkbox when
@@ -25,6 +25,10 @@
25
25
  // eyebrow, filled rail). Unset, completed steps follow the accent.
26
26
  // --wui-step-size compact/vertical badge diameter (default 28px)
27
27
  // --wui-step-badge-size icons-variant badge diameter (default 56px)
28
+ // --wui-step-journey-size journey badge diameter (default 40px)
29
+ // --wui-step-number-font the step number in a badge (default 12px; journey 13px, icons 22px)
30
+ // --wui-step-label-font the step label (default 13px; journey 16px, icons 15px)
31
+ // The `.wui-step-size-sm` / `.wui-step-size-lg` classes (end of this block) set these together.
28
32
  // Every variant defaults to the brand primary — active, done and rail share one accent
29
33
  // (no multi-colour). Colour variety is opt-in: a `.wui-step-tone-*` class or a var override.
30
34
  // ============================================================================
@@ -76,6 +80,15 @@
76
80
  .step.done + .step::before {
77
81
  background: var(--wui-step-done-accent, var(--wui-step-accent, var(--color-primary)));
78
82
  }
83
+ // The active circle's 4px ring would swallow the 4px gap, so both connectors touching the
84
+ // active step start 4px further out and keep the same gap from the ring's edge.
85
+ .step.active + .step::before {
86
+ left: calc(-50% + var(--wui-step-size, 28px) / 2 + 8px);
87
+ }
88
+ .step.active + .step::before,
89
+ .step.active::before {
90
+ right: calc(50% + var(--wui-step-size, 28px) / 2 + 8px);
91
+ }
79
92
  .step-circle {
80
93
  position: relative;
81
94
  z-index: 1;
@@ -87,7 +100,7 @@
87
100
  border: 1.5px solid var(--color-border);
88
101
  border-radius: 999px;
89
102
  background: var(--color-surface);
90
- font-size: 12px;
103
+ font-size: var(--wui-step-number-font, 12px);
91
104
  font-weight: 600;
92
105
  color: var(--color-text-subtle);
93
106
  }
@@ -106,7 +119,7 @@
106
119
  }
107
120
  .step-label {
108
121
  max-width: 130px;
109
- font-size: 13px;
122
+ font-size: var(--wui-step-label-font, 13px);
110
123
  font-weight: 500;
111
124
  line-height: 1.25;
112
125
  color: var(--color-text-subtle);
@@ -210,11 +223,11 @@ button.step.done:focus-visible .step-circle {
210
223
  content: none;
211
224
  }
212
225
  .stepper-journey .step-circle {
213
- width: 40px;
214
- height: 40px;
226
+ width: var(--wui-step-journey-size, 40px);
227
+ height: var(--wui-step-journey-size, 40px);
215
228
  border: none;
216
229
  background: var(--color-bg-muted);
217
- font-size: 13px;
230
+ font-size: var(--wui-step-number-font, 13px);
218
231
  font-weight: 700;
219
232
  transition:
220
233
  background 0.2s ease,
@@ -265,7 +278,7 @@ button.step.done:focus-visible .step-circle {
265
278
  .stepper-journey .step-label {
266
279
  max-width: none;
267
280
  margin-top: 8px;
268
- font-size: 16px;
281
+ font-size: var(--wui-step-label-font, 16px);
269
282
  line-height: 1.3;
270
283
  white-space: nowrap;
271
284
  overflow: hidden;
@@ -406,7 +419,7 @@ button.step.done:focus-visible .step-circle {
406
419
  border: none;
407
420
  background: var(--color-bg-muted);
408
421
  color: var(--color-text-subtle);
409
- font-size: 22px;
422
+ font-size: var(--wui-step-number-font, 22px);
410
423
  transition:
411
424
  background 0.2s ease,
412
425
  color 0.2s ease,
@@ -457,7 +470,7 @@ button.step.done:focus-visible .step-circle {
457
470
  }
458
471
  .stepper-icons .step-label {
459
472
  max-width: 150px;
460
- font-size: 15px;
473
+ font-size: var(--wui-step-label-font, 15px);
461
474
  font-weight: 600;
462
475
  color: var(--color-text-subtle);
463
476
  }
@@ -534,28 +547,29 @@ button.step.done:focus-visible .step-circle {
534
547
  --wui-step-soft: var(--color-primary-soft);
535
548
  --wui-step-soft-fg: var(--color-primary);
536
549
  }
537
- // Text on a bright status fill: a near-black ink in both modes, where white fails AA.
550
+ // Like primary, each tone fills with its deep shade (the -text token) under white ink in light mode,
551
+ // and with the bright fill under near-black ink in dark mode; both pairs clear AA.
538
552
  .wui-step-tone-success {
539
- --wui-step-accent: var(--color-success);
540
- --wui-step-accent-fg: var(--color-on-status-fill);
553
+ --wui-step-accent: var(--color-success-text);
554
+ --wui-step-accent-fg: var(--color-on-status-strong);
541
555
  --wui-step-soft: var(--color-success-soft);
542
556
  --wui-step-soft-fg: var(--color-success-text);
543
557
  }
544
558
  .wui-step-tone-warning {
545
- --wui-step-accent: var(--color-warning);
546
- --wui-step-accent-fg: var(--color-on-status-fill);
559
+ --wui-step-accent: var(--color-warning-text);
560
+ --wui-step-accent-fg: var(--color-on-status-strong);
547
561
  --wui-step-soft: var(--color-warning-soft);
548
562
  --wui-step-soft-fg: var(--color-warning-text);
549
563
  }
550
564
  .wui-step-tone-danger {
551
- --wui-step-accent: var(--color-danger);
552
- --wui-step-accent-fg: var(--color-on-status-fill);
565
+ --wui-step-accent: var(--color-danger-text);
566
+ --wui-step-accent-fg: var(--color-on-status-strong);
553
567
  --wui-step-soft: var(--color-danger-soft);
554
568
  --wui-step-soft-fg: var(--color-danger-text);
555
569
  }
556
570
  .wui-step-tone-info {
557
- --wui-step-accent: var(--color-info);
558
- --wui-step-accent-fg: var(--color-on-status-fill);
571
+ --wui-step-accent: var(--color-info-text);
572
+ --wui-step-accent-fg: var(--color-on-status-strong);
559
573
  --wui-step-soft: var(--color-info-soft);
560
574
  --wui-step-soft-fg: var(--color-info-text);
561
575
  }
@@ -572,30 +586,70 @@ button.step.done:focus-visible .step-circle {
572
586
  --wui-step-done-soft-fg: var(--color-primary);
573
587
  }
574
588
  .wui-step-done-tone-success {
575
- --wui-step-done-accent: var(--color-success);
576
- --wui-step-done-accent-fg: var(--color-on-status-fill);
589
+ --wui-step-done-accent: var(--color-success-text);
590
+ --wui-step-done-accent-fg: var(--color-on-status-strong);
577
591
  --wui-step-done-soft: var(--color-success-soft);
578
592
  --wui-step-done-soft-fg: var(--color-success-text);
579
593
  }
580
594
  .wui-step-done-tone-warning {
581
- --wui-step-done-accent: var(--color-warning);
582
- --wui-step-done-accent-fg: var(--color-on-status-fill);
595
+ --wui-step-done-accent: var(--color-warning-text);
596
+ --wui-step-done-accent-fg: var(--color-on-status-strong);
583
597
  --wui-step-done-soft: var(--color-warning-soft);
584
598
  --wui-step-done-soft-fg: var(--color-warning-text);
585
599
  }
586
600
  .wui-step-done-tone-danger {
587
- --wui-step-done-accent: var(--color-danger);
588
- --wui-step-done-accent-fg: var(--color-on-status-fill);
601
+ --wui-step-done-accent: var(--color-danger-text);
602
+ --wui-step-done-accent-fg: var(--color-on-status-strong);
589
603
  --wui-step-done-soft: var(--color-danger-soft);
590
604
  --wui-step-done-soft-fg: var(--color-danger-text);
591
605
  }
592
606
  .wui-step-done-tone-info {
593
- --wui-step-done-accent: var(--color-info);
594
- --wui-step-done-accent-fg: var(--color-on-status-fill);
607
+ --wui-step-done-accent: var(--color-info-text);
608
+ --wui-step-done-accent-fg: var(--color-on-status-strong);
595
609
  --wui-step-done-soft: var(--color-info-soft);
596
610
  --wui-step-done-soft-fg: var(--color-info-text);
597
611
  }
598
612
 
613
+ // Size presets: scale the badge, its number and the label together, on every variant. Same
614
+ // placement rules as the tone presets: on the stepper or any ancestor (the wizard host). Medium is
615
+ // the unset default above. The number and label fonts are per-variant here, so these rules name
616
+ // each variant; one class sets them all.
617
+ // ----------------------------------------------------------------------------
618
+ .wui-step-size-sm {
619
+ --wui-step-size: 22px;
620
+ --wui-step-badge-size: 44px;
621
+ --wui-step-journey-size: 32px;
622
+ --wui-step-number-font: 11px;
623
+ --wui-step-label-font: 12px;
624
+ }
625
+ .wui-step-size-sm .stepper-journey,
626
+ .wui-step-size-sm.stepper-journey {
627
+ --wui-step-number-font: 12px;
628
+ --wui-step-label-font: 14px;
629
+ }
630
+ .wui-step-size-sm .stepper-icons,
631
+ .wui-step-size-sm.stepper-icons {
632
+ --wui-step-number-font: 18px;
633
+ --wui-step-label-font: 13px;
634
+ }
635
+ .wui-step-size-lg {
636
+ --wui-step-size: 36px;
637
+ --wui-step-badge-size: 68px;
638
+ --wui-step-journey-size: 48px;
639
+ --wui-step-number-font: 14px;
640
+ --wui-step-label-font: 15px;
641
+ }
642
+ .wui-step-size-lg .stepper-journey,
643
+ .wui-step-size-lg.stepper-journey {
644
+ --wui-step-number-font: 15px;
645
+ --wui-step-label-font: 18px;
646
+ }
647
+ .wui-step-size-lg .stepper-icons,
648
+ .wui-step-size-lg.stepper-icons {
649
+ --wui-step-number-font: 26px;
650
+ --wui-step-label-font: 17px;
651
+ }
652
+
599
653
  // ============================================================================
600
654
  // Pricing card. A tier's name, price, pitch, feature list and CTA.
601
655
  //
@@ -610,23 +610,22 @@
610
610
  // Determinate progress-bar fill tones (AppProgress / AppMeter / <app-progress>) — the fill sits
611
611
  // in Bootstrap's .progress track; only the bar colour is brand-toned.
612
612
  //
613
- // success + danger use the DEEPENED -strong fills, not the mid tones: when showLabel is set the
614
- // bar carries Bootstrap's WHITE label text, and white on the mid --wui-success (#17C653) /
615
- // --wui-danger (#F8285A) fails AA (2.78:1 / 3.86:1). The -strong fills clear it (both ~5.8:1),
616
- // the same trick btn-danger uses. info/warning/neutral stay on their mid tones (info/neutral
617
- // pass with white; warning is not shown with a white label).
613
+ // Every tone fills with its -strong alias, so the label always sits on a fill chosen to carry it.
614
+ // In light mode white label text fails AA on the mid --wui-success (#04B440) / --wui-danger
615
+ // (#E82646) (2.76:1 / 4.38:1), so those two use the deepened fills (both ~5.8:1), the same trick
616
+ // btn-danger uses; info and warning -strong equal their mid tones there.
618
617
  //
619
618
  // They read the --color-*-strong aliases, not --wui-*-strong, because dark mode remaps the
620
- // aliases to the bright fills: the deep fills sink into the dark track (about 2:1). The label
619
+ // aliases to the lifted fills: the deep fills sink into the dark track (about 2:1). The label
621
620
  // flips to a dark ink there (--color-progress-label), which clears AA on all five dark fills.
622
621
  .wui-progress-info {
623
- background-color: var(--color-info);
622
+ background-color: var(--color-info-strong);
624
623
  }
625
624
  .wui-progress-success {
626
625
  background-color: var(--color-success-strong);
627
626
  }
628
627
  .wui-progress-warning {
629
- background-color: var(--color-warning);
628
+ background-color: var(--color-warning-strong);
630
629
  }
631
630
  .wui-progress-danger {
632
631
  background-color: var(--color-danger-strong);
@@ -1006,15 +1005,51 @@
1006
1005
  .alert-accent.alert-dismissible {
1007
1006
  padding-right: 53px;
1008
1007
  }
1009
- // The dismiss × is Bootstrap's ink-dark glyph; the token whitens it on a dark page.
1010
- .alert .btn-close {
1011
- filter: var(--color-close-filter);
1012
- }
1013
- // Centre the dismiss × vertically rather than pinning it to the top corner.
1008
+ // The dismiss × is a 32px rounded square like the modal close, centred vertically rather
1009
+ // than pinned to the top corner. Its 8px glyph takes the variant's text colour: Bootstrap's
1010
+ // glyph is a fixed-colour background image, so the button drops it and paints the same
1011
+ // shape as a mask on ::before, filled with the colour. Hover tints the square with a
1012
+ // transparent wash of the variant. Both colours come from tokens the dark theme remaps, so
1013
+ // the close filter is not needed here. A variant without its own tokens falls back to ink.
1014
1014
  .alert-dismissible .btn-close {
1015
1015
  top: 50%;
1016
- padding: 12px 16px;
1016
+ right: 10px;
1017
+ display: grid;
1018
+ place-items: center;
1019
+ width: 32px;
1020
+ height: 32px;
1021
+ padding: 0;
1022
+ background-image: none;
1023
+ border-radius: var(--radius-lg);
1024
+ corner-shape: squircle;
1025
+ opacity: 1;
1026
+ filter: none;
1017
1027
  transform: translateY(-50%);
1028
+ transition: background-color 180ms cubic-bezier(0.4, 0, 0.2, 1);
1029
+ }
1030
+ .alert-dismissible .btn-close::before {
1031
+ content: "";
1032
+ width: 8px;
1033
+ height: 8px;
1034
+ background-color: var(--alert-text, var(--color-text-muted));
1035
+ mask: var(--bs-btn-close-bg) center / contain no-repeat;
1036
+ }
1037
+ .alert-dismissible .btn-close:hover {
1038
+ background-color: color-mix(in srgb, var(--alert-accent, var(--color-text-muted)) 8%, transparent);
1039
+ }
1040
+ @media (prefers-reduced-motion: reduce) {
1041
+ .alert-dismissible .btn-close {
1042
+ transition: none;
1043
+ }
1044
+ }
1045
+ // Bootstrap's .btn-close paints a box-shadow ring on :focus (fires on mouse click too).
1046
+ // Drop it; keep a keyboard-only outline so dismissal stays reachable without a mouse.
1047
+ .alert .btn-close:focus {
1048
+ box-shadow: none;
1049
+ }
1050
+ .alert .btn-close:focus-visible {
1051
+ outline: 2px solid var(--color-primary);
1052
+ outline-offset: 2px;
1018
1053
  }
1019
1054
 
1020
1055
  // ============================================================================