@lilydesignsystem/themes 0.2.0 → 0.3.1

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 (46) hide show
  1. package/dist/abyss.css +55 -7
  2. package/dist/acid.css +55 -7
  3. package/dist/adobe-spectrum.css +55 -7
  4. package/dist/aqua.css +55 -7
  5. package/dist/autumn.css +55 -7
  6. package/dist/black.css +55 -7
  7. package/dist/bumblebee.css +55 -7
  8. package/dist/business.css +55 -7
  9. package/dist/caramellatte.css +55 -7
  10. package/dist/cmyk.css +55 -7
  11. package/dist/coffee.css +55 -7
  12. package/dist/corporate.css +55 -7
  13. package/dist/cupcake.css +55 -7
  14. package/dist/cyberpunk.css +55 -7
  15. package/dist/dark.css +55 -7
  16. package/dist/dim.css +55 -7
  17. package/dist/dracula.css +55 -7
  18. package/dist/emerald.css +55 -7
  19. package/dist/fantasy.css +55 -7
  20. package/dist/forest.css +55 -7
  21. package/dist/garden.css +55 -7
  22. package/dist/halloween.css +55 -7
  23. package/dist/lemonade.css +55 -7
  24. package/dist/light.css +55 -7
  25. package/dist/lofi.css +55 -7
  26. package/dist/luxury.css +55 -7
  27. package/dist/mozilla-protocol.css +55 -7
  28. package/dist/night.css +55 -7
  29. package/dist/nord.css +55 -7
  30. package/dist/pastel.css +55 -7
  31. package/dist/retro.css +55 -7
  32. package/dist/silk.css +55 -7
  33. package/dist/sunset.css +55 -7
  34. package/dist/synthwave.css +55 -7
  35. package/dist/united-kingdom-government-digital-service.css +55 -7
  36. package/dist/united-kingdom-national-health-service-england-for-patients.css +55 -7
  37. package/dist/united-kingdom-national-health-service-england-for-practitioners.css +55 -7
  38. package/dist/united-kingdom-national-health-service-scotland-for-patients.css +55 -7
  39. package/dist/united-kingdom-national-health-service-scotland-for-practitioners.css +55 -7
  40. package/dist/united-kingdom-national-health-service-wales-for-patients.css +55 -7
  41. package/dist/united-kingdom-national-health-service-wales-for-practitioners.css +55 -7
  42. package/dist/united-states-web-design-system.css +55 -7
  43. package/dist/valentine.css +55 -7
  44. package/dist/winter.css +55 -7
  45. package/dist/wireframe.css +55 -7
  46. package/package.json +1 -1
@@ -535,6 +535,14 @@
535
535
  /* Primary body-copy colour. Maps to the theme's high-contrast
536
536
  reading colour. */
537
537
  --lily-text: var(--color-base-content);
538
+ /* Error text: the theme's error colour pulled toward the theme's own reading colour (`--color-base-content`, not `--lily-text`, which a host page may override) so it
539
+ keeps WCAG contrast as text on the page and on the error-tinted
540
+ callout surface (`--color-error` itself is a fill colour). */
541
+ --lily-error-text: color-mix(
542
+ in oklch,
543
+ var(--color-error) 22%,
544
+ var(--color-base-content)
545
+ );
538
546
  /* Muted / secondary text — mixed 70% with transparent to keep the
539
547
  hue but lower contrast for captions, helptext, placeholders. */
540
548
  --lily-text-muted: color-mix(
@@ -1586,7 +1594,7 @@
1586
1594
  }
1587
1595
  :where(.dont-list-item)::marker {
1588
1596
  content: "✗ ";
1589
- color: var(--color-error);
1597
+ color: var(--lily-error-text);
1590
1598
  }
1591
1599
 
1592
1600
  /* --- Tree --------------------------------------------------------------- */
@@ -1668,7 +1676,7 @@
1668
1676
  color: var(--color-success);
1669
1677
  }
1670
1678
  :where(.validation-list-item[data-status="failed"]) {
1671
- color: var(--color-error);
1679
+ color: var(--lily-error-text);
1672
1680
  }
1673
1681
  :where(.validation-list-item[data-status="pending"]) {
1674
1682
  color: var(--lily-text-muted);
@@ -2154,7 +2162,7 @@
2154
2162
 
2155
2163
  /* Error message — inline form-validation message. */
2156
2164
  :where(.error-message) {
2157
- color: var(--color-error);
2165
+ color: var(--lily-error-text);
2158
2166
  font-weight: 600;
2159
2167
  }
2160
2168
  /* Error summary — heavyweight border (GOV.UK pattern) so users scrolling
@@ -3505,6 +3513,7 @@
3505
3513
  .theme-picker-list,
3506
3514
  .locale-picker-list,
3507
3515
  .text-size-picker-list,
3516
+ .motion-picker-list,
3508
3517
  .share-picker-list,
3509
3518
  .search-picker-panel
3510
3519
  ) {
@@ -3530,17 +3539,56 @@
3530
3539
  .theme-picker-list,
3531
3540
  .locale-picker-list,
3532
3541
  .text-size-picker-list,
3542
+ .motion-picker-list,
3533
3543
  .share-picker-list,
3534
3544
  .search-picker-panel
3535
3545
  )[hidden] {
3536
3546
  display: none;
3537
3547
  }
3538
3548
 
3539
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
3549
+ /* Popups open under their button, aligned to its start edge, and flip to
3550
+ its end edge (or above it) when they would run off the viewport. A header
3551
+ picker near the right edge otherwise overflows, and focusing the popup
3552
+ scrolls the whole page sideways. `position: fixed` makes the viewport the
3553
+ containing block that the flip fallbacks are checked against; browsers
3554
+ without anchor positioning keep the absolute placement above. */
3555
+ @supports (position-area: block-end) {
3556
+ :where(.theme-picker) { anchor-name: --lily-theme-picker; }
3557
+ :where(.locale-picker) { anchor-name: --lily-locale-picker; }
3558
+ :where(.text-size-picker) { anchor-name: --lily-text-size-picker; }
3559
+ :where(.motion-picker) { anchor-name: --lily-motion-picker; }
3560
+ :where(.share-picker) { anchor-name: --lily-share-picker; }
3561
+ :where(.search-picker) { anchor-name: --lily-search-picker; }
3562
+ :where(.theme-picker-list) { position-anchor: --lily-theme-picker; }
3563
+ :where(.locale-picker-list) { position-anchor: --lily-locale-picker; }
3564
+ :where(.text-size-picker-list) { position-anchor: --lily-text-size-picker; }
3565
+ :where(.motion-picker-list) { position-anchor: --lily-motion-picker; }
3566
+ :where(.share-picker-list) { position-anchor: --lily-share-picker; }
3567
+ :where(.search-picker-panel) { position-anchor: --lily-search-picker; }
3568
+ :where(
3569
+ .theme-picker-list,
3570
+ .locale-picker-list,
3571
+ .text-size-picker-list,
3572
+ .motion-picker-list,
3573
+ .share-picker-list,
3574
+ .search-picker-panel
3575
+ ) {
3576
+ position: fixed;
3577
+ inset: auto;
3578
+ position-area: block-end span-inline-end;
3579
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
3580
+ min-width: 0;
3581
+ width: max-content;
3582
+ max-width: calc(100vw - 1rem);
3583
+ }
3584
+ }
3585
+
3586
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
3540
3587
  > :where(
3541
3588
  .theme-picker-option,
3542
3589
  .locale-picker-option,
3543
- .text-size-picker-option
3590
+ .text-size-picker-option,
3591
+ .motion-picker-option
3544
3592
  ) {
3545
3593
  padding: var(--lily-space-xs) var(--lily-space-sm);
3546
3594
  border-radius: var(--lily-radius-sm);
@@ -3549,7 +3597,7 @@
3549
3597
 
3550
3598
  /* data-active is the keyboard cursor (aria-activedescendant); focus
3551
3599
  stays on the listbox, so this is the only visible cue for it. */
3552
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
3600
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
3553
3601
  > :where([data-active]) {
3554
3602
  background-color: var(--lily-surface-alt);
3555
3603
  outline: var(--lily-focus-ring);
@@ -3558,7 +3606,7 @@
3558
3606
 
3559
3607
  /* aria-selected marks the applied theme/locale, which is not the
3560
3608
  same thing as the keyboard cursor. Both can be visible at once. */
3561
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
3609
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
3562
3610
  > :where([aria-selected="true"]) {
3563
3611
  font-weight: 600;
3564
3612
  background-color: var(--lily-surface-sunken);
@@ -156,6 +156,14 @@
156
156
  /* Primary body-copy colour. Maps to the theme's high-contrast
157
157
  reading colour. */
158
158
  --lily-text: var(--color-base-content);
159
+ /* Error text: the theme's error colour pulled toward the theme's own reading colour (`--color-base-content`, not `--lily-text`, which a host page may override) so it
160
+ keeps WCAG contrast as text on the page and on the error-tinted
161
+ callout surface (`--color-error` itself is a fill colour). */
162
+ --lily-error-text: color-mix(
163
+ in oklch,
164
+ var(--color-error) 22%,
165
+ var(--color-base-content)
166
+ );
159
167
  /* Muted / secondary text — mixed 70% with transparent to keep the
160
168
  hue but lower contrast for captions, helptext, placeholders. */
161
169
  --lily-text-muted: color-mix(
@@ -895,7 +903,7 @@
895
903
  }
896
904
  :where(.dont-list-item)::marker {
897
905
  content: "✗ ";
898
- color: var(--color-error);
906
+ color: var(--lily-error-text);
899
907
  }
900
908
 
901
909
  /* --- Tree --------------------------------------------------------------- */
@@ -977,7 +985,7 @@
977
985
  color: var(--color-success);
978
986
  }
979
987
  :where(.validation-list-item[data-status="failed"]) {
980
- color: var(--color-error);
988
+ color: var(--lily-error-text);
981
989
  }
982
990
  :where(.validation-list-item[data-status="pending"]) {
983
991
  color: var(--lily-text-muted);
@@ -1335,7 +1343,7 @@
1335
1343
 
1336
1344
  /* Error message — inline form-validation message. */
1337
1345
  :where(.error-message) {
1338
- color: var(--color-error);
1346
+ color: var(--lily-error-text);
1339
1347
  font-weight: 600;
1340
1348
  }
1341
1349
  /* Error summary — heavyweight 4px border (GOV.UK pattern) so users
@@ -2549,6 +2557,7 @@
2549
2557
  .theme-picker-list,
2550
2558
  .locale-picker-list,
2551
2559
  .text-size-picker-list,
2560
+ .motion-picker-list,
2552
2561
  .share-picker-list,
2553
2562
  .search-picker-panel
2554
2563
  ) {
@@ -2574,17 +2583,56 @@
2574
2583
  .theme-picker-list,
2575
2584
  .locale-picker-list,
2576
2585
  .text-size-picker-list,
2586
+ .motion-picker-list,
2577
2587
  .share-picker-list,
2578
2588
  .search-picker-panel
2579
2589
  )[hidden] {
2580
2590
  display: none;
2581
2591
  }
2582
2592
 
2583
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2593
+ /* Popups open under their button, aligned to its start edge, and flip to
2594
+ its end edge (or above it) when they would run off the viewport. A header
2595
+ picker near the right edge otherwise overflows, and focusing the popup
2596
+ scrolls the whole page sideways. `position: fixed` makes the viewport the
2597
+ containing block that the flip fallbacks are checked against; browsers
2598
+ without anchor positioning keep the absolute placement above. */
2599
+ @supports (position-area: block-end) {
2600
+ :where(.theme-picker) { anchor-name: --lily-theme-picker; }
2601
+ :where(.locale-picker) { anchor-name: --lily-locale-picker; }
2602
+ :where(.text-size-picker) { anchor-name: --lily-text-size-picker; }
2603
+ :where(.motion-picker) { anchor-name: --lily-motion-picker; }
2604
+ :where(.share-picker) { anchor-name: --lily-share-picker; }
2605
+ :where(.search-picker) { anchor-name: --lily-search-picker; }
2606
+ :where(.theme-picker-list) { position-anchor: --lily-theme-picker; }
2607
+ :where(.locale-picker-list) { position-anchor: --lily-locale-picker; }
2608
+ :where(.text-size-picker-list) { position-anchor: --lily-text-size-picker; }
2609
+ :where(.motion-picker-list) { position-anchor: --lily-motion-picker; }
2610
+ :where(.share-picker-list) { position-anchor: --lily-share-picker; }
2611
+ :where(.search-picker-panel) { position-anchor: --lily-search-picker; }
2612
+ :where(
2613
+ .theme-picker-list,
2614
+ .locale-picker-list,
2615
+ .text-size-picker-list,
2616
+ .motion-picker-list,
2617
+ .share-picker-list,
2618
+ .search-picker-panel
2619
+ ) {
2620
+ position: fixed;
2621
+ inset: auto;
2622
+ position-area: block-end span-inline-end;
2623
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
2624
+ min-width: 0;
2625
+ width: max-content;
2626
+ max-width: calc(100vw - 1rem);
2627
+ }
2628
+ }
2629
+
2630
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2584
2631
  > :where(
2585
2632
  .theme-picker-option,
2586
2633
  .locale-picker-option,
2587
- .text-size-picker-option
2634
+ .text-size-picker-option,
2635
+ .motion-picker-option
2588
2636
  ) {
2589
2637
  padding: var(--lily-space-xs) var(--lily-space-sm);
2590
2638
  border-radius: var(--lily-radius-sm);
@@ -2593,7 +2641,7 @@
2593
2641
 
2594
2642
  /* data-active is the keyboard cursor (aria-activedescendant); focus
2595
2643
  stays on the listbox, so this is the only visible cue for it. */
2596
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2644
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2597
2645
  > :where([data-active]) {
2598
2646
  background-color: var(--lily-surface-alt);
2599
2647
  outline: var(--lily-focus-ring);
@@ -2602,7 +2650,7 @@
2602
2650
 
2603
2651
  /* aria-selected marks the applied theme/locale, which is not the
2604
2652
  same thing as the keyboard cursor. Both can be visible at once. */
2605
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2653
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2606
2654
  > :where([aria-selected="true"]) {
2607
2655
  font-weight: 600;
2608
2656
  background-color: var(--lily-surface-sunken);
@@ -156,6 +156,14 @@
156
156
  /* Primary body-copy colour. Maps to the theme's high-contrast
157
157
  reading colour. */
158
158
  --lily-text: var(--color-base-content);
159
+ /* Error text: the theme's error colour pulled toward the theme's own reading colour (`--color-base-content`, not `--lily-text`, which a host page may override) so it
160
+ keeps WCAG contrast as text on the page and on the error-tinted
161
+ callout surface (`--color-error` itself is a fill colour). */
162
+ --lily-error-text: color-mix(
163
+ in oklch,
164
+ var(--color-error) 22%,
165
+ var(--color-base-content)
166
+ );
159
167
  /* Muted / secondary text — mixed 70% with transparent to keep the
160
168
  hue but lower contrast for captions, helptext, placeholders. */
161
169
  --lily-text-muted: color-mix(
@@ -895,7 +903,7 @@
895
903
  }
896
904
  :where(.dont-list-item)::marker {
897
905
  content: "✗ ";
898
- color: var(--color-error);
906
+ color: var(--lily-error-text);
899
907
  }
900
908
 
901
909
  /* --- Tree --------------------------------------------------------------- */
@@ -977,7 +985,7 @@
977
985
  color: var(--color-success);
978
986
  }
979
987
  :where(.validation-list-item[data-status="failed"]) {
980
- color: var(--color-error);
988
+ color: var(--lily-error-text);
981
989
  }
982
990
  :where(.validation-list-item[data-status="pending"]) {
983
991
  color: var(--lily-text-muted);
@@ -1335,7 +1343,7 @@
1335
1343
 
1336
1344
  /* Error message — inline form-validation message. */
1337
1345
  :where(.error-message) {
1338
- color: var(--color-error);
1346
+ color: var(--lily-error-text);
1339
1347
  font-weight: 600;
1340
1348
  }
1341
1349
  /* Error summary — heavyweight 4px border (GOV.UK pattern) so users
@@ -2549,6 +2557,7 @@
2549
2557
  .theme-picker-list,
2550
2558
  .locale-picker-list,
2551
2559
  .text-size-picker-list,
2560
+ .motion-picker-list,
2552
2561
  .share-picker-list,
2553
2562
  .search-picker-panel
2554
2563
  ) {
@@ -2574,17 +2583,56 @@
2574
2583
  .theme-picker-list,
2575
2584
  .locale-picker-list,
2576
2585
  .text-size-picker-list,
2586
+ .motion-picker-list,
2577
2587
  .share-picker-list,
2578
2588
  .search-picker-panel
2579
2589
  )[hidden] {
2580
2590
  display: none;
2581
2591
  }
2582
2592
 
2583
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2593
+ /* Popups open under their button, aligned to its start edge, and flip to
2594
+ its end edge (or above it) when they would run off the viewport. A header
2595
+ picker near the right edge otherwise overflows, and focusing the popup
2596
+ scrolls the whole page sideways. `position: fixed` makes the viewport the
2597
+ containing block that the flip fallbacks are checked against; browsers
2598
+ without anchor positioning keep the absolute placement above. */
2599
+ @supports (position-area: block-end) {
2600
+ :where(.theme-picker) { anchor-name: --lily-theme-picker; }
2601
+ :where(.locale-picker) { anchor-name: --lily-locale-picker; }
2602
+ :where(.text-size-picker) { anchor-name: --lily-text-size-picker; }
2603
+ :where(.motion-picker) { anchor-name: --lily-motion-picker; }
2604
+ :where(.share-picker) { anchor-name: --lily-share-picker; }
2605
+ :where(.search-picker) { anchor-name: --lily-search-picker; }
2606
+ :where(.theme-picker-list) { position-anchor: --lily-theme-picker; }
2607
+ :where(.locale-picker-list) { position-anchor: --lily-locale-picker; }
2608
+ :where(.text-size-picker-list) { position-anchor: --lily-text-size-picker; }
2609
+ :where(.motion-picker-list) { position-anchor: --lily-motion-picker; }
2610
+ :where(.share-picker-list) { position-anchor: --lily-share-picker; }
2611
+ :where(.search-picker-panel) { position-anchor: --lily-search-picker; }
2612
+ :where(
2613
+ .theme-picker-list,
2614
+ .locale-picker-list,
2615
+ .text-size-picker-list,
2616
+ .motion-picker-list,
2617
+ .share-picker-list,
2618
+ .search-picker-panel
2619
+ ) {
2620
+ position: fixed;
2621
+ inset: auto;
2622
+ position-area: block-end span-inline-end;
2623
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
2624
+ min-width: 0;
2625
+ width: max-content;
2626
+ max-width: calc(100vw - 1rem);
2627
+ }
2628
+ }
2629
+
2630
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2584
2631
  > :where(
2585
2632
  .theme-picker-option,
2586
2633
  .locale-picker-option,
2587
- .text-size-picker-option
2634
+ .text-size-picker-option,
2635
+ .motion-picker-option
2588
2636
  ) {
2589
2637
  padding: var(--lily-space-xs) var(--lily-space-sm);
2590
2638
  border-radius: var(--lily-radius-sm);
@@ -2593,7 +2641,7 @@
2593
2641
 
2594
2642
  /* data-active is the keyboard cursor (aria-activedescendant); focus
2595
2643
  stays on the listbox, so this is the only visible cue for it. */
2596
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2644
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2597
2645
  > :where([data-active]) {
2598
2646
  background-color: var(--lily-surface-alt);
2599
2647
  outline: var(--lily-focus-ring);
@@ -2602,7 +2650,7 @@
2602
2650
 
2603
2651
  /* aria-selected marks the applied theme/locale, which is not the
2604
2652
  same thing as the keyboard cursor. Both can be visible at once. */
2605
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2653
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2606
2654
  > :where([aria-selected="true"]) {
2607
2655
  font-weight: 600;
2608
2656
  background-color: var(--lily-surface-sunken);
package/dist/winter.css CHANGED
@@ -156,6 +156,14 @@
156
156
  /* Primary body-copy colour. Maps to the theme's high-contrast
157
157
  reading colour. */
158
158
  --lily-text: var(--color-base-content);
159
+ /* Error text: the theme's error colour pulled toward the theme's own reading colour (`--color-base-content`, not `--lily-text`, which a host page may override) so it
160
+ keeps WCAG contrast as text on the page and on the error-tinted
161
+ callout surface (`--color-error` itself is a fill colour). */
162
+ --lily-error-text: color-mix(
163
+ in oklch,
164
+ var(--color-error) 22%,
165
+ var(--color-base-content)
166
+ );
159
167
  /* Muted / secondary text — mixed 70% with transparent to keep the
160
168
  hue but lower contrast for captions, helptext, placeholders. */
161
169
  --lily-text-muted: color-mix(
@@ -895,7 +903,7 @@
895
903
  }
896
904
  :where(.dont-list-item)::marker {
897
905
  content: "✗ ";
898
- color: var(--color-error);
906
+ color: var(--lily-error-text);
899
907
  }
900
908
 
901
909
  /* --- Tree --------------------------------------------------------------- */
@@ -977,7 +985,7 @@
977
985
  color: var(--color-success);
978
986
  }
979
987
  :where(.validation-list-item[data-status="failed"]) {
980
- color: var(--color-error);
988
+ color: var(--lily-error-text);
981
989
  }
982
990
  :where(.validation-list-item[data-status="pending"]) {
983
991
  color: var(--lily-text-muted);
@@ -1335,7 +1343,7 @@
1335
1343
 
1336
1344
  /* Error message — inline form-validation message. */
1337
1345
  :where(.error-message) {
1338
- color: var(--color-error);
1346
+ color: var(--lily-error-text);
1339
1347
  font-weight: 600;
1340
1348
  }
1341
1349
  /* Error summary — heavyweight 4px border (GOV.UK pattern) so users
@@ -2549,6 +2557,7 @@
2549
2557
  .theme-picker-list,
2550
2558
  .locale-picker-list,
2551
2559
  .text-size-picker-list,
2560
+ .motion-picker-list,
2552
2561
  .share-picker-list,
2553
2562
  .search-picker-panel
2554
2563
  ) {
@@ -2574,17 +2583,56 @@
2574
2583
  .theme-picker-list,
2575
2584
  .locale-picker-list,
2576
2585
  .text-size-picker-list,
2586
+ .motion-picker-list,
2577
2587
  .share-picker-list,
2578
2588
  .search-picker-panel
2579
2589
  )[hidden] {
2580
2590
  display: none;
2581
2591
  }
2582
2592
 
2583
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2593
+ /* Popups open under their button, aligned to its start edge, and flip to
2594
+ its end edge (or above it) when they would run off the viewport. A header
2595
+ picker near the right edge otherwise overflows, and focusing the popup
2596
+ scrolls the whole page sideways. `position: fixed` makes the viewport the
2597
+ containing block that the flip fallbacks are checked against; browsers
2598
+ without anchor positioning keep the absolute placement above. */
2599
+ @supports (position-area: block-end) {
2600
+ :where(.theme-picker) { anchor-name: --lily-theme-picker; }
2601
+ :where(.locale-picker) { anchor-name: --lily-locale-picker; }
2602
+ :where(.text-size-picker) { anchor-name: --lily-text-size-picker; }
2603
+ :where(.motion-picker) { anchor-name: --lily-motion-picker; }
2604
+ :where(.share-picker) { anchor-name: --lily-share-picker; }
2605
+ :where(.search-picker) { anchor-name: --lily-search-picker; }
2606
+ :where(.theme-picker-list) { position-anchor: --lily-theme-picker; }
2607
+ :where(.locale-picker-list) { position-anchor: --lily-locale-picker; }
2608
+ :where(.text-size-picker-list) { position-anchor: --lily-text-size-picker; }
2609
+ :where(.motion-picker-list) { position-anchor: --lily-motion-picker; }
2610
+ :where(.share-picker-list) { position-anchor: --lily-share-picker; }
2611
+ :where(.search-picker-panel) { position-anchor: --lily-search-picker; }
2612
+ :where(
2613
+ .theme-picker-list,
2614
+ .locale-picker-list,
2615
+ .text-size-picker-list,
2616
+ .motion-picker-list,
2617
+ .share-picker-list,
2618
+ .search-picker-panel
2619
+ ) {
2620
+ position: fixed;
2621
+ inset: auto;
2622
+ position-area: block-end span-inline-end;
2623
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
2624
+ min-width: 0;
2625
+ width: max-content;
2626
+ max-width: calc(100vw - 1rem);
2627
+ }
2628
+ }
2629
+
2630
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2584
2631
  > :where(
2585
2632
  .theme-picker-option,
2586
2633
  .locale-picker-option,
2587
- .text-size-picker-option
2634
+ .text-size-picker-option,
2635
+ .motion-picker-option
2588
2636
  ) {
2589
2637
  padding: var(--lily-space-xs) var(--lily-space-sm);
2590
2638
  border-radius: var(--lily-radius-sm);
@@ -2593,7 +2641,7 @@
2593
2641
 
2594
2642
  /* data-active is the keyboard cursor (aria-activedescendant); focus
2595
2643
  stays on the listbox, so this is the only visible cue for it. */
2596
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2644
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2597
2645
  > :where([data-active]) {
2598
2646
  background-color: var(--lily-surface-alt);
2599
2647
  outline: var(--lily-focus-ring);
@@ -2602,7 +2650,7 @@
2602
2650
 
2603
2651
  /* aria-selected marks the applied theme/locale, which is not the
2604
2652
  same thing as the keyboard cursor. Both can be visible at once. */
2605
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2653
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2606
2654
  > :where([aria-selected="true"]) {
2607
2655
  font-weight: 600;
2608
2656
  background-color: var(--lily-surface-sunken);
@@ -156,6 +156,14 @@
156
156
  /* Primary body-copy colour. Maps to the theme's high-contrast
157
157
  reading colour. */
158
158
  --lily-text: var(--color-base-content);
159
+ /* Error text: the theme's error colour pulled toward the theme's own reading colour (`--color-base-content`, not `--lily-text`, which a host page may override) so it
160
+ keeps WCAG contrast as text on the page and on the error-tinted
161
+ callout surface (`--color-error` itself is a fill colour). */
162
+ --lily-error-text: color-mix(
163
+ in oklch,
164
+ var(--color-error) 22%,
165
+ var(--color-base-content)
166
+ );
159
167
  /* Muted / secondary text — mixed 70% with transparent to keep the
160
168
  hue but lower contrast for captions, helptext, placeholders. */
161
169
  --lily-text-muted: color-mix(
@@ -895,7 +903,7 @@
895
903
  }
896
904
  :where(.dont-list-item)::marker {
897
905
  content: "✗ ";
898
- color: var(--color-error);
906
+ color: var(--lily-error-text);
899
907
  }
900
908
 
901
909
  /* --- Tree --------------------------------------------------------------- */
@@ -977,7 +985,7 @@
977
985
  color: var(--color-success);
978
986
  }
979
987
  :where(.validation-list-item[data-status="failed"]) {
980
- color: var(--color-error);
988
+ color: var(--lily-error-text);
981
989
  }
982
990
  :where(.validation-list-item[data-status="pending"]) {
983
991
  color: var(--lily-text-muted);
@@ -1335,7 +1343,7 @@
1335
1343
 
1336
1344
  /* Error message — inline form-validation message. */
1337
1345
  :where(.error-message) {
1338
- color: var(--color-error);
1346
+ color: var(--lily-error-text);
1339
1347
  font-weight: 600;
1340
1348
  }
1341
1349
  /* Error summary — heavyweight 4px border (GOV.UK pattern) so users
@@ -2549,6 +2557,7 @@
2549
2557
  .theme-picker-list,
2550
2558
  .locale-picker-list,
2551
2559
  .text-size-picker-list,
2560
+ .motion-picker-list,
2552
2561
  .share-picker-list,
2553
2562
  .search-picker-panel
2554
2563
  ) {
@@ -2574,17 +2583,56 @@
2574
2583
  .theme-picker-list,
2575
2584
  .locale-picker-list,
2576
2585
  .text-size-picker-list,
2586
+ .motion-picker-list,
2577
2587
  .share-picker-list,
2578
2588
  .search-picker-panel
2579
2589
  )[hidden] {
2580
2590
  display: none;
2581
2591
  }
2582
2592
 
2583
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2593
+ /* Popups open under their button, aligned to its start edge, and flip to
2594
+ its end edge (or above it) when they would run off the viewport. A header
2595
+ picker near the right edge otherwise overflows, and focusing the popup
2596
+ scrolls the whole page sideways. `position: fixed` makes the viewport the
2597
+ containing block that the flip fallbacks are checked against; browsers
2598
+ without anchor positioning keep the absolute placement above. */
2599
+ @supports (position-area: block-end) {
2600
+ :where(.theme-picker) { anchor-name: --lily-theme-picker; }
2601
+ :where(.locale-picker) { anchor-name: --lily-locale-picker; }
2602
+ :where(.text-size-picker) { anchor-name: --lily-text-size-picker; }
2603
+ :where(.motion-picker) { anchor-name: --lily-motion-picker; }
2604
+ :where(.share-picker) { anchor-name: --lily-share-picker; }
2605
+ :where(.search-picker) { anchor-name: --lily-search-picker; }
2606
+ :where(.theme-picker-list) { position-anchor: --lily-theme-picker; }
2607
+ :where(.locale-picker-list) { position-anchor: --lily-locale-picker; }
2608
+ :where(.text-size-picker-list) { position-anchor: --lily-text-size-picker; }
2609
+ :where(.motion-picker-list) { position-anchor: --lily-motion-picker; }
2610
+ :where(.share-picker-list) { position-anchor: --lily-share-picker; }
2611
+ :where(.search-picker-panel) { position-anchor: --lily-search-picker; }
2612
+ :where(
2613
+ .theme-picker-list,
2614
+ .locale-picker-list,
2615
+ .text-size-picker-list,
2616
+ .motion-picker-list,
2617
+ .share-picker-list,
2618
+ .search-picker-panel
2619
+ ) {
2620
+ position: fixed;
2621
+ inset: auto;
2622
+ position-area: block-end span-inline-end;
2623
+ position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
2624
+ min-width: 0;
2625
+ width: max-content;
2626
+ max-width: calc(100vw - 1rem);
2627
+ }
2628
+ }
2629
+
2630
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2584
2631
  > :where(
2585
2632
  .theme-picker-option,
2586
2633
  .locale-picker-option,
2587
- .text-size-picker-option
2634
+ .text-size-picker-option,
2635
+ .motion-picker-option
2588
2636
  ) {
2589
2637
  padding: var(--lily-space-xs) var(--lily-space-sm);
2590
2638
  border-radius: var(--lily-radius-sm);
@@ -2593,7 +2641,7 @@
2593
2641
 
2594
2642
  /* data-active is the keyboard cursor (aria-activedescendant); focus
2595
2643
  stays on the listbox, so this is the only visible cue for it. */
2596
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2644
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2597
2645
  > :where([data-active]) {
2598
2646
  background-color: var(--lily-surface-alt);
2599
2647
  outline: var(--lily-focus-ring);
@@ -2602,7 +2650,7 @@
2602
2650
 
2603
2651
  /* aria-selected marks the applied theme/locale, which is not the
2604
2652
  same thing as the keyboard cursor. Both can be visible at once. */
2605
- :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list)
2653
+ :where(.theme-picker-list, .locale-picker-list, .text-size-picker-list, .motion-picker-list)
2606
2654
  > :where([aria-selected="true"]) {
2607
2655
  font-weight: 600;
2608
2656
  background-color: var(--lily-surface-sunken);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lilydesignsystem/themes",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "engines": {
5
5
  "node": "=26"
6
6
  },