@webority/theme 0.39.0 → 0.40.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/theme",
3
- "version": "0.39.0",
3
+ "version": "0.40.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": {
@@ -46,6 +46,8 @@
46
46
  --wui-primary-10: #F1EBFF;
47
47
  --wui-primary-5: #F8F5FF;
48
48
  --wui-primary-text: #FFFFFF;
49
+ // Violet clears 4.5:1 as text on every light surface and its tint, so it is its own ink.
50
+ --wui-primary-ink: #4A0FBF;
49
51
  --wui-accent-dark: #340A88;
50
52
  --wui-surface-soft: #F1EBFF;
51
53
  --wui-surface-violet: #E6DCFD;
@@ -117,7 +119,7 @@
117
119
  }
118
120
 
119
121
  .btn-outline-primary {
120
- --bs-btn-color: var(--color-primary);
122
+ --bs-btn-color: var(--color-primary-ink);
121
123
  --bs-btn-border-color: var(--color-primary);
122
124
  --bs-btn-hover-color: #fff;
123
125
  --bs-btn-hover-bg: #4A0FBF;
@@ -47,13 +47,13 @@
47
47
  // then press. No border (the tint carries the shape). Disabled is the shared
48
48
  // neutral-grey state below, like every other variant.
49
49
  .btn-secondary {
50
- --bs-btn-color: var(--color-primary);
50
+ --bs-btn-color: var(--color-primary-ink);
51
51
  --bs-btn-bg: color-mix(in srgb, var(--color-primary) 12%, transparent);
52
52
  --bs-btn-border-color: transparent;
53
- --bs-btn-hover-color: var(--color-primary);
53
+ --bs-btn-hover-color: var(--color-primary-ink);
54
54
  --bs-btn-hover-bg: color-mix(in srgb, var(--color-primary) 18%, transparent);
55
55
  --bs-btn-hover-border-color: transparent;
56
- --bs-btn-active-color: var(--color-primary);
56
+ --bs-btn-active-color: var(--color-primary-ink);
57
57
  --bs-btn-active-bg: color-mix(in srgb, var(--color-primary) 26%, transparent);
58
58
  --bs-btn-active-border-color: transparent;
59
59
  }
@@ -104,7 +104,7 @@
104
104
  // on a transparent fill; hover fills with the brand and flips the label to the on-brand text
105
105
  // token; active deepens to the pressed fill.
106
106
  .btn-outline-primary {
107
- --bs-btn-color: var(--color-primary);
107
+ --bs-btn-color: var(--color-primary-ink);
108
108
  --bs-btn-border-color: var(--color-primary);
109
109
  --bs-btn-hover-color: var(--wui-primary-text);
110
110
  --bs-btn-hover-bg: var(--color-primary);
@@ -112,7 +112,7 @@
112
112
  --bs-btn-active-color: var(--wui-primary-text);
113
113
  --bs-btn-active-bg: var(--wui-primary-pressed);
114
114
  --bs-btn-active-border-color: var(--wui-primary-pressed);
115
- --bs-btn-disabled-color: var(--color-primary);
115
+ --bs-btn-disabled-color: var(--color-primary-ink);
116
116
  --bs-btn-disabled-border-color: var(--color-primary);
117
117
  }
118
118
 
@@ -465,7 +465,7 @@
465
465
  }
466
466
  &:hover .wui-icon, &:hover i, &:hover svg,
467
467
  &:focus .wui-icon, &:focus i, &:focus svg {
468
- color: var(--wui-primary);
468
+ color: var(--color-primary-ink);
469
469
  }
470
470
 
471
471
  // A leading picture instead of a glyph — an external logo/avatar, a brand mark.
@@ -651,7 +651,7 @@ button.wui-input-trailing:focus-visible,
651
651
  button.wui-input-leading:focus-visible {
652
652
  outline: 2px solid var(--color-primary);
653
653
  outline-offset: 2px;
654
- color: var(--wui-primary);
654
+ color: var(--color-primary-ink);
655
655
  border-radius: 9999px;
656
656
  }
657
657
 
@@ -30,7 +30,7 @@
30
30
  font-size: 14px;
31
31
  font-weight: 700;
32
32
  line-height: 1;
33
- color: var(--wui-primary);
33
+ color: var(--color-primary-ink);
34
34
  }
35
35
  .eyebrow::before {
36
36
  content: "";
package/scss/_tokens.scss CHANGED
@@ -138,9 +138,16 @@
138
138
  // from a fill, for using the status colour AS body text), this is the text/icon
139
139
  // colour to use ON TOP OF a solid --wui-primary fill — a selected datepicker day,
140
140
  // an active pagination page, a checked segmented option, .btn-primary's label.
141
- // Default assumes primary is dark enough for white; a brand with a light primary
142
- // overrides this to a dark value instead.
143
- --wui-primary-text: #fff;
141
+ // Derived from $primary (_variables.scss): white when it clears AA on the primary, near-black
142
+ // when the primary is too light for white. A runtime rebrand of --wui-primary sets it too.
143
+ --wui-primary-text: #{wui-hex($wui-primary-text)};
144
+
145
+ // ---- Primary as text -----------------------------------------------
146
+ // The brand primary darkened until it clears AA (4.5:1) as text on every light surface and on
147
+ // its own soft tint (_variables.scss). Every link, tab label, page number, selected option and
148
+ // primary-coloured icon reads this, never --wui-primary, which is sized as a fill. Equal to
149
+ // --wui-primary when the primary already clears AA. A runtime rebrand of --wui-primary sets it too.
150
+ --wui-primary-ink: #{wui-hex($wui-primary-ink)};
144
151
 
145
152
  // ---- Semantic status ramps -------------------------------------
146
153
  // Canonical info/success/warning/danger scales: `active` (text/icon on
@@ -1,3 +1,8 @@
1
+ @use "sass:color";
2
+ @use "sass:list";
3
+ @use "sass:math";
4
+ @use "sass:string";
5
+
1
6
  // =====================================================================
2
7
  // Webority UI — Bootstrap variable overrides.
3
8
  // Imported BEFORE `bootstrap/scss/bootstrap` so Bootstrap derives every
@@ -43,10 +48,90 @@ $theme-colors: (
43
48
  "dark": $dark,
44
49
  );
45
50
 
51
+ // ---- Primary as text ------------------------------------------------
52
+ // A brand primary is chosen as a fill. A light one (an orange measures 3.6:1 on white) fails
53
+ // WCAG AA (4.5:1) as text, so text and small UI in the brand colour use the primary ink: the
54
+ // primary mixed with black, in 2% steps, until it clears 4.5:1 on every light surface it lands
55
+ // on (white, the page, the muted and elevated fills) and on the brand's own soft tint. A primary
56
+ // that already clears it is its own ink. Derived from $primary, so a product that sets $primary
57
+ // gets a matching ink; it can still set $wui-primary-ink, or --wui-primary-ink at runtime.
58
+ $wui-ink-floor: 4.5;
59
+ $wui-ink-surfaces: (#FFFFFF, #F4F6F8, #F5F6FA, #F9FBFC);
60
+
61
+ @function wui-luminance($color) {
62
+ $linear: ();
63
+ @each $channel in ("red", "green", "blue") {
64
+ $s: math.div(color.channel($color, $channel, $space: rgb), 255);
65
+ @if $s <= 0.04045 {
66
+ $linear: list.append($linear, math.div($s, 12.92));
67
+ } @else {
68
+ $linear: list.append($linear, math.pow(math.div($s + 0.055, 1.055), 2.4));
69
+ }
70
+ }
71
+ @return 0.2126 * list.nth($linear, 1) + 0.7152 * list.nth($linear, 2) + 0.0722 * list.nth($linear, 3);
72
+ }
73
+
74
+ @function wui-contrast($a, $b) {
75
+ $la: wui-luminance($a);
76
+ $lb: wui-luminance($b);
77
+ @return math.div(math.max($la, $lb) + 0.05, math.min($la, $lb) + 0.05);
78
+ }
79
+
80
+ @function wui-worst-contrast($ink, $surfaces) {
81
+ $worst: 21;
82
+ @each $surface in $surfaces {
83
+ $worst: math.min($worst, wui-contrast($ink, $surface));
84
+ }
85
+ @return $worst;
86
+ }
87
+
88
+ @function wui-primary-ink($primary) {
89
+ // The soft tint (selected rows, avatar initials, the secondary button) is about 15% of the
90
+ // primary over white.
91
+ $surfaces: list.append($wui-ink-surfaces, color.mix($primary, #FFFFFF, 15%));
92
+ $ink: $primary;
93
+ $step: 0;
94
+ @while wui-worst-contrast($ink, $surfaces) < $wui-ink-floor and $step < 50 {
95
+ $step: $step + 1;
96
+ $ink: color.mix(black, $primary, $step * 2%);
97
+ }
98
+ // Whole channels, so the token compiles to a plain hex.
99
+ @return rgb(
100
+ math.round(color.channel($ink, "red", $space: rgb)),
101
+ math.round(color.channel($ink, "green", $space: rgb)),
102
+ math.round(color.channel($ink, "blue", $space: rgb))
103
+ );
104
+ }
105
+
106
+ // A colour as a #rrggbb literal, so a computed token reads like the hand-written ones around it.
107
+ @function wui-hex($color) {
108
+ $digits: "0123456789abcdef";
109
+ $out: "#";
110
+ @each $channel in ("red", "green", "blue") {
111
+ $v: math.round(color.channel($color, $channel, $space: rgb));
112
+ $out: $out + string.slice($digits, math.floor(math.div($v, 16)) + 1, math.floor(math.div($v, 16)) + 1) + string.slice($digits, $v % 16 + 1, $v % 16 + 1);
113
+ }
114
+ @return string.unquote($out);
115
+ }
116
+
117
+ // The label on a solid primary fill (.btn-primary, the active page, a checked option): white
118
+ // when it clears 4.5:1 on the primary, near-black when the primary is too light for white.
119
+ @function wui-on-primary($primary) {
120
+ @if wui-contrast(#FFFFFF, $primary) >= $wui-ink-floor {
121
+ @return #FFFFFF;
122
+ }
123
+ @return #0F0F0F;
124
+ }
125
+
126
+ $wui-primary-ink: wui-primary-ink($primary) !default;
127
+ $wui-primary-text: wui-on-primary($primary) !default;
128
+ $link-color: $wui-primary-ink !default;
129
+
46
130
  // Brand helper — set a product's primary ONCE and have the theme-colour map
47
131
  // (so every .btn-primary / .bg-primary / .text-primary utility), $link-color,
48
- // and $link-hover-color follow, instead of overriding each in turn. Call it in
49
- // the product's pre-bootstrap variables, BEFORE `bootstrap/scss/bootstrap`, e.g.:
132
+ // $link-hover-color, the primary ink and the on-primary label follow, instead of
133
+ // overriding each in turn. Call it in the product's pre-bootstrap variables,
134
+ // BEFORE `bootstrap/scss/bootstrap`, e.g.:
50
135
  //
51
136
  // @include wui-brand-primary(#0EA5E9);
52
137
  // @import "bootstrap/scss/bootstrap";
@@ -57,8 +142,10 @@ $theme-colors: (
57
142
  @mixin wui-brand-primary($primary) {
58
143
  $primary: $primary !global;
59
144
  $theme-colors: map-merge($theme-colors, ("primary": $primary)) !global;
60
- $link-color: $primary !global;
61
- $link-hover-color: mix(black, $primary, 12%) !global;
145
+ $wui-primary-ink: wui-primary-ink($primary) !global;
146
+ $wui-primary-text: wui-on-primary($primary) !global;
147
+ $link-color: $wui-primary-ink !global;
148
+ $link-hover-color: mix(black, $wui-primary-ink, 12%) !global;
62
149
  }
63
150
 
64
151
 
@@ -208,11 +295,11 @@ $toast-box-shadow: $wui-shadow-overlay-lg;
208
295
 
209
296
 
210
297
  // ---- Links ----------------------------------------------------------
211
- // $link-color left at Bootstrap's default ($primary) so it tracks brand $primary.
212
- // Hover is a darkened mix of $primary — never a hard-coded indigo — so a product
298
+ // $link-color is the primary ink (above), so a link clears AA whatever the brand.
299
+ // Hover is a darkened mix of the ink — never a hard-coded indigo — so a product
213
300
  // that sets $primary (or calls wui-brand-primary) gets a matching hover. Approximate
214
301
  // of Bootstrap's shift-color($link-color, 20%) without importing BS functions here.
215
- $link-hover-color: mix(black, $primary, 12%);
302
+ $link-hover-color: mix(black, $wui-primary-ink, 12%);
216
303
  $link-decoration: none; // BS default is underline
217
304
  $link-hover-decoration: underline;
218
305
 
@@ -62,6 +62,12 @@
62
62
  // Text/icon colour for content sitting ON a solid --color-primary fill — see
63
63
  // --wui-primary-text for why this is a different axis from the -text tokens below.
64
64
  --color-primary-text: var(--wui-primary-text);
65
+ // The primary as TEXT or a small glyph on a light surface: AA-safe whatever the brand (see
66
+ // --wui-primary-ink). --color-primary stays the fill, border and focus colour.
67
+ --color-primary-ink: var(--wui-primary-ink);
68
+ // Ink on the primary ink used as a fill (the active step badge): the ink clears 4.5:1 against
69
+ // white by construction, so white in light mode; near-black on the lifted dark primary.
70
+ --color-on-primary-ink: #FFFFFF;
65
71
 
66
72
  // Semantic status ramps
67
73
  --color-info-active: var(--wui-info-active);
@@ -211,6 +217,9 @@
211
217
  // Dark mode primary text: #fff on #6B83EC measures 3.5:1, below WCAG AA (4.5:1).
212
218
  // Map to a near-black for safe contrast (6.9:1 on --wui-dark-primary) when text sits on primary fill.
213
219
  --color-primary-text: #0F0F0F;
220
+ // The dark primary is already lifted to clear 4.5:1 on every dark surface and tint.
221
+ --color-primary-ink: var(--wui-dark-primary);
222
+ --color-on-primary-ink: #0F0F0F;
214
223
  --color-on-status-strong: #0F0F0F;
215
224
 
216
225
  // On a dark surface the darkened `-text` values would sink into the background, so dark
@@ -26,7 +26,7 @@
26
26
  border: 1px solid var(--color-border);
27
27
  border-radius: 50%;
28
28
  background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface));
29
- color: var(--color-primary);
29
+ color: var(--color-primary-ink);
30
30
  font: inherit;
31
31
  cursor: pointer;
32
32
  }
@@ -70,7 +70,7 @@ wui-calendar {
70
70
  .wui-calendar-nav-btn:hover {
71
71
  background: var(--color-primary-soft);
72
72
  border-color: var(--color-primary);
73
- color: var(--color-primary);
73
+ color: var(--color-primary-ink);
74
74
  }
75
75
  .wui-calendar-today-btn:focus-visible,
76
76
  .wui-calendar-nav-btn:focus-visible,
@@ -116,12 +116,12 @@ wui-calendar {
116
116
  color 0.14s ease;
117
117
  }
118
118
  .wui-calendar-view-btn:hover {
119
- color: var(--color-primary);
119
+ color: var(--color-primary-ink);
120
120
  }
121
121
  .wui-calendar-view-btn[aria-pressed="true"] {
122
122
  background: color-mix(in srgb, var(--color-primary) 8%, var(--color-surface));
123
123
  box-shadow: inset 0 0 0 2px var(--color-primary);
124
- color: var(--color-primary);
124
+ color: var(--color-primary-ink);
125
125
  }
126
126
 
127
127
  // On a phone the title takes a row of its own between the two controls, and every control is a
@@ -217,7 +217,7 @@ wui-calendar[data-view="week"] .wui-calendar-surface > * {
217
217
  .wui-calendar-event-primary {
218
218
  --fc-event-bg-color: var(--color-primary-soft);
219
219
  --fc-event-border-color: var(--color-primary);
220
- --fc-event-text-color: var(--color-primary);
220
+ --fc-event-text-color: var(--color-primary-ink);
221
221
  }
222
222
  .wui-calendar-event-success {
223
223
  --fc-event-bg-color: var(--color-success-soft);
@@ -7,7 +7,7 @@
7
7
  :where(.wui-sparkline) {
8
8
  display: inline-block;
9
9
  vertical-align: middle;
10
- color: var(--color-primary);
10
+ color: var(--color-primary-ink);
11
11
  }
12
12
  .wui-sparkline polyline {
13
13
  stroke-width: 1.5;
@@ -27,7 +27,7 @@
27
27
  }
28
28
  .wui-chart-card-tool[aria-pressed="true"] {
29
29
  background: var(--color-primary-soft);
30
- color: var(--color-primary);
30
+ color: var(--color-primary-ink);
31
31
  }
32
32
  // The legend toggle is useful only while the chart shows a legend: two or more series, in the
33
33
  // chart view. Before the element has drawn there is none, so the button waits for it.
@@ -33,7 +33,7 @@
33
33
  background: color-mix(in srgb, var(--color-primary) 5%, transparent);
34
34
  }
35
35
  .struct-card.checked .struct-card-title {
36
- color: var(--color-primary);
36
+ color: var(--color-primary-ink);
37
37
  }
38
38
  .struct-card-input {
39
39
  position: absolute;
@@ -56,7 +56,7 @@
56
56
  }
57
57
  .struct-card.checked .struct-card-ic {
58
58
  background: var(--color-surface);
59
- color: var(--color-primary);
59
+ color: var(--color-primary-ink);
60
60
  }
61
61
  // An image in the round icon disc (React iconNode={<img …/>}) keeps its own colours whether or not
62
62
  // the card is selected, fills the whole disc and is cropped to its circle, whatever the image's own
@@ -92,7 +92,7 @@
92
92
  align-items: center;
93
93
  justify-content: center;
94
94
  background: transparent;
95
- color: var(--color-primary);
95
+ color: var(--color-primary-ink);
96
96
  opacity: 0;
97
97
  transform: scale(0.5);
98
98
  transition:
@@ -433,7 +433,7 @@
433
433
  border-color: var(--color-focus-border);
434
434
  }
435
435
  .search-select-search:focus-within svg {
436
- color: var(--color-primary);
436
+ color: var(--color-primary-ink);
437
437
  }
438
438
  .search-select-clear {
439
439
  display: grid;
@@ -500,17 +500,17 @@
500
500
  .search-select-option:hover,
501
501
  .search-select-option.is-active {
502
502
  background: var(--color-primary-soft);
503
- color: var(--color-primary);
503
+ color: var(--color-primary-ink);
504
504
  }
505
505
  .search-select-option.selected {
506
506
  background: var(--color-primary-soft);
507
- color: var(--color-primary);
507
+ color: var(--color-primary-ink);
508
508
  font-weight: 500;
509
509
  }
510
510
  // allow-custom's freeform row — reads as an action ("Add "…"") rather than a listed value.
511
511
  .search-select-option-custom {
512
512
  font-style: italic;
513
- color: var(--color-primary);
513
+ color: var(--color-primary-ink);
514
514
  }
515
515
  .search-select-empty {
516
516
  padding: 12px 10px;
@@ -651,7 +651,7 @@ wui-date-range:not([aria-invalid="true"]) .daterange:not(.open) .daterange-trigg
651
651
  }
652
652
  button.datepicker-month:hover {
653
653
  background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
654
- color: var(--color-primary);
654
+ color: var(--color-primary-ink);
655
655
  }
656
656
  .datepicker-nav {
657
657
  display: inline-flex;
@@ -676,7 +676,7 @@ button.datepicker-month:hover {
676
676
  .datepicker-nav-btn:hover {
677
677
  background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
678
678
  border-color: var(--color-primary);
679
- color: var(--color-primary);
679
+ color: var(--color-primary-ink);
680
680
  }
681
681
  .datepicker-weekdays {
682
682
  display: grid;
@@ -728,7 +728,7 @@ button.datepicker-month:hover {
728
728
  }
729
729
  .datepicker-day:hover:not(:disabled):not(.is-selected) {
730
730
  background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
731
- color: var(--color-primary);
731
+ color: var(--color-primary-ink);
732
732
  }
733
733
  .datepicker-day.is-outside {
734
734
  color: var(--color-text-subtle);
@@ -736,7 +736,7 @@ button.datepicker-month:hover {
736
736
  }
737
737
  .datepicker-day.is-today {
738
738
  font-weight: 700;
739
- color: var(--color-primary);
739
+ color: var(--color-primary-ink);
740
740
  box-shadow: inset 0 0 0 1.5px var(--color-primary);
741
741
  }
742
742
  .datepicker-day.is-selected {
@@ -770,7 +770,7 @@ button.datepicker-month:hover {
770
770
  border-radius: var(--radius);
771
771
  font-size: 13px;
772
772
  font-weight: 600;
773
- color: var(--color-primary);
773
+ color: var(--color-primary-ink);
774
774
  cursor: pointer;
775
775
  transition: background 0.12s;
776
776
  }
@@ -808,11 +808,11 @@ button.datepicker-month:hover {
808
808
  }
809
809
  .datepicker-cell:hover:not(:disabled):not(.is-selected) {
810
810
  background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
811
- color: var(--color-primary);
811
+ color: var(--color-primary-ink);
812
812
  }
813
813
  .datepicker-cell.is-today {
814
814
  box-shadow: inset 0 0 0 1.5px var(--color-primary);
815
- color: var(--color-primary);
815
+ color: var(--color-primary-ink);
816
816
  font-weight: 700;
817
817
  }
818
818
  .datepicker-cell.is-selected {
@@ -961,7 +961,7 @@ button.datepicker-month:hover {
961
961
  // Days inside the chosen span get a soft rail; the two endpoints get the solid fill.
962
962
  .datepicker-day.in-range {
963
963
  background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 12%, transparent));
964
- color: var(--color-primary);
964
+ color: var(--color-primary-ink);
965
965
  border-radius: 6px;
966
966
  }
967
967
  .datepicker-day.is-range-start,
@@ -1232,7 +1232,7 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1232
1232
  transition: color 0.12s ease;
1233
1233
  }
1234
1234
  .dropdown-search-wrapper:focus-within .dropdown-search-icon {
1235
- color: var(--color-primary);
1235
+ color: var(--color-primary-ink);
1236
1236
  }
1237
1237
  .dropdown-search-input {
1238
1238
  width: 100%;
@@ -1306,11 +1306,11 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1306
1306
  .country-dropdown-item:focus,
1307
1307
  .country-dropdown-item.is-active {
1308
1308
  background: var(--color-primary-soft) !important;
1309
- color: var(--color-primary) !important;
1309
+ color: var(--color-primary-ink) !important;
1310
1310
  }
1311
1311
  .country-dropdown-item.selected {
1312
1312
  background: var(--color-primary-soft) !important;
1313
- color: var(--color-primary) !important;
1313
+ color: var(--color-primary-ink) !important;
1314
1314
  font-weight: 500;
1315
1315
  }
1316
1316
  .country-dropdown-menu .wui-flag {
@@ -1340,7 +1340,7 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1340
1340
  .selected-check-icon {
1341
1341
  margin-left: auto;
1342
1342
  display: flex;
1343
- color: var(--color-primary);
1343
+ color: var(--color-primary-ink);
1344
1344
  }
1345
1345
  .country-empty {
1346
1346
  padding: 10px 16px;
@@ -1387,7 +1387,7 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1387
1387
  }
1388
1388
  .wui-timepicker-toggle:hover:not(:disabled),
1389
1389
  .timepicker.open .wui-timepicker-toggle {
1390
- color: var(--color-primary);
1390
+ color: var(--color-primary-ink);
1391
1391
  }
1392
1392
  .wui-timepicker-toggle:focus-visible {
1393
1393
  outline: 2px solid var(--color-primary);
@@ -1507,7 +1507,7 @@ wui-timepicker[aria-invalid="true"] .timepicker.open .timepicker-trigger {
1507
1507
  .timepicker-option:hover:not(:disabled),
1508
1508
  .timepicker-option.is-active {
1509
1509
  background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 8%, transparent));
1510
- color: var(--color-primary);
1510
+ color: var(--color-primary-ink);
1511
1511
  }
1512
1512
  .timepicker-option.is-selected {
1513
1513
  background: var(--color-primary);
@@ -60,7 +60,7 @@ wui-command-palette {
60
60
  }
61
61
  .wui-cmd-spinner {
62
62
  flex: 0 0 auto;
63
- color: var(--color-primary);
63
+ color: var(--color-primary-ink);
64
64
  }
65
65
 
66
66
  .wui-cmd-list {
@@ -103,7 +103,7 @@ wui-command-palette {
103
103
  }
104
104
  .wui-cmd-item[aria-selected="true"] {
105
105
  background: var(--color-primary-soft);
106
- color: var(--color-primary);
106
+ color: var(--color-primary-ink);
107
107
  }
108
108
  .wui-cmd-item[aria-disabled="true"] {
109
109
  color: var(--color-text-muted);
@@ -123,7 +123,7 @@ wui-command-palette {
123
123
  }
124
124
  .wui-cmd-item[aria-selected="true"] .wui-cmd-item-icon {
125
125
  background: var(--color-surface);
126
- color: var(--color-primary);
126
+ color: var(--color-primary-ink);
127
127
  }
128
128
  .wui-cmd-item-text {
129
129
  display: flex;
@@ -57,7 +57,7 @@ wui-consent-banner:not([open]) {
57
57
  }
58
58
 
59
59
  .wui-consent-banner-policy {
60
- color: var(--color-primary);
60
+ color: var(--color-primary-ink);
61
61
  font-weight: 500;
62
62
  }
63
63
 
@@ -607,7 +607,7 @@ textarea.form-control.autosize {
607
607
  color: var(--color-text);
608
608
  }
609
609
  .num-stepper-btns button:active {
610
- color: var(--color-primary);
610
+ color: var(--color-primary-ink);
611
611
  }
612
612
  .input-date-wrap > .input {
613
613
  padding-right: 38px;
@@ -701,7 +701,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
701
701
  padding-left: 40px;
702
702
  }
703
703
  .input-icon:focus-within > .ic {
704
- color: var(--color-primary);
704
+ color: var(--color-primary-ink);
705
705
  }
706
706
 
707
707
  // Trailing action beside the field — a control (a labelled button, or a confirmation once the
@@ -751,7 +751,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
751
751
  background 0.12s ease;
752
752
  }
753
753
  .pw-toggle:hover {
754
- color: var(--color-primary);
754
+ color: var(--color-primary-ink);
755
755
  background: transparent;
756
756
  }
757
757
  .pw-toggle:focus-visible {
@@ -912,7 +912,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
912
912
  transform: translateY(-50%) scale(0.86);
913
913
  }
914
914
  .field-float .input:focus ~ .float-label {
915
- color: var(--color-primary);
915
+ color: var(--color-primary-ink);
916
916
  }
917
917
  .field-float .input.error ~ .float-label {
918
918
  color: var(--color-danger-text);
@@ -937,7 +937,7 @@ $wui-field-error-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/
937
937
  }
938
938
  .field-float:has(.search-select-control:focus) .float-label,
939
939
  .field-float:has(.search-select-control.open) .float-label {
940
- color: var(--color-primary);
940
+ color: var(--color-primary-ink);
941
941
  // The control turns white while focused/open, so the label mask follows (a filled-but-closed
942
942
  // select keeps the default muted mask, matching its muted resting fill).
943
943
  background: var(--color-surface);
@@ -1243,15 +1243,15 @@ wui-select[compact] {
1243
1243
  flex: none;
1244
1244
  }
1245
1245
  .num-btn:hover:not(:disabled) {
1246
- color: var(--color-primary);
1246
+ color: var(--color-primary-ink);
1247
1247
  background: var(--color-bg-muted);
1248
1248
  }
1249
1249
  .num-btn:focus-visible {
1250
1250
  outline: none;
1251
- color: var(--color-primary);
1251
+ color: var(--color-primary-ink);
1252
1252
  }
1253
1253
  .num-btn:active:not(:disabled) {
1254
- color: color-mix(in srgb, var(--color-primary) 80%, black);
1254
+ color: color-mix(in srgb, var(--color-primary-ink) 80%, black);
1255
1255
  }
1256
1256
  .num-btn:disabled {
1257
1257
  color: var(--color-text-subtle);
@@ -1320,7 +1320,7 @@ wui-select[compact] {
1320
1320
  border: 1px solid var(--color-border);
1321
1321
  }
1322
1322
  .badge-primary {
1323
- @include wui-tone-pill-colors(primary, var(--color-primary));
1323
+ @include wui-tone-pill-colors(primary, var(--color-primary-ink));
1324
1324
  border-color: color-mix(in srgb, var(--color-primary) 25%, transparent);
1325
1325
  }
1326
1326
  .badge-success {
@@ -1373,7 +1373,7 @@ wui-select[compact] {
1373
1373
  @include wui-tone-pill-colors(info);
1374
1374
  }
1375
1375
  .status-label.primary {
1376
- @include wui-tone-pill-colors(primary, var(--color-primary));
1376
+ @include wui-tone-pill-colors(primary, var(--color-primary-ink));
1377
1377
  }
1378
1378
  // Default/neutral — also the bare `.status-label` look, so an unknown or omitted
1379
1379
  // tone still renders as a proper pill instead of transparent text.
@@ -1423,7 +1423,7 @@ wui-select[compact] {
1423
1423
  // full-strength primary line (they read as the same selection language).
1424
1424
  border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
1425
1425
  background: var(--color-primary-soft);
1426
- color: var(--color-primary);
1426
+ color: var(--color-primary-ink);
1427
1427
  }
1428
1428
 
1429
1429
  // A count badge inside a selectable chip — "Active 12". Inverts on selection for
@@ -1439,7 +1439,7 @@ wui-select[compact] {
1439
1439
  }
1440
1440
  .choice-chip.checked .count {
1441
1441
  background: var(--color-surface);
1442
- color: var(--color-primary);
1442
+ color: var(--color-primary-ink);
1443
1443
  }
1444
1444
 
1445
1445
  // Size scale (`size`): padding, gap and font step together; md is the base above.