@helixui/library 3.2.0-next.74 → 3.2.0-next.78

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.
Files changed (41) hide show
  1. package/custom-elements.json +5 -5
  2. package/dist/components/hx-button/hx-button.d.ts.map +1 -1
  3. package/dist/components/hx-button/hx-button.styles.d.ts.map +1 -1
  4. package/dist/components/hx-button/index.js +1 -1
  5. package/dist/components/hx-side-nav/hx-nav-item.d.ts.map +1 -1
  6. package/dist/components/hx-side-nav/hx-nav-item.styles.d.ts.map +1 -1
  7. package/dist/components/hx-side-nav/hx-side-nav.d.ts.map +1 -1
  8. package/dist/components/hx-side-nav/hx-side-nav.styles.d.ts.map +1 -1
  9. package/dist/components/hx-side-nav/index.js +1 -1
  10. package/dist/components/hx-text-input/hx-text-input.d.ts +5 -5
  11. package/dist/components/hx-text-input/hx-text-input.d.ts.map +1 -1
  12. package/dist/components/hx-text-input/index.js +1 -1
  13. package/dist/components/hx-toast/hx-toast.styles.d.ts.map +1 -1
  14. package/dist/components/hx-toast/index.js +1 -1
  15. package/dist/css/helix-all.css +85 -63
  16. package/dist/css/helix-core.css +54 -34
  17. package/dist/css/helix-feedback.css +18 -13
  18. package/dist/css/helix-forms.css +4 -4
  19. package/dist/css/helix-navigation.css +9 -12
  20. package/dist/css/helix-tokens.css +43 -0
  21. package/dist/css/hx-button.css +54 -34
  22. package/dist/css/hx-side-nav.css +9 -12
  23. package/dist/css/hx-text-input.css +4 -4
  24. package/dist/css/hx-toast.css +18 -13
  25. package/dist/css/index.css +1 -1
  26. package/dist/css/manifest.json +25 -16
  27. package/dist/index.js +4 -4
  28. package/dist/shared/{hx-button-modUSOpY.js → hx-button-YeUzCe6e.js} +77 -58
  29. package/dist/shared/hx-button-YeUzCe6e.js.map +1 -0
  30. package/dist/shared/{hx-nav-item-D8xHLVOs.js → hx-nav-item-BqML5BAh.js} +45 -42
  31. package/dist/shared/hx-nav-item-BqML5BAh.js.map +1 -0
  32. package/dist/shared/{hx-text-input-B-caO5fI.js → hx-text-input-ClrrmoE1.js} +20 -21
  33. package/dist/shared/hx-text-input-ClrrmoE1.js.map +1 -0
  34. package/dist/shared/{toast-factory-DvDRAh0l.js → toast-factory-CIiZDZGZ.js} +59 -54
  35. package/dist/shared/toast-factory-CIiZDZGZ.js.map +1 -0
  36. package/figma-inventory.json +16 -16
  37. package/package.json +2 -2
  38. package/dist/shared/hx-button-modUSOpY.js.map +0 -1
  39. package/dist/shared/hx-nav-item-D8xHLVOs.js.map +0 -1
  40. package/dist/shared/hx-text-input-B-caO5fI.js.map +0 -1
  41. package/dist/shared/toast-factory-DvDRAh0l.js.map +0 -1
@@ -395,10 +395,7 @@
395
395
 
396
396
  .button:focus-visible {
397
397
  outline: var(--hx-focus-ring-width, 2px) solid
398
- var(
399
- --hx-button-focus-ring-color,
400
- var(--hx-focus-ring-color, var(--hx-color-primary-500, #429797))
401
- );
398
+ var(--hx-button-focus-ring-color, var(--hx-focus-ring-color, #6ab1b1));
402
399
  outline-offset: var(--hx-focus-ring-offset, 2px);
403
400
  }
404
401
 
@@ -436,8 +433,11 @@
436
433
  /* ─── Style Variants ─── */
437
434
 
438
435
  .button--primary {
439
- --hx-button-bg: var(--hx-color-primary-500, #429797);
440
- --hx-button-color: var(--hx-color-text-on-primary, #ffffff);
436
+ --hx-button-bg: var(--hx-color-action-primary-bg, #429797);
437
+ /* Inline #0d1825 matches text.on-primary's resolved primitive (neutral-900);
438
+ cold-start without the semantic still paints AA-tuned dark-on-primary
439
+ rather than white-on-primary (3.43:1 fail). */
440
+ --hx-button-color: var(--hx-color-text-on-primary, #0d1825);
441
441
  --hx-button-border-color: transparent;
442
442
  }
443
443
 
@@ -445,8 +445,8 @@
445
445
  --hx-button-bg: transparent;
446
446
  /* primary-500 (#429797) text on white surface = 3.43:1 — fails AA.
447
447
  primary-600 (#0F7078) on white = 6.06:1 — AA pass. */
448
- --hx-button-color: var(--hx-color-primary-600, #0f7078);
449
- --hx-button-border-color: var(--hx-color-primary-600, #0f7078);
448
+ --hx-button-color: var(--hx-color-action-secondary-fg, #0f7078);
449
+ --hx-button-border-color: var(--hx-color-action-secondary-border, #0f7078);
450
450
  }
451
451
 
452
452
  .button--secondary:hover {
@@ -464,25 +464,29 @@
464
464
  }
465
465
 
466
466
  .button--danger {
467
- --hx-button-bg: var(--hx-color-error-500, #e5493e);
468
- --hx-button-color: var(--hx-color-text-on-error, #ffffff);
467
+ --hx-button-bg: var(--hx-color-action-danger-bg, #e5493e);
468
+ /* Inline #0d1825 matches text.on-error's resolved primitive (neutral-900);
469
+ cold-start without the semantic still paints AA-tuned dark-on-error
470
+ rather than white-on-error (3.92:1 fail). */
471
+ --hx-button-color: var(--hx-color-text-on-error, #0d1825);
469
472
  --hx-button-border-color: transparent;
470
473
  }
471
474
 
472
475
  /* on-error tokens are tuned for error-500 (neutral-900 on #E5493E ≈ 4.59:1).
473
- error-600 (#C92A2A) drops that to 2.25:1 — AA fail. Hold fg at neutral-0
474
- directly so darker hover fills stay legible. Mirrors hx-toast precedent
475
- (commit 300e21ab0). */
476
+ error-600 (#C92A2A) drops that to 2.25:1 — AA fail. text.on-error-strong
477
+ resolves to neutral-0 across modes (no dark flip) so the darker hover fill
478
+ stays legible. Mirrors hx-toast precedent (commit 300e21ab0); routed
479
+ through the semantic tier in 3.2.1 token-cascade remediation. */
476
480
  .button--danger:hover {
477
- --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-error-600, #c92a2a));
478
- --hx-button-color: var(--hx-color-neutral-0, #ffffff);
481
+ --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-action-danger-bg-hover, #c92a2a));
482
+ --hx-button-color: var(--hx-color-text-on-error-strong, #ffffff);
479
483
  }
480
484
 
481
485
  .button--ghost {
482
486
  --hx-button-bg: transparent;
483
487
  /* primary-500 (#429797) text on white surface = 3.43:1 — fails AA.
484
488
  primary-600 (#0F7078) on white = 6.06:1 — AA pass. */
485
- --hx-button-color: var(--hx-color-primary-600, #0f7078);
489
+ --hx-button-color: var(--hx-color-action-ghost-fg, #0f7078);
486
490
  --hx-button-border-color: transparent;
487
491
  }
488
492
 
@@ -501,12 +505,13 @@
501
505
  }
502
506
 
503
507
  /* on-primary token resolves to neutral-900 (#0D1825) — tuned for primary-500.
504
- primary-600 (#0F7078) drops the pair to 3.07:1 — AA fail. Pin fg at
505
- neutral-0 for the darker hover fill. Mirrors hx-toast precedent
506
- (commit 300e21ab0). */
508
+ primary-600 (#0F7078) drops the pair to 3.07:1 — AA fail. text.on-primary-strong
509
+ resolves to neutral-0 across modes (no dark flip) for the darker hover fill.
510
+ Mirrors hx-toast precedent (commit 300e21ab0); routed through the semantic
511
+ tier in 3.2.1 token-cascade remediation. */
507
512
  .button--primary:hover {
508
- --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-primary-600, #0f7078));
509
- --hx-button-color: var(--hx-color-neutral-0, #ffffff);
513
+ --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-action-primary-bg-hover, #0f7078));
514
+ --hx-button-color: var(--hx-color-text-on-primary-strong, #ffffff);
510
515
  }
511
516
 
512
517
  /* ─── Disabled ─── */
@@ -552,7 +557,7 @@
552
557
 
553
558
  /* Override text color and filter-based hover/active for all variants */
554
559
  :host([inverted]) .button {
555
- color: var(--hx-button-inverted-color, var(--hx-color-neutral-0, #ffffff));
560
+ color: var(--hx-button-inverted-color, var(--hx-color-text-inverse, #ffffff));
556
561
  filter: none;
557
562
  }
558
563
 
@@ -565,37 +570,42 @@
565
570
  }
566
571
 
567
572
  :host([inverted]) .button:focus-visible {
573
+ /* WCAG 1.4.11: focus indicator needs ≥3:1 against adjacent colors.
574
+ border-on-dark-default (overlay-white-30) ≈ 2.7:1 on neutral-900 — fails.
575
+ border-on-dark-strong (overlay-white-70) ≈ 5:1 — passes. */
568
576
  outline-color: var(
569
577
  --hx-button-inverted-focus-ring-color,
570
- var(--hx-overlay-white-50, rgba(255, 255, 255, 0.5))
578
+ var(--hx-color-border-on-dark-strong, rgba(255, 255, 255, 0.7))
571
579
  );
572
580
  }
573
581
 
574
582
  /* Primary inverted — slight transparent white overlay on hover */
575
583
  :host([inverted]) .button--primary:hover {
576
- --hx-button-bg: var(--hx-color-primary-400, #6ab1b1);
584
+ --hx-button-bg: var(--hx-color-action-primary-bg-inverted-hover, #6ab1b1);
577
585
  }
578
586
 
579
- /* Secondary inverted — white border and text */
587
+ /* Secondary inverted — white border and translucent hover fill */
580
588
  :host([inverted]) .button--secondary {
581
- --hx-button-border-color: var(--hx-overlay-white-70, rgba(255, 255, 255, 0.7));
589
+ --hx-button-border-color: var(--hx-color-border-on-dark-strong, rgba(255, 255, 255, 0.7));
582
590
  }
583
591
 
584
592
  :host([inverted]) .button--secondary:hover {
585
- --hx-button-bg: var(--hx-overlay-white-15, rgba(255, 255, 255, 0.15));
593
+ --hx-button-bg: var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.15));
586
594
  }
587
595
 
588
- /* Tertiary inverted */
596
+ /* Tertiary inverted — resting at subtle (10%) lifts to default (30%) on hover
597
+ so the runtime hover delta is visually distinct, not collapsed onto a
598
+ single token. */
589
599
  :host([inverted]) .button--tertiary {
590
- --hx-button-bg: var(--hx-overlay-white-15, rgba(255, 255, 255, 0.15));
600
+ --hx-button-bg: var(--hx-color-border-on-dark-subtle, rgba(255, 255, 255, 0.1));
591
601
  --hx-button-border-color: transparent;
592
602
  }
593
603
 
594
604
  :host([inverted]) .button--tertiary:hover {
595
- --hx-button-bg: var(--hx-overlay-white-25, rgba(255, 255, 255, 0.25));
605
+ --hx-button-bg: var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.3));
596
606
  }
597
607
 
598
- /* Ghost inverted — transparent base, white hover bg */
608
+ /* Ghost inverted — transparent base, translucent hover bg */
599
609
  :host([inverted]) .button--ghost {
600
610
  --hx-button-bg: transparent;
601
611
  --hx-button-border-color: transparent;
@@ -604,17 +614,17 @@
604
614
  :host([inverted]) .button--ghost:hover {
605
615
  --hx-button-bg: var(
606
616
  --hx-button-inverted-ghost-hover-bg,
607
- var(--hx-overlay-white-20, rgba(255, 255, 255, 0.2))
617
+ var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.2))
608
618
  );
609
619
  }
610
620
 
611
621
  /* Outline inverted — white border */
612
622
  :host([inverted]) .button--outline {
613
- --hx-button-border-color: var(--hx-overlay-white-70, rgba(255, 255, 255, 0.7));
623
+ --hx-button-border-color: var(--hx-color-border-on-dark-strong, rgba(255, 255, 255, 0.7));
614
624
  }
615
625
 
616
626
  :host([inverted]) .button--outline:hover {
617
- --hx-button-bg: var(--hx-overlay-white-15, rgba(255, 255, 255, 0.15));
627
+ --hx-button-bg: var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.15));
618
628
  }
619
629
 
620
630
  /* ─── Prefix / Suffix / Label ─── */
@@ -642,6 +652,16 @@
642
652
  border: 2px solid ButtonText;
643
653
  }
644
654
 
655
+ .button:hover {
656
+ /* Hover affordance must survive in HC. Highlight/HighlightText is the
657
+ OS-level "selected" pair, mirroring the forcedColorsInteractive mixin's
658
+ hover contract — kept inline since this component owns its bespoke HC
659
+ block (XOR rule). */
660
+ background-color: Highlight;
661
+ color: HighlightText;
662
+ border-color: Highlight;
663
+ }
664
+
645
665
  .button:focus-visible {
646
666
  outline: 3px solid Highlight;
647
667
  outline-offset: 2px;
@@ -1233,33 +1233,38 @@
1233
1233
  * error-600) because the lighter -500 fills can't pass AA against white
1234
1234
  * text in the precision-cool palette. The neutral-900 on-{role} tokens
1235
1235
  * are tuned for the lighter -500 surfaces and would fail here (e.g.
1236
- * neutral-900 on primary-600 = 3.07:1), so we hold fg at neutral-0
1237
- * directly for primary/success/danger.
1238
- * - neutral-0 on primary-600 (#0F7078) = 5.39:1 — AA pass
1239
- * - neutral-0 on success-700 (#146831) = 6.88:1 — AA pass
1236
+ * neutral-900 on primary-600 = 3.07:1), so the on-{role}-strong tokens
1237
+ * (neutral-0, no dark-mode flip) keep fg legible on the darker fills.
1238
+ * - text.on-primary-strong on info.bg-strong (primary-600, #0F7078) = 5.39:1
1239
+ * - text.on-success-strong on success.bg-strong (success-700, #146831) = 6.88:1
1240
1240
  * (success-600 #0E8A4A on white = 4.41:1 — drifts under AA at 14px)
1241
- * - neutral-0 on error-600 (#C92A2A) = 5.92:1 — AA pass
1242
- * - neutral-900 on warning-500 (#C2711C) = 4.83:1 — AA pass
1241
+ * - text.on-error-strong on danger.bg-strong (error-600, #C92A2A) = 5.92:1
1242
+ * - text.on-warning on warning.bg-strong (warning-500, #C2711C) = 4.83:1
1243
1243
  * (warning stays on the lighter -500 surface so on-warning works)
1244
+ *
1245
+ * 3.2.1 token-cascade: bg variants now route through surface.{role}-strong
1246
+ * semantics; fg variants route through text.on-{role}-strong (or on-warning
1247
+ * for the warning variant). Component-tier tokens are NOT bypassed — the
1248
+ * --hx-toast-bg / --hx-toast-color slots remain the single override point.
1244
1249
  */
1245
1250
  .toast--success {
1246
- --hx-toast-bg: var(--hx-color-success-700, #146831);
1247
- --hx-toast-color: var(--hx-color-neutral-0, #ffffff);
1251
+ --hx-toast-bg: var(--hx-color-surface-success-strong, #146831);
1252
+ --hx-toast-color: var(--hx-color-text-on-success-strong, #ffffff);
1248
1253
  }
1249
1254
 
1250
1255
  .toast--warning {
1251
- --hx-toast-bg: var(--hx-color-warning-500, #c2711c);
1256
+ --hx-toast-bg: var(--hx-color-surface-warning-strong, #c2711c);
1252
1257
  --hx-toast-color: var(--hx-color-text-on-warning, #0d1825);
1253
1258
  }
1254
1259
 
1255
1260
  .toast--danger {
1256
- --hx-toast-bg: var(--hx-color-error-600, #c92a2a);
1257
- --hx-toast-color: var(--hx-color-neutral-0, #ffffff);
1261
+ --hx-toast-bg: var(--hx-color-surface-danger-strong, #c92a2a);
1262
+ --hx-toast-color: var(--hx-color-text-on-error-strong, #ffffff);
1258
1263
  }
1259
1264
 
1260
1265
  .toast--info {
1261
- --hx-toast-bg: var(--hx-color-primary-600, #0f7078);
1262
- --hx-toast-color: var(--hx-color-neutral-0, #ffffff);
1266
+ --hx-toast-bg: var(--hx-color-surface-info-strong, #0f7078);
1267
+ --hx-toast-color: var(--hx-color-text-on-primary-strong, #ffffff);
1263
1268
  }
1264
1269
 
1265
1270
  /* ─── Severity Label (WCAG 1.4.1) ─── */
@@ -3861,10 +3861,10 @@
3861
3861
 
3862
3862
  /* ─── High Contrast Mode (forced-colors) ───
3863
3863
  *
3864
- * Component-specific overrides that complement the shared forcedColorsField
3865
- * mixin (composed in static styles). The mixin handles the input/wrapper
3866
- * core; the rules below extend it to the label / error / help-text /
3867
- * disabled-host surfaces unique to hx-text-input.
3864
+ * Bespoke block sole owner of forced-colors deference for hx-text-input.
3865
+ * Covers wrapper/input/placeholder/focus/disabled/error/label/help-text;
3866
+ * strictly more than forcedColorsField. The mixin is intentionally NOT
3867
+ * composed (XOR rule see styles/forced-colors.ts COMPOSITION RULES).
3868
3868
  */
3869
3869
 
3870
3870
  @media (forced-colors: active) {
@@ -739,7 +739,7 @@
739
739
  and evaluates their text against the page white background, producing
740
740
  false-positive color-contrast violations (WCAG 2.1 AA). */
741
741
  background-color: var(--hx-side-nav-bg, var(--hx-color-surface-inverse, #0d1825));
742
- color: var(--hx-side-nav-color, var(--hx-color-text-inverse, #ebeee9));
742
+ color: var(--hx-side-nav-color, var(--hx-color-text-inverse, #ffffff));
743
743
  }
744
744
 
745
745
  * {
@@ -754,11 +754,11 @@
754
754
  height: 100%;
755
755
  width: var(--hx-side-nav-width, 16rem);
756
756
  background-color: var(--hx-side-nav-bg, var(--hx-color-surface-inverse, #0d1825));
757
- color: var(--hx-side-nav-color, var(--hx-color-text-inverse, #ebeee9));
757
+ color: var(--hx-side-nav-color, var(--hx-color-text-inverse, #ffffff));
758
758
  transition: width var(--hx-transition-normal, 300ms) ease;
759
759
  overflow: hidden;
760
760
  border-inline-end: var(--hx-border-width-thin, 1px) solid
761
- var(--hx-side-nav-border-color, var(--hx-color-border-strong, #313e4b));
761
+ var(--hx-side-nav-border-color, var(--hx-color-border-strong, #8e9c98));
762
762
  }
763
763
 
764
764
  /* ─── Collapsed State ─── */
@@ -776,7 +776,7 @@
776
776
  flex-shrink: 0;
777
777
  min-height: var(--hx-space-14, 3.5rem);
778
778
  border-bottom: var(--hx-border-width-thin, 1px) solid
779
- var(--hx-side-nav-border-color, var(--hx-color-border-strong, #313e4b));
779
+ var(--hx-side-nav-border-color, var(--hx-color-border-strong, #8e9c98));
780
780
  overflow: hidden;
781
781
  }
782
782
 
@@ -803,7 +803,7 @@
803
803
  flex-shrink: 0;
804
804
  min-height: var(--hx-space-14, 3.5rem);
805
805
  border-top: var(--hx-border-width-thin, 1px) solid
806
- var(--hx-side-nav-border-color, var(--hx-color-border-strong, #313e4b));
806
+ var(--hx-side-nav-border-color, var(--hx-color-border-strong, #8e9c98));
807
807
  overflow: hidden;
808
808
  }
809
809
 
@@ -826,7 +826,7 @@
826
826
  border: none;
827
827
  border-radius: var(--hx-border-radius-sm, 0.25rem);
828
828
  background: transparent;
829
- color: var(--hx-side-nav-toggle-color, var(--hx-color-text-inverse, #b6bfb9));
829
+ color: var(--hx-side-nav-toggle-color, var(--hx-color-text-inverse, #ffffff));
830
830
  cursor: pointer;
831
831
  transition:
832
832
  background-color var(--hx-transition-fast, 150ms) ease,
@@ -835,10 +835,10 @@
835
835
 
836
836
  .side-nav__toggle:hover {
837
837
  background-color: var(
838
- --hx-overlay-white-10,
838
+ --hx-color-border-on-dark-subtle,
839
839
  rgba(255, 255, 255, 0.1)
840
840
  ); /* fallback for browsers without color-mix() */
841
- color: var(--hx-color-text-inverse, #ebeee9);
841
+ color: var(--hx-side-nav-toggle-hover-color, var(--hx-color-text-inverse, #ffffff));
842
842
  }
843
843
 
844
844
  @supports (color: color-mix(in srgb, red 50%, blue)) {
@@ -849,10 +849,7 @@
849
849
 
850
850
  .side-nav__toggle:focus-visible {
851
851
  outline: var(--hx-focus-ring-width, 2px) solid
852
- var(
853
- --hx-side-nav-focus-ring-color,
854
- var(--hx-focus-ring-color, var(--hx-color-primary-400, #6ab1b1))
855
- );
852
+ var(--hx-side-nav-focus-ring-color, var(--hx-focus-ring-color, #6ab1b1));
856
853
  outline-offset: var(--hx-focus-ring-offset, 2px);
857
854
  }
858
855
 
@@ -101,6 +101,9 @@
101
101
  --hx-color-text-on-success: var(--hx-color-neutral-900);
102
102
  --hx-color-text-on-warning: var(--hx-color-neutral-900);
103
103
  --hx-color-text-on-info: var(--hx-color-neutral-900);
104
+ --hx-color-text-on-primary-strong: var(--hx-color-neutral-0);
105
+ --hx-color-text-on-success-strong: var(--hx-color-neutral-0);
106
+ --hx-color-text-on-error-strong: var(--hx-color-neutral-0);
104
107
  --hx-color-text-link: var(--hx-color-primary-600);
105
108
  --hx-color-text-link-hover: var(--hx-color-primary-700);
106
109
  --hx-color-text-link-visited: var(--hx-color-secondary-600);
@@ -112,13 +115,32 @@
112
115
  --hx-color-surface-sunken: var(--hx-color-neutral-100);
113
116
  --hx-color-surface-inverse: var(--hx-color-neutral-900);
114
117
  --hx-color-surface-overlay: rgba(0, 0, 0, 0.75);
118
+ --hx-color-surface-success-strong: var(--hx-color-success-700);
119
+ --hx-color-surface-warning-strong: var(--hx-color-warning-500);
120
+ --hx-color-surface-danger-strong: var(--hx-color-error-600);
121
+ --hx-color-surface-info-strong: var(--hx-color-primary-600);
115
122
  --hx-color-border-default: var(--hx-color-neutral-200);
116
123
  --hx-color-border-subtle: var(--hx-color-neutral-100);
117
124
  --hx-color-border-strong: var(--hx-color-neutral-400);
118
125
  --hx-color-border-focus: var(--hx-color-primary-500);
126
+ --hx-color-border-on-dark-strong: var(--hx-overlay-white-70);
127
+ --hx-color-border-on-dark-default: var(--hx-overlay-white-30);
128
+ --hx-color-border-on-dark-subtle: var(--hx-overlay-white-10);
119
129
  --hx-color-focus-ring: var(--hx-color-primary-400);
120
130
  --hx-color-selection-bg: var(--hx-color-primary-200);
121
131
  --hx-color-selection-color: var(--hx-color-neutral-900);
132
+ --hx-color-action-primary-bg: var(--hx-color-primary-500);
133
+ --hx-color-action-primary-bg-hover: var(--hx-color-primary-600);
134
+ --hx-color-action-primary-bg-active: var(--hx-color-primary-700);
135
+ --hx-color-action-primary-bg-inverted-hover: var(--hx-color-primary-400);
136
+ --hx-color-action-secondary-fg: var(--hx-color-primary-600);
137
+ --hx-color-action-secondary-border: var(--hx-color-primary-600);
138
+ --hx-color-action-secondary-bg-hover: var(--hx-color-primary-50);
139
+ --hx-color-action-ghost-fg: var(--hx-color-primary-600);
140
+ --hx-color-action-ghost-bg-hover: var(--hx-color-primary-50);
141
+ --hx-color-action-danger-bg: var(--hx-color-error-500);
142
+ --hx-color-action-danger-bg-hover: var(--hx-color-error-600);
143
+ --hx-color-action-danger-bg-active: var(--hx-color-error-700);
122
144
  --hx-body-bg: var(--hx-color-surface-default);
123
145
  --hx-body-color: var(--hx-color-text-primary);
124
146
  --hx-body-font-family: var(--hx-font-family-sans);
@@ -351,6 +373,11 @@
351
373
  --hx-color-focus-ring: var(--hx-color-primary-400);
352
374
  --hx-color-selection-bg: var(--hx-color-primary-800);
353
375
  --hx-color-selection-color: var(--hx-color-neutral-100);
376
+ --hx-color-action-secondary-fg: var(--hx-color-primary-400);
377
+ --hx-color-action-secondary-border: var(--hx-color-primary-400);
378
+ --hx-color-action-secondary-bg-hover: var(--hx-color-primary-900);
379
+ --hx-color-action-ghost-fg: var(--hx-color-primary-400);
380
+ --hx-color-action-ghost-bg-hover: var(--hx-color-primary-900);
354
381
  --hx-body-bg: var(--hx-color-surface-default);
355
382
  --hx-body-color: var(--hx-color-text-primary);
356
383
  --hx-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
@@ -387,6 +414,11 @@
387
414
  --hx-color-focus-ring: var(--hx-color-primary-400);
388
415
  --hx-color-selection-bg: var(--hx-color-primary-800);
389
416
  --hx-color-selection-color: var(--hx-color-neutral-100);
417
+ --hx-color-action-secondary-fg: var(--hx-color-primary-400);
418
+ --hx-color-action-secondary-border: var(--hx-color-primary-400);
419
+ --hx-color-action-secondary-bg-hover: var(--hx-color-primary-900);
420
+ --hx-color-action-ghost-fg: var(--hx-color-primary-400);
421
+ --hx-color-action-ghost-bg-hover: var(--hx-color-primary-900);
390
422
  --hx-body-bg: var(--hx-color-surface-default);
391
423
  --hx-body-color: var(--hx-color-text-primary);
392
424
  --hx-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
@@ -426,6 +458,9 @@
426
458
  --hx-color-text-on-success: #000000;
427
459
  --hx-color-text-on-warning: #000000;
428
460
  --hx-color-text-on-info: #000000;
461
+ --hx-color-text-on-primary-strong: #000000;
462
+ --hx-color-text-on-error-strong: #000000;
463
+ --hx-color-text-on-success-strong: #000000;
429
464
  --hx-color-text-link: #FFFF00;
430
465
  --hx-color-text-link-hover: #FFFF99;
431
466
  --hx-color-text-link-visited: #FF80FF;
@@ -435,13 +470,21 @@
435
470
  --hx-color-surface-sunken: #000000;
436
471
  --hx-color-surface-inverse: #FFFFFF;
437
472
  --hx-color-surface-overlay: rgba(0, 0, 0, 0.95);
473
+ --hx-color-surface-success-strong: var(--hx-color-success-500);
474
+ --hx-color-surface-warning-strong: var(--hx-color-warning-500);
475
+ --hx-color-surface-danger-strong: var(--hx-color-error-500);
476
+ --hx-color-surface-info-strong: var(--hx-color-primary-500);
438
477
  --hx-color-border-default: #FFFFFF;
439
478
  --hx-color-border-subtle: #C0C0C0;
440
479
  --hx-color-border-strong: #FFFFFF;
441
480
  --hx-color-border-focus: #FFFF00;
481
+ --hx-color-border-on-dark-strong: #FFFFFF;
482
+ --hx-color-border-on-dark-default: #FFFFFF;
483
+ --hx-color-border-on-dark-subtle: #C0C0C0;
442
484
  --hx-color-focus-ring: #FFFF00;
443
485
  --hx-color-selection-bg: #1AEBFF;
444
486
  --hx-color-selection-color: #000000;
487
+ --hx-color-action-danger-bg-active: var(--hx-color-error-500);
445
488
  --hx-body-bg: #000000;
446
489
  --hx-body-color: #FFFFFF;
447
490
  --hx-focus-ring-color: #FFFF00;
@@ -46,10 +46,7 @@
46
46
 
47
47
  .button:focus-visible {
48
48
  outline: var(--hx-focus-ring-width, 2px) solid
49
- var(
50
- --hx-button-focus-ring-color,
51
- var(--hx-focus-ring-color, var(--hx-color-primary-500, #429797))
52
- );
49
+ var(--hx-button-focus-ring-color, var(--hx-focus-ring-color, #6ab1b1));
53
50
  outline-offset: var(--hx-focus-ring-offset, 2px);
54
51
  }
55
52
 
@@ -87,8 +84,11 @@
87
84
  /* ─── Style Variants ─── */
88
85
 
89
86
  .button--primary {
90
- --hx-button-bg: var(--hx-color-primary-500, #429797);
91
- --hx-button-color: var(--hx-color-text-on-primary, #ffffff);
87
+ --hx-button-bg: var(--hx-color-action-primary-bg, #429797);
88
+ /* Inline #0d1825 matches text.on-primary's resolved primitive (neutral-900);
89
+ cold-start without the semantic still paints AA-tuned dark-on-primary
90
+ rather than white-on-primary (3.43:1 fail). */
91
+ --hx-button-color: var(--hx-color-text-on-primary, #0d1825);
92
92
  --hx-button-border-color: transparent;
93
93
  }
94
94
 
@@ -96,8 +96,8 @@
96
96
  --hx-button-bg: transparent;
97
97
  /* primary-500 (#429797) text on white surface = 3.43:1 — fails AA.
98
98
  primary-600 (#0F7078) on white = 6.06:1 — AA pass. */
99
- --hx-button-color: var(--hx-color-primary-600, #0f7078);
100
- --hx-button-border-color: var(--hx-color-primary-600, #0f7078);
99
+ --hx-button-color: var(--hx-color-action-secondary-fg, #0f7078);
100
+ --hx-button-border-color: var(--hx-color-action-secondary-border, #0f7078);
101
101
  }
102
102
 
103
103
  .button--secondary:hover {
@@ -115,25 +115,29 @@
115
115
  }
116
116
 
117
117
  .button--danger {
118
- --hx-button-bg: var(--hx-color-error-500, #e5493e);
119
- --hx-button-color: var(--hx-color-text-on-error, #ffffff);
118
+ --hx-button-bg: var(--hx-color-action-danger-bg, #e5493e);
119
+ /* Inline #0d1825 matches text.on-error's resolved primitive (neutral-900);
120
+ cold-start without the semantic still paints AA-tuned dark-on-error
121
+ rather than white-on-error (3.92:1 fail). */
122
+ --hx-button-color: var(--hx-color-text-on-error, #0d1825);
120
123
  --hx-button-border-color: transparent;
121
124
  }
122
125
 
123
126
  /* on-error tokens are tuned for error-500 (neutral-900 on #E5493E ≈ 4.59:1).
124
- error-600 (#C92A2A) drops that to 2.25:1 — AA fail. Hold fg at neutral-0
125
- directly so darker hover fills stay legible. Mirrors hx-toast precedent
126
- (commit 300e21ab0). */
127
+ error-600 (#C92A2A) drops that to 2.25:1 — AA fail. text.on-error-strong
128
+ resolves to neutral-0 across modes (no dark flip) so the darker hover fill
129
+ stays legible. Mirrors hx-toast precedent (commit 300e21ab0); routed
130
+ through the semantic tier in 3.2.1 token-cascade remediation. */
127
131
  .button--danger:hover {
128
- --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-error-600, #c92a2a));
129
- --hx-button-color: var(--hx-color-neutral-0, #ffffff);
132
+ --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-action-danger-bg-hover, #c92a2a));
133
+ --hx-button-color: var(--hx-color-text-on-error-strong, #ffffff);
130
134
  }
131
135
 
132
136
  .button--ghost {
133
137
  --hx-button-bg: transparent;
134
138
  /* primary-500 (#429797) text on white surface = 3.43:1 — fails AA.
135
139
  primary-600 (#0F7078) on white = 6.06:1 — AA pass. */
136
- --hx-button-color: var(--hx-color-primary-600, #0f7078);
140
+ --hx-button-color: var(--hx-color-action-ghost-fg, #0f7078);
137
141
  --hx-button-border-color: transparent;
138
142
  }
139
143
 
@@ -152,12 +156,13 @@
152
156
  }
153
157
 
154
158
  /* on-primary token resolves to neutral-900 (#0D1825) — tuned for primary-500.
155
- primary-600 (#0F7078) drops the pair to 3.07:1 — AA fail. Pin fg at
156
- neutral-0 for the darker hover fill. Mirrors hx-toast precedent
157
- (commit 300e21ab0). */
159
+ primary-600 (#0F7078) drops the pair to 3.07:1 — AA fail. text.on-primary-strong
160
+ resolves to neutral-0 across modes (no dark flip) for the darker hover fill.
161
+ Mirrors hx-toast precedent (commit 300e21ab0); routed through the semantic
162
+ tier in 3.2.1 token-cascade remediation. */
158
163
  .button--primary:hover {
159
- --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-primary-600, #0f7078));
160
- --hx-button-color: var(--hx-color-neutral-0, #ffffff);
164
+ --hx-button-bg: var(--hx-button-hover-bg, var(--hx-color-action-primary-bg-hover, #0f7078));
165
+ --hx-button-color: var(--hx-color-text-on-primary-strong, #ffffff);
161
166
  }
162
167
 
163
168
  /* ─── Disabled ─── */
@@ -203,7 +208,7 @@
203
208
 
204
209
  /* Override text color and filter-based hover/active for all variants */
205
210
  :host([inverted]) .button {
206
- color: var(--hx-button-inverted-color, var(--hx-color-neutral-0, #ffffff));
211
+ color: var(--hx-button-inverted-color, var(--hx-color-text-inverse, #ffffff));
207
212
  filter: none;
208
213
  }
209
214
 
@@ -216,37 +221,42 @@
216
221
  }
217
222
 
218
223
  :host([inverted]) .button:focus-visible {
224
+ /* WCAG 1.4.11: focus indicator needs ≥3:1 against adjacent colors.
225
+ border-on-dark-default (overlay-white-30) ≈ 2.7:1 on neutral-900 — fails.
226
+ border-on-dark-strong (overlay-white-70) ≈ 5:1 — passes. */
219
227
  outline-color: var(
220
228
  --hx-button-inverted-focus-ring-color,
221
- var(--hx-overlay-white-50, rgba(255, 255, 255, 0.5))
229
+ var(--hx-color-border-on-dark-strong, rgba(255, 255, 255, 0.7))
222
230
  );
223
231
  }
224
232
 
225
233
  /* Primary inverted — slight transparent white overlay on hover */
226
234
  :host([inverted]) .button--primary:hover {
227
- --hx-button-bg: var(--hx-color-primary-400, #6ab1b1);
235
+ --hx-button-bg: var(--hx-color-action-primary-bg-inverted-hover, #6ab1b1);
228
236
  }
229
237
 
230
- /* Secondary inverted — white border and text */
238
+ /* Secondary inverted — white border and translucent hover fill */
231
239
  :host([inverted]) .button--secondary {
232
- --hx-button-border-color: var(--hx-overlay-white-70, rgba(255, 255, 255, 0.7));
240
+ --hx-button-border-color: var(--hx-color-border-on-dark-strong, rgba(255, 255, 255, 0.7));
233
241
  }
234
242
 
235
243
  :host([inverted]) .button--secondary:hover {
236
- --hx-button-bg: var(--hx-overlay-white-15, rgba(255, 255, 255, 0.15));
244
+ --hx-button-bg: var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.15));
237
245
  }
238
246
 
239
- /* Tertiary inverted */
247
+ /* Tertiary inverted — resting at subtle (10%) lifts to default (30%) on hover
248
+ so the runtime hover delta is visually distinct, not collapsed onto a
249
+ single token. */
240
250
  :host([inverted]) .button--tertiary {
241
- --hx-button-bg: var(--hx-overlay-white-15, rgba(255, 255, 255, 0.15));
251
+ --hx-button-bg: var(--hx-color-border-on-dark-subtle, rgba(255, 255, 255, 0.1));
242
252
  --hx-button-border-color: transparent;
243
253
  }
244
254
 
245
255
  :host([inverted]) .button--tertiary:hover {
246
- --hx-button-bg: var(--hx-overlay-white-25, rgba(255, 255, 255, 0.25));
256
+ --hx-button-bg: var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.3));
247
257
  }
248
258
 
249
- /* Ghost inverted — transparent base, white hover bg */
259
+ /* Ghost inverted — transparent base, translucent hover bg */
250
260
  :host([inverted]) .button--ghost {
251
261
  --hx-button-bg: transparent;
252
262
  --hx-button-border-color: transparent;
@@ -255,17 +265,17 @@
255
265
  :host([inverted]) .button--ghost:hover {
256
266
  --hx-button-bg: var(
257
267
  --hx-button-inverted-ghost-hover-bg,
258
- var(--hx-overlay-white-20, rgba(255, 255, 255, 0.2))
268
+ var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.2))
259
269
  );
260
270
  }
261
271
 
262
272
  /* Outline inverted — white border */
263
273
  :host([inverted]) .button--outline {
264
- --hx-button-border-color: var(--hx-overlay-white-70, rgba(255, 255, 255, 0.7));
274
+ --hx-button-border-color: var(--hx-color-border-on-dark-strong, rgba(255, 255, 255, 0.7));
265
275
  }
266
276
 
267
277
  :host([inverted]) .button--outline:hover {
268
- --hx-button-bg: var(--hx-overlay-white-15, rgba(255, 255, 255, 0.15));
278
+ --hx-button-bg: var(--hx-color-border-on-dark-default, rgba(255, 255, 255, 0.15));
269
279
  }
270
280
 
271
281
  /* ─── Prefix / Suffix / Label ─── */
@@ -293,6 +303,16 @@
293
303
  border: 2px solid ButtonText;
294
304
  }
295
305
 
306
+ .button:hover {
307
+ /* Hover affordance must survive in HC. Highlight/HighlightText is the
308
+ OS-level "selected" pair, mirroring the forcedColorsInteractive mixin's
309
+ hover contract — kept inline since this component owns its bespoke HC
310
+ block (XOR rule). */
311
+ background-color: Highlight;
312
+ color: HighlightText;
313
+ border-color: Highlight;
314
+ }
315
+
296
316
  .button:focus-visible {
297
317
  outline: 3px solid Highlight;
298
318
  outline-offset: 2px;