@slotted/styles 0.0.0-stage → 0.1.0-beta.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.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,754 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * One surface for four members. Button, ButtonLink, IconButton and
4
+ * ToggleButton are one control with different jobs — an action, a
5
+ * navigation, an action with no room for a label, and an action that stays
6
+ * on — so they share an identifier, a geometry scale and a state machine,
7
+ * and differ only in what they contain.
8
+ *
9
+ * Every geometric decision is read from `--slotted-control-*` rather than
10
+ * aliased into `--slotted-button-*`. The alias looked harmless and was not:
11
+ * the theme raises the control tokens under `@media (pointer: coarse)`, and
12
+ * a family reading its own copy never grew to the touch target.
13
+ *
14
+ * The tone roles travel through private properties because three variants
15
+ * paint the same roles differently; naming them once per tone is what keeps
16
+ * the variant rules from repeating five colour maps each.
17
+ *
18
+ * Nothing in this sheet changes a size when a state changes. Every state is
19
+ * a colour; the only one drawn outside the border box is focus, which is an
20
+ * outline. The rebuild that preceded this one thickened the border on a
21
+ * pressed toggle, which moved every control beside it by two pixels.
22
+ */
23
+ [data-ui='button'] {
24
+ --_solid: var(--slotted-tone-accent-solid);
25
+ --_solid-hover: var(--slotted-tone-accent-solid-hover);
26
+ --_solid-active: var(--slotted-tone-accent-solid-active);
27
+ --_on-solid: var(--slotted-tone-accent-on-solid);
28
+ --_text: var(--slotted-tone-accent-text);
29
+ /**
30
+ * The edge of a filled control is the fill itself. A border a shade away
31
+ * from what it encloses reads as a halo around the shape rather than as
32
+ * its edge, and one borrowed from another role reads as a line laid over
33
+ * the control. One tone overrides it, and only just.
34
+ */
35
+ --_edge: var(--_solid);
36
+
37
+ --_height: var(--slotted-control-height-md, 34px);
38
+ --_padding-inline: var(--slotted-control-padding-inline-md, 13px);
39
+ --_font-size: var(--slotted-control-font-size-md, 13px);
40
+ --_icon-size: var(--slotted-control-icon-size-md, 16px);
41
+ --_gap: var(--slotted-control-gap-md, 8px);
42
+ /**
43
+ * A glyph reaches closer to the edge than a letter does, so the side that
44
+ * holds one is inset less. Without the correction an icon looks pushed
45
+ * into the middle of the control while the label looks pinned to its
46
+ * edge — the same padding measured against two different shapes.
47
+ */
48
+ --_padding-inline-visual: max(
49
+ var(--slotted-space-2, 0.25rem),
50
+ calc(var(--_padding-inline) - var(--slotted-space-2, 0.25rem))
51
+ );
52
+ /**
53
+ * The band the control waits behind, measured against the control rather
54
+ * than in pixels: a fixed length reads as a wash across a small button and
55
+ * as a glint across a wide one. Two heights is the tile, so a button that
56
+ * holds a phrase carries two or three bands rather than one long smear —
57
+ * a train crossing the control is what reads as waiting, and one band wide
58
+ * enough to cover it reads as a gradient someone left on.
59
+ */
60
+ --_sweep-width: calc(var(--_height) * 2);
61
+ /**
62
+ * A band of the control's own ink over the control's own fill, and the two
63
+ * grounds do not take it the same way. A saturated fill is already far from
64
+ * the page, so the band has only the distance left between the fill and the
65
+ * label to work in and needs most of it; a bare variant lays the same ink
66
+ * straight on the page, where nothing dilutes it and half as much reads
67
+ * louder. Both numbers were read in `light`, `dark` and `high-contrast`
68
+ * rather than preferred: above these the filled band stops being a band and
69
+ * becomes a gloss over the fill, and the one tone whose label is dark turns
70
+ * it into a shadow.
71
+ */
72
+ --_sweep-strength: 0.34;
73
+
74
+ align-items: center;
75
+ appearance: none;
76
+ background: transparent;
77
+ border: var(--slotted-control-border-width, 1px) solid transparent;
78
+ border-radius: var(--slotted-control-radius, 4px);
79
+ box-sizing: border-box;
80
+ cursor: pointer;
81
+ display: inline-flex;
82
+ font-family: var(--slotted-control-font-family, ui-sans-serif, system-ui, sans-serif);
83
+ font-size: var(--_font-size);
84
+ font-weight: var(--slotted-control-font-weight, 600);
85
+ gap: var(--_gap);
86
+ justify-content: center;
87
+ letter-spacing: var(--slotted-control-letter-spacing, 0);
88
+ line-height: var(--slotted-control-line-height, 1);
89
+ /**
90
+ * A control never pushes past the container it was placed in, whatever it
91
+ * holds. With the label allowed to wrap and to break inside a word, this
92
+ * is what turns an unbreakable file name into a wrapped one instead of a
93
+ * horizontal scrollbar on the page.
94
+ */
95
+ max-inline-size: 100%;
96
+ /**
97
+ * A minimum rather than a height, because the label wraps: a control that
98
+ * refuses to wrap pushes a long action past the edge of a narrow
99
+ * container.
100
+ *
101
+ * There is no block padding under it, and that is measured rather than
102
+ * preferred. A padding is added to the label's own line box, so at the
103
+ * `dense` density — where the small step is 24px and the medium 26px, and
104
+ * the line box is already 17.4px — four pixels of padding took both steps
105
+ * to the same 27.4 pixels. The density stopped meaning the height the
106
+ * theme states, and two steps of the scale collapsed into one. A wrapped
107
+ * label is kept off the border by the half-leading its own line height
108
+ * already carries, which is the same three pixels the padding was adding.
109
+ */
110
+ min-block-size: var(--_height);
111
+ padding-block: 0;
112
+ padding-inline: var(--_padding-inline);
113
+ /* The loading indicator is placed against this box. */
114
+ position: relative;
115
+ text-align: center;
116
+ text-decoration: none;
117
+ /** A single-word difference between two lines is worse than a shorter first one. */
118
+ text-wrap: balance;
119
+ /* No tap delay waiting for a second tap the control cannot use. */
120
+ touch-action: manipulation;
121
+ transition:
122
+ background-color var(--slotted-control-transition-duration)
123
+ var(--slotted-control-transition-easing),
124
+ border-color var(--slotted-control-transition-duration)
125
+ var(--slotted-control-transition-easing),
126
+ box-shadow var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
127
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
128
+ /* The press below moves the control, and the release eases it back. */ transform
129
+ var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
130
+ user-select: none;
131
+ /* A control written inside a sentence sits on the text's centre, not on its baseline. */
132
+ vertical-align: middle;
133
+ -webkit-tap-highlight-color: transparent;
134
+ }
135
+
136
+ /* ── Tone: which of the five semantic colours the control speaks in ───── */
137
+
138
+ [data-ui='button'][data-tone='secondary'] {
139
+ --_solid: var(--slotted-tone-secondary-solid);
140
+ --_solid-hover: var(--slotted-tone-secondary-solid-hover);
141
+ --_solid-active: var(--slotted-tone-secondary-solid-active);
142
+ --_on-solid: var(--slotted-tone-secondary-on-solid);
143
+ --_text: var(--slotted-tone-secondary-text);
144
+ /**
145
+ * The one fill that cannot be its own edge: measured against the surface it
146
+ * sits on, `--slotted-tone-secondary-solid` is 1.23:1 in `light`, 1.25:1 in
147
+ * `dark` and 1.00:1 in `high-contrast`, where it is the page's own white. A
148
+ * neutral control with no edge at all is a control that cannot be found.
149
+ *
150
+ * The edge is the next darker step of the tone's own ramp — the active
151
+ * fill — and nothing stronger. It was the hover fill while a hover
152
+ * darkened; a hover now lightens (the rule is in the theme), and an edge
153
+ * lighter than what it encloses is no edge. The tone's `border` role was
154
+ * tried and is far too loud: a filled neutral button is a quiet control,
155
+ * and an edge that announces itself turns it into an outline button
156
+ * wearing a fill.
157
+ */
158
+ --_edge: var(--_solid-active);
159
+ }
160
+
161
+ [data-ui='button'][data-tone='success'] {
162
+ --_solid: var(--slotted-tone-success-solid);
163
+ --_solid-hover: var(--slotted-tone-success-solid-hover);
164
+ --_solid-active: var(--slotted-tone-success-solid-active);
165
+ --_on-solid: var(--slotted-tone-success-on-solid);
166
+ --_text: var(--slotted-tone-success-text);
167
+ }
168
+
169
+ [data-ui='button'][data-tone='warning'] {
170
+ --_solid: var(--slotted-tone-warning-solid);
171
+ --_solid-hover: var(--slotted-tone-warning-solid-hover);
172
+ --_solid-active: var(--slotted-tone-warning-solid-active);
173
+ --_on-solid: var(--slotted-tone-warning-on-solid);
174
+ --_text: var(--slotted-tone-warning-text);
175
+ }
176
+
177
+ [data-ui='button'][data-tone='danger'] {
178
+ --_solid: var(--slotted-tone-danger-solid);
179
+ --_solid-hover: var(--slotted-tone-danger-solid-hover);
180
+ --_solid-active: var(--slotted-tone-danger-solid-active);
181
+ --_on-solid: var(--slotted-tone-danger-on-solid);
182
+ --_text: var(--slotted-tone-danger-text);
183
+ }
184
+
185
+ /* ── Size: the control scale, one step of it per member instance ──────── */
186
+
187
+ [data-ui='button'][data-size='sm'] {
188
+ --_height: var(--slotted-control-height-sm, 30px);
189
+ --_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
190
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
191
+ --_icon-size: var(--slotted-control-icon-size-sm, 14px);
192
+ --_gap: var(--slotted-control-gap-sm, 6px);
193
+ }
194
+
195
+ [data-ui='button'][data-size='lg'] {
196
+ --_height: var(--slotted-control-height-lg, 38px);
197
+ --_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
198
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
199
+ --_icon-size: var(--slotted-control-icon-size-lg, 18px);
200
+ --_gap: var(--slotted-control-gap-lg, 8px);
201
+ }
202
+
203
+ /**
204
+ * ── Variant: how the tone is painted ──────────────────────────────────
205
+ *
206
+ * Three, not four. `soft` painted the tone's own surface and was removed at
207
+ * the visual review: a pale tinted fill read as a control on its way to
208
+ * being unavailable rather than as a quieter action, and three ways to
209
+ * paint one tone is one more than a page can tell apart at a glance. The
210
+ * `surface` role each tone carries belongs to the families a tinted
211
+ * rectangle is — `badge`, `tag`, `alert` — and this family no longer reads
212
+ * it.
213
+ *
214
+ * Every variant that is not `ghost` draws an edge, and the edge is the
215
+ * tone's `border` role rather than the fill repeated. On a filled control
216
+ * that reads as a rim a shade away from the fill; on the neutral tone,
217
+ * whose solid and surface are the same pale grey, it is the whole
218
+ * difference between a secondary action and a disabled one.
219
+ */
220
+
221
+ /**
222
+ * ── Elevation, and the one variant that has none ──────────────────────
223
+ *
224
+ * `--slotted-control-shadow` is, in the token foundation's own words, "the
225
+ * shadow a framed control carries at rest": it separates a control from the
226
+ * ground it sits on so the border can soften. That makes it a property of
227
+ * the frame, not of the family, and it is written on the two variants that
228
+ * draw one.
229
+ *
230
+ * `ghost` draws neither a fill nor an edge. It is a label the pointer can
231
+ * press, and a label that casts a shadow is a rectangle nobody drew — the
232
+ * control announces a box on a ground it is supposed to sit flat against,
233
+ * and a toolbar of ghost actions turns into a row of invisible cards. The
234
+ * sheet carried the shadow on the base rule for one release and every
235
+ * member inherited it, which is the defect this split exists to close.
236
+ */
237
+ [data-ui='button'][data-variant='solid'] {
238
+ background: var(--_solid, ButtonFace);
239
+ border-color: var(--_edge, ButtonBorder);
240
+ box-shadow: var(--slotted-control-shadow, none);
241
+ color: var(--_on-solid, ButtonText);
242
+ }
243
+
244
+ /**
245
+ * The edge of an outline button is neutral and its label carries the tone.
246
+ * A saturated border on all five tones turned a row of actions into a row
247
+ * of status chips: the eye read five different colours of rectangle before
248
+ * it read a single word. The edge is the control step, the one a field
249
+ * carries at rest: reading `--slotted-border-strong` here made the outline
250
+ * button the darkest line on the form, and the foundation now keeps that
251
+ * step for the line between two surfaces.
252
+ */
253
+ [data-ui='button'][data-variant='outline'] {
254
+ background: transparent;
255
+ border-color: var(--slotted-control-border-color, ButtonBorder);
256
+ box-shadow: var(--slotted-control-shadow, none);
257
+ color: var(--_text, ButtonText);
258
+ }
259
+
260
+ [data-ui='button'][data-variant='ghost'] {
261
+ background: transparent;
262
+ border-color: transparent;
263
+ color: var(--_text, ButtonText);
264
+ }
265
+
266
+ /**
267
+ * The half of the pair for a variant with no fill of its own. `secondary` is
268
+ * the third case for the same reason it is the exception to every other fill
269
+ * rule: its solid step is the page's own colour, so it takes the band the way
270
+ * a bare control does.
271
+ */
272
+ [data-ui='button'][data-variant='outline'],
273
+ [data-ui='button'][data-variant='ghost'],
274
+ [data-ui='button'][data-variant='solid'][data-tone='secondary'] {
275
+ --_sweep-strength: 0.18;
276
+ }
277
+
278
+ /**
279
+ * ── Pressed ───────────────────────────────────────────────────────────
280
+ *
281
+ * A toggle that is on is a selected thing, and it is painted the way every
282
+ * selected thing in the foundation is (R1): the accent selection ground
283
+ * under the accent text, inside the accent edge — whatever tone and variant
284
+ * the toggle was written as, because "on" is one colour across the system
285
+ * and not a shade of each control's own. It took the tone's solid fill
286
+ * before this, which made a neutral toggle that was on the same rectangle
287
+ * as one being pressed (`#e2e8f0` for both in `light`) and made a toggle
288
+ * that was on look like a primary action.
289
+ *
290
+ * The ground is the tone's `subtle-active` step: the same value as its
291
+ * `surface` in `light` and `dark`, and in `high-contrast` the one tinted
292
+ * step the scheme keeps (`#d3e0fa`) where the surface is the page's white.
293
+ * It sits above hover and below loading and disabled, which is the order
294
+ * `STATE_PRECEDENCE` sets.
295
+ */
296
+ [data-ui='button'][data-pressed]:not(:disabled):not([data-disabled]) {
297
+ background: var(--slotted-tone-accent-subtle-active, Highlight);
298
+ border-color: var(--slotted-tone-accent-border, Highlight);
299
+ /* A ghost toggle that is on has taken a fill and an edge, so it is a framed
300
+ control and carries what one carries. Without this the same toggle would
301
+ be lifted or flat depending on the variant it was written as. */
302
+ box-shadow: var(--slotted-control-shadow, none);
303
+ color: var(--slotted-tone-accent-text, HighlightText);
304
+ }
305
+
306
+ /**
307
+ * ── Hover and active, held back for every stronger state ──────────────
308
+ *
309
+ * A hovered control changes one thing, its fill, in one step of the
310
+ * control's transition. A filled control lightens: `--slotted-tone-*-solid-
311
+ * hover` is the fill moved a share of the way toward white, and the theme
312
+ * derives it from the fill by one rule rather than choosing it beside it. A
313
+ * bare control takes the neutral hover ground whatever its tone, because a
314
+ * tinted ground under an outline or ghost action read as five different
315
+ * status chips before it read as five hovered buttons; the tone stays in
316
+ * the label, where it was.
317
+ *
318
+ * A press answers at once and releases at the control's own pace. The
319
+ * active rules set the transition into the state to the instant step,
320
+ * so the fill, the shadow and the shift land on the same frame the button
321
+ * went down on; the base rule's duration is what the release eases out
322
+ * with, because a transition's timing is read from the state being
323
+ * entered. A filled control drops one hairline and loses its shadow —
324
+ * Primer's press, chosen over Radix's brightness filter: the control that
325
+ * was lifted off the page is put down on it. A bare control has no shadow
326
+ * to lose and nothing to put down, so it takes the darker neutral ground
327
+ * and nothing moves.
328
+ */
329
+
330
+ [data-ui='button'][data-variant='solid']:hover:not(:disabled):not([data-disabled]):not(
331
+ [data-loading]
332
+ ):not([data-pressed]) {
333
+ background: var(--_solid-hover, Highlight);
334
+ }
335
+
336
+ [data-ui='button'][data-variant='solid']:active:not(:disabled):not([data-disabled]):not(
337
+ [data-loading]
338
+ ):not([data-pressed]) {
339
+ background: var(--_solid-active, Highlight);
340
+ box-shadow: none;
341
+ transform: translateY(var(--slotted-border-width-thin, 1px));
342
+ transition-duration: var(--slotted-duration-instant);
343
+ }
344
+
345
+ [data-ui='button'][data-variant='outline']:hover:not(:disabled):not([data-disabled]):not(
346
+ [data-loading]
347
+ ):not([data-pressed]),
348
+ [data-ui='button'][data-variant='ghost']:hover:not(:disabled):not([data-disabled]):not(
349
+ [data-loading]
350
+ ):not([data-pressed]) {
351
+ background: var(--slotted-tone-secondary-subtle-hover, ButtonFace);
352
+ }
353
+
354
+ [data-ui='button'][data-variant='outline']:active:not(:disabled):not([data-disabled]):not(
355
+ [data-loading]
356
+ ):not([data-pressed]),
357
+ [data-ui='button'][data-variant='ghost']:active:not(:disabled):not([data-disabled]):not(
358
+ [data-loading]
359
+ ):not([data-pressed]) {
360
+ background: var(--slotted-tone-secondary-subtle-active, ButtonFace);
361
+ transition-duration: var(--slotted-duration-instant);
362
+ }
363
+
364
+ /**
365
+ * A toggle that is already on still answers the pointer, on the accent
366
+ * ground's own hover step and never on the neutral one a toggle that is off
367
+ * hovers with — the two states share no surface in any of theirs.
368
+ */
369
+ [data-ui='button'][data-pressed]:hover:not(:disabled):not([data-disabled]):not([data-loading]) {
370
+ background: var(--slotted-tone-accent-subtle-hover, Highlight);
371
+ }
372
+
373
+ [data-ui='button'][data-pressed]:active:not(:disabled):not([data-disabled]):not([data-loading]) {
374
+ background: var(--slotted-tone-accent-subtle-active, Highlight);
375
+ transition-duration: var(--slotted-duration-instant);
376
+ }
377
+
378
+ /* ── Focus ────────────────────────────────────────────────────────────── */
379
+
380
+ [data-ui='button']:focus-visible {
381
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
382
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
383
+ }
384
+
385
+ /**
386
+ * ── Disabled ──────────────────────────────────────────────────────────
387
+ *
388
+ * Per variant, because a flat control that fills with grey on the way to
389
+ * being unavailable reads as a different control rather than as the same
390
+ * one unavailable. A ghost button stays flat and loses only its colour.
391
+ *
392
+ * `[data-disabled]` rather than `:disabled` alone, because a control that
393
+ * keeps its place in the tab sequence carries `aria-disabled` and no
394
+ * `disabled` attribute; the attribute the contract declares is the one that
395
+ * is always there.
396
+ */
397
+
398
+ [data-ui='button']:disabled,
399
+ [data-ui='button'][data-disabled] {
400
+ /* Nothing a person cannot reach is lifted off the page — the rule
401
+ `text-field` states for the same shadow. */
402
+ box-shadow: none;
403
+ color: var(--slotted-disabled-foreground, GrayText);
404
+ cursor: not-allowed;
405
+ }
406
+
407
+ [data-ui='button'][data-variant='solid']:disabled,
408
+ [data-ui='button'][data-variant='solid'][data-disabled] {
409
+ background: var(--slotted-disabled-background, ButtonFace);
410
+ /**
411
+ * The disabled border rather than the disabled surface, because in the
412
+ * `high-contrast` scheme that surface is the page's white and the control
413
+ * would lose its edge exactly where an edge matters most.
414
+ */
415
+ border-color: var(--slotted-disabled-border, ButtonBorder);
416
+ }
417
+
418
+ [data-ui='button'][data-variant='outline']:disabled,
419
+ [data-ui='button'][data-variant='outline'][data-disabled] {
420
+ background: transparent;
421
+ border-color: var(--slotted-disabled-border, GrayText);
422
+ }
423
+
424
+ /**
425
+ * A toggle that is on and unavailable takes the disabled fill and the
426
+ * disabled edge, like any other unavailable control. What still says it is on
427
+ * is `aria-pressed`, which the platform announces whether or not the control
428
+ * can be operated.
429
+ */
430
+ [data-ui='button'][data-pressed]:disabled,
431
+ [data-ui='button'][data-pressed][data-disabled] {
432
+ background: var(--slotted-disabled-background, ButtonFace);
433
+ border-color: var(--slotted-disabled-border, GrayText);
434
+ color: var(--slotted-disabled-foreground, GrayText);
435
+ }
436
+
437
+ /**
438
+ * ── Unavailable, and still reachable ──────────────────────────────────
439
+ *
440
+ * Being unavailable and being unreachable are two decisions, and the sheet
441
+ * painted them as one: a control that stays in the tab sequence carrying
442
+ * `aria-disabled` — because it holds a tooltip, an explanation, a place in a
443
+ * toolbar — wore the same grey as one the platform took out, and a disabled
444
+ * accent action was the same rectangle as a resting neutral one (the fill,
445
+ * the edge and the ground of `solid secondary` are the disabled tokens'
446
+ * values in `light`).
447
+ *
448
+ * A control a person can reach has to be read, so its label is muted text
449
+ * rather than disabled text: `--slotted-text-muted` on the neutral subtle
450
+ * ground is 4.97:1 in `light`, 6.91:1 in `dark` and 11.76:1 in
451
+ * `high-contrast`, where the disabled pair is held to 3:1 and sits at 3.86.
452
+ * The ground is the quiet neutral step and not the neutral fill, because the
453
+ * neutral fill is what a secondary action rests on; the edge is the subtle
454
+ * border, one step quieter than any control at rest; nothing is lifted and
455
+ * nothing answers the pointer. `:not(:disabled)` is the whole difference:
456
+ * a natively disabled control keeps the disabled tokens above, and a ghost
457
+ * stays flat and takes only the text.
458
+ */
459
+ [data-ui='button'][data-disabled]:not(:disabled) {
460
+ color: var(--slotted-text-muted, GrayText);
461
+ }
462
+
463
+ [data-ui='button'][data-variant='solid'][data-disabled]:not(:disabled),
464
+ [data-ui='button'][data-pressed][data-disabled]:not(:disabled) {
465
+ background: var(--slotted-tone-secondary-subtle-hover, ButtonFace);
466
+ border-color: var(--slotted-border-subtle, GrayText);
467
+ color: var(--slotted-text-muted, GrayText);
468
+ }
469
+
470
+ [data-ui='button'][data-variant='outline'][data-disabled]:not(:disabled) {
471
+ border-color: var(--slotted-border-subtle, GrayText);
472
+ }
473
+
474
+ /**
475
+ * ── Loading ───────────────────────────────────────────────────────────
476
+ *
477
+ * The ring is what the control falls back to, and the sweep below is what it
478
+ * shows when the page can carry one. Both are written, in that order, so an
479
+ * engine that drops the sweep is left with a state rather than with nothing.
480
+ *
481
+ * The ring takes two shapes, decided by what the control already holds.
482
+ *
483
+ * A control with a start or end visual keeps its label and gives the ring
484
+ * that one box: the wait is legible, the action stays readable, and the box
485
+ * was already the size of the ring. Primer takes the same decision, and it
486
+ * is the one that stops a loading button from being a coloured rectangle
487
+ * with nothing in it.
488
+ *
489
+ * A control with no visual has nowhere to put the ring but over its own
490
+ * label, so the label goes to `opacity: 0` — in the flow, so the control
491
+ * cannot change size, and in the accessibility tree, so the name it
492
+ * supplies survives the wait and combines with the indicator's own.
493
+ */
494
+
495
+ [data-ui='button'][data-loading] {
496
+ cursor: progress;
497
+ }
498
+
499
+ [data-ui='button'][data-loading]:has(> [data-part='indicator']) > :not([data-part='indicator']) {
500
+ opacity: 0;
501
+ }
502
+
503
+ /**
504
+ * The ring is the `spinner` sheet's: one arc, drawn once, for every family
505
+ * that waits. This family only says how big it is — the glyph box of its
506
+ * own step — and where it sits.
507
+ */
508
+ [data-ui='button'] [data-part='indicator'] {
509
+ --slotted-spinner-size: var(--_icon-size);
510
+
511
+ flex: none;
512
+ }
513
+
514
+ /* Only the ring that has no visual slot to sit in is placed against the box. */
515
+ [data-ui='button'] > [data-part='indicator'] {
516
+ inset: 0;
517
+ /* `inset: 0` with a definite size and automatic margins centres on both
518
+ axes without naming a physical side. */
519
+ margin: auto;
520
+ position: absolute;
521
+ }
522
+
523
+ /**
524
+ * ── The sweep ─────────────────────────────────────────────────────────
525
+ *
526
+ * A band of the control's own ink crosses it for as long as it waits. It
527
+ * replaces the ring on every control that has words in it, and what it buys
528
+ * is the words: the ring had to be drawn over the label of a control with no
529
+ * visual slot, so the reader lost the name of the action they had just
530
+ * pressed. The band is painted behind everything instead, so the label and
531
+ * the glyph both stay where they were and nothing in the control moves.
532
+ *
533
+ * The ring stays in the document. It is the element that carries
534
+ * `progressbar` and the name of the wait, so it steps out of the flow rather
535
+ * than out of the tree, and the glyph it was standing in for takes its own
536
+ * width back.
537
+ *
538
+ * An icon-only control keeps the ring drawn. There is no label for a band to
539
+ * spare, and a sweep across a box the size of one glyph is a flicker rather
540
+ * than a direction.
541
+ *
542
+ * The band waits for the ring to be in the document, not for
543
+ * `data-loading`: the refusal starts at once, and `loadingDelay` holds back
544
+ * every picture of the wait, so a wait that ends inside the delay shows none.
545
+ *
546
+ * `currentColor` is what makes the band answer every tone and every variant
547
+ * without a token of its own: it is the ink the control is already painting
548
+ * its own label with, so it contrasts with that control's ground by
549
+ * construction — the on-tone colour over a filled one, the tone itself over
550
+ * a bare one, and whatever the scheme remapped either to.
551
+ */
552
+ @media (prefers-reduced-motion: no-preference) {
553
+ @media not (forced-colors: active) {
554
+ [data-ui='button']:not([data-shape])[data-loading]:has([data-part='indicator'])::after {
555
+ /**
556
+ * The tile is crossed once per cycle, so the pair sets the speed the
557
+ * band travels rather than the time it takes: two heights per loop
558
+ * step is a rate the eye reads as work in progress at every size.
559
+ */
560
+ animation: slotted-button-sweep var(--slotted-duration-loop) linear infinite;
561
+ background-image: linear-gradient(to right, transparent, currentColor 40%, transparent 80%);
562
+ /* One tile, travelled exactly once per cycle, is a loop with no seam. */
563
+ background-repeat: repeat-x;
564
+ background-size: var(--_sweep-width) 100%;
565
+ border-radius: inherit;
566
+ content: '';
567
+ inset: 0;
568
+ opacity: var(--_sweep-strength);
569
+ /* The band is over the label and must not take the press away from it. */
570
+ pointer-events: none;
571
+ position: absolute;
572
+ }
573
+
574
+ /**
575
+ * A band that travels against the reading direction reads as undoing
576
+ * rather than as progressing, so it is mirrored rather than reused.
577
+ */
578
+ [data-ui='button']:not([data-shape])[data-loading]:has([data-part='indicator']):dir(
579
+ rtl
580
+ )::after {
581
+ animation-direction: reverse;
582
+ background-image: linear-gradient(to left, transparent, currentColor 40%, transparent 80%);
583
+ }
584
+
585
+ [data-ui='button']:not([data-shape])[data-loading]:has(> [data-part='indicator'])
586
+ > :not([data-part='indicator']) {
587
+ opacity: 1;
588
+ }
589
+
590
+ [data-ui='button']:not([data-shape])[data-loading] [data-part='indicator'] {
591
+ opacity: 0;
592
+ position: absolute;
593
+ }
594
+ }
595
+ }
596
+
597
+ /* ── Parts ────────────────────────────────────────────────────────────── */
598
+
599
+ [data-ui='button'] [data-part='start'],
600
+ [data-ui='button'] [data-part='end'],
601
+ [data-ui='button'] [data-part='icon'] {
602
+ align-items: center;
603
+ block-size: var(--_icon-size);
604
+ box-sizing: border-box;
605
+ display: inline-flex;
606
+ flex: none;
607
+ font-size: var(--_icon-size);
608
+ inline-size: var(--_icon-size);
609
+ justify-content: center;
610
+ line-height: 1;
611
+ /* The ring that stands in for this glyph is placed against this box. */
612
+ position: relative;
613
+ }
614
+
615
+ [data-ui='button'] [data-part='start'] > *:not([data-part='indicator']),
616
+ [data-ui='button'] [data-part='end'] > *:not([data-part='indicator']),
617
+ [data-ui='button'] [data-part='icon'] > *:not([data-part='indicator']) {
618
+ block-size: 100%;
619
+ display: block;
620
+ inline-size: 100%;
621
+ }
622
+
623
+ /**
624
+ * The label carries a wrapping line height of its own: the control's line
625
+ * height is 1, which centres a single line inside the box and crushes the
626
+ * second one. A token longer than the container breaks instead of escaping.
627
+ */
628
+ [data-ui='button'] [data-part='label'] {
629
+ line-height: var(--slotted-line-height-sm, 1.45);
630
+ min-inline-size: 0;
631
+ overflow-wrap: anywhere;
632
+ }
633
+
634
+ /**
635
+ * A flex container centres the line box, and a line box is not symmetric
636
+ * around the letters inside it. Measured on this family, the word sat 1.2
637
+ * pixels above the centre of the glyph beside it at every step of every
638
+ * density except the two where the rounding of the font size made it 0.92
639
+ * and 0.16 — the label and the icon were centred against different things.
640
+ * `text-box` trims the line to the cap and the baseline, so what the flex
641
+ * container centres is the ink, and the offset is 0 everywhere.
642
+ *
643
+ * The trim is paid for rather than taken. Trimming a label that wraps also
644
+ * takes the half-leading off the top and bottom of the whole block, which is
645
+ * the only thing keeping a wrapped word off the border — measured, a
646
+ * two-line control lost 8.39 pixels of height and its descenders fell two
647
+ * pixels below the edge. The padding gives that leading back symmetrically,
648
+ * so the ink stays centred and the control is the height it was: 36.77
649
+ * pixels against 36.78 for the same two lines.
650
+ *
651
+ * Both terms are proportions of the type rather than steps of a scale.
652
+ * `0.75em` is the cap height the trim leaves standing, measured on the UI
653
+ * font at every step; the line height is the token the label already reads.
654
+ * A theme that changes the line height moves the padding with it, and one
655
+ * that changes the font moves the clearance of a wrapped word by a fraction
656
+ * of a pixel, which is the only thing the term governs.
657
+ */
658
+ @supports (text-box: trim-both cap alphabetic) {
659
+ [data-ui='button'] [data-part='label'] {
660
+ padding-block: calc((var(--slotted-line-height-sm, 1.45) * 1em - 0.75em) / 2);
661
+ text-box: trim-both cap alphabetic;
662
+ }
663
+ }
664
+
665
+ /**
666
+ * The side that holds a glyph is inset less than the side that holds a
667
+ * letter. An icon-only control is exempt, because both of its sides hold
668
+ * the same glyph and its inset is zero.
669
+ */
670
+ [data-ui='button']:not([data-shape]):has(> [data-part='start']) {
671
+ padding-inline-start: var(--_padding-inline-visual);
672
+ }
673
+
674
+ [data-ui='button']:not([data-shape]):has(> [data-part='end']) {
675
+ padding-inline-end: var(--_padding-inline-visual);
676
+ }
677
+
678
+ /* ── Inline size ──────────────────────────────────────────────────────── */
679
+
680
+ [data-ui='button'][data-full-width] {
681
+ inline-size: 100%;
682
+ }
683
+
684
+ /**
685
+ * An icon-only control is square from the control height, so its visual box
686
+ * and its target are the same box. The two minimums are the WCAG 2.2 floor
687
+ * stated by the family rather than inferred from the theme: a target this
688
+ * small in one axis is the family's to correct, and theme validation only
689
+ * guarantees the block axis.
690
+ */
691
+ [data-ui='button'][data-shape] {
692
+ aspect-ratio: 1;
693
+ inline-size: var(--_height);
694
+ min-block-size: max(var(--_height), var(--slotted-target-min, 24px));
695
+ min-inline-size: var(--slotted-target-min, 24px);
696
+ padding-block: 0;
697
+ padding-inline: 0;
698
+ }
699
+
700
+ [data-ui='button'][data-shape='circle'] {
701
+ border-radius: var(--slotted-radius-full, 9999px);
702
+ }
703
+
704
+ /**
705
+ * ── Forced colours ────────────────────────────────────────────────────
706
+ *
707
+ * The theme remaps every token it owns, so this block exists only for what
708
+ * the family paints itself: the transparent border of `ghost` and of a
709
+ * control at rest, which leaves it with no edge at all when the system
710
+ * replaces the palette. `Highlight` on a pressed toggle is the same repair
711
+ * for a fill the system has flattened.
712
+ */
713
+ @media (forced-colors: active) {
714
+ [data-ui='button'] {
715
+ border-color: ButtonBorder;
716
+ }
717
+
718
+ [data-ui='button'][data-pressed] {
719
+ border-color: Highlight;
720
+ }
721
+
722
+ [data-ui='button']:disabled,
723
+ [data-ui='button'][data-disabled] {
724
+ border-color: GrayText;
725
+ color: GrayText;
726
+ }
727
+ }
728
+ /**
729
+ * A frozen ring still communicates waiting. The spinner sheet stops itself
730
+ * under reduced motion; it is restated here because this is the family that
731
+ * shows the wait, and the sweep above is the loop this sheet owns.
732
+ */
733
+ @media (prefers-reduced-motion: reduce) {
734
+ [data-ui='button'] [data-part='indicator'] {
735
+ animation: none;
736
+ }
737
+ }
738
+ }
739
+
740
+ /**
741
+ * The band moves by exactly one tile of a repeating background, so the frame
742
+ * the loop ends on is the frame it started from and there is no seam. Moving
743
+ * the background rather than the box is what keeps it inside the control
744
+ * without a second element to clip it.
745
+ */
746
+ @keyframes slotted-button-sweep {
747
+ from {
748
+ background-position-x: calc(-1 * var(--_sweep-width));
749
+ }
750
+
751
+ to {
752
+ background-position-x: 0;
753
+ }
754
+ }