@slotted/styles 0.0.0-stage → 0.1.0-beta.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 (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,764 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The message ────────────────────────────────────────────────────────
4
+ *
5
+ * A block of text about the page, a section, or the thing beside it, with a
6
+ * mark saying how serious it is and — sometimes — a control that puts it
7
+ * away and a row of controls that act on it.
8
+ *
9
+ * The box is a `section`, not a live region. MDN's `alert` role reference
10
+ * says the role "should only be used for text content, not interactive
11
+ * elements such as links or buttons", and every alert worth shipping has a
12
+ * dismiss control. Four of the ten surveyed implementations write a live
13
+ * role on the box anyway and put buttons inside it; two write no live region
14
+ * at all and announce nothing. Here the region is `content`, which holds the
15
+ * title and the description and nothing else, and the actions and the
16
+ * dismiss control are its siblings outside it.
17
+ *
18
+ * The family owns its own container, because two of its layout decisions
19
+ * answer the width of the box rather than the width of the window: a run of
20
+ * actions stacks and stretches when the box is narrow, and a page banner
21
+ * puts its actions back on the message's own line when the box is wide.
22
+ */
23
+ [data-ui='alert'][data-part='root'] {
24
+ /**
25
+ * The tone's five roles, defaulted to `accent` so an alert with no stated
26
+ * tone is an informational one. Each tone below restates the same five;
27
+ * every one of them is a role the theme's contrast test measures, so the
28
+ * family never invents a colour and never mixes a ground the theme holds
29
+ * with a foreground it does not.
30
+ */
31
+ --_solid: var(--slotted-tone-accent-solid);
32
+ --_on-solid: var(--slotted-tone-accent-on-solid);
33
+ --_surface: var(--slotted-tone-accent-surface);
34
+ --_border: var(--slotted-tone-accent-border);
35
+ --_text: var(--slotted-tone-accent-text);
36
+
37
+ /**
38
+ * How the dismiss control answers a pointer, and it is one mechanism for
39
+ * all three variants rather than three.
40
+ *
41
+ * `tag` measured why the tone's own ramp cannot do it: in the light scheme
42
+ * `--slotted-tone-accent-subtle-hover` is #eff6ff against a #dbeafe ground
43
+ * and `subtle-active` is that ground exactly, so a control on a tinted
44
+ * alert either lightened on hover or did nothing at all on press. On a
45
+ * `solid` alert the same roles are calibrated against the page rather than
46
+ * against the tone's fill, and on `outline` the ground is not the family's
47
+ * to know.
48
+ *
49
+ * A state layer answers all three: the control's own text colour, at a low
50
+ * strength, over whatever ground it already has. `currentcolor` is legible
51
+ * on that ground by construction — it is the colour the words are read in —
52
+ * so a wash of it is always perceptible and never depends on a pair the
53
+ * theme does not measure. The cost is that a theme retunes it by strength
54
+ * rather than by colour, which is what the two component tokens are for.
55
+ *
56
+ * Ten and sixteen per cent, not eight and fourteen: white at eight per cent
57
+ * over the `danger` fill moved OKLab lightness by 0.019 in the light and
58
+ * dark schemes, under the 0.02 a surface step has to reach. Ten moves it by
59
+ * 0.025 there and by at least 0.044 on every soft and outline ground
60
+ * (`docs/history/designs/2026-09-25-alert-reui-dossier.md` § 4).
61
+ */
62
+ --_state-hover: color-mix(
63
+ in oklab,
64
+ currentcolor var(--slotted-alert-state-layer-hover, 10%),
65
+ transparent
66
+ );
67
+ --_state-active: color-mix(
68
+ in oklab,
69
+ currentcolor var(--slotted-alert-state-layer-active, 16%),
70
+ transparent
71
+ );
72
+
73
+ /**
74
+ * Type and the dismiss glyph come from the control scales, the only ones
75
+ * the theme restates per density, so an alert inside a dense table shrinks
76
+ * with the rows around it. The box's inset comes from the space scale,
77
+ * which does not vary by density — the cost `card` accepted for the same
78
+ * reason, and recorded in
79
+ * `docs/history/designs/2026-09-02-alert-family-design.md`: at `dense`
80
+ * the box keeps its inset while the type inside it shrinks.
81
+ *
82
+ * `--_font-size` is the *title's* size and the root's own, and the
83
+ * description steps down from it. That is what lets the mark be sized in
84
+ * `em` and land on the message's first line at every reach without a
85
+ * number saying how.
86
+ *
87
+ * The status mark is a filled disc a step above the title's `1em`, rounded
88
+ * to an even number of pixels so its centre falls on a whole pixel: 16 at
89
+ * 13-pixel type, 14 at 12, 16 at 14. A filled mark carries more weight per
90
+ * pixel than an outline, so it stays within a few pixels of the capitals
91
+ * and still reads as the loudest thing on the line.
92
+ */
93
+ --_font-size: var(--slotted-control-font-size-md, 13px);
94
+ --_description-font-size: var(--slotted-control-font-size-md, 13px);
95
+ --_glyph-size: var(--slotted-control-icon-size-md, 16px);
96
+ --_inner-size: var(--slotted-control-inner-size-md, 24px);
97
+ --_mark-size: round(nearest, 1.2em, 2px);
98
+ --_line-height: var(--slotted-line-height-md, 1.5);
99
+
100
+ /**
101
+ * Each measure is named once, and a reach moves only the value it falls
102
+ * back to. An application that retunes `--slotted-alert-padding-block`
103
+ * therefore retunes all three reaches, rather than discovering that the
104
+ * name it set is the one the default reach happens to read.
105
+ */
106
+ --_inset-block: var(--slotted-alert-padding-block, var(--slotted-space-5, 0.75rem));
107
+ --_inset-inline: var(--slotted-alert-padding-inline, var(--slotted-space-6, 1rem));
108
+ /**
109
+ * The distance from the mark to the words it belongs to: eight pixels at
110
+ * every reach (R5). One step below the box's own inset on purpose: read at
111
+ * 420 pixels with the inset and the gap equal, the mark sat as far from its
112
+ * own sentence as it did from the edge of the box, and belonged to neither.
113
+ * It does not step with the reach — the mark already does, through `em`,
114
+ * and a gap that shrank beside a smaller mark pulled the two apart twice.
115
+ */
116
+ --_column-gap: var(--slotted-alert-column-gap, var(--slotted-space-4, 0.5rem));
117
+ --_row-gap: var(--slotted-alert-row-gap, var(--slotted-space-5, 0.75rem));
118
+ --_text-gap: var(--slotted-alert-text-gap, var(--slotted-space-2, 0.25rem));
119
+ --_radius: var(--slotted-alert-radius, var(--slotted-radius-md, 6px));
120
+ --_border-width: var(--slotted-alert-border-width, var(--slotted-border-width-thin, 1px));
121
+
122
+ /**
123
+ * The dismiss control is an inner control, not a button (R2): its box is
124
+ * the glyph plus a step of air on each side, floored at WCAG 2.2 2.5.8's
125
+ * twenty-four pixels so the pointer target holds at every density. It
126
+ * used to take the smallest control height, which put a thirty-pixel
127
+ * square beside a sixteen-pixel mark and a line of text twenty tall.
128
+ */
129
+ --_dismiss-size: var(
130
+ --slotted-alert-dismiss-size,
131
+ max(
132
+ var(--slotted-target-min, 24px),
133
+ min(calc(var(--_glyph-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
134
+ )
135
+ );
136
+
137
+ /**
138
+ * Two composition changes that answer the box rather than the window.
139
+ * A container name rather than the anonymous form, because an alert inside
140
+ * a card inside a page would otherwise resolve against whichever ancestor
141
+ * happened to be a container first.
142
+ */
143
+ container-name: slotted-alert;
144
+ container-type: inline-size;
145
+ /**
146
+ * `container-type: inline-size` applies `contain: inline-size`, which makes
147
+ * the box's intrinsic width zero: it is the whole point of the containment,
148
+ * and it is why the width has to be stated rather than left to `auto`.
149
+ *
150
+ * Found in the rendered reference page rather than in a test. In normal
151
+ * flow the box filled its parent and nothing was wrong; placed in a column
152
+ * that shrink-wraps its children — a flex column aligned to `start`, which
153
+ * is what the workbench's own stage is — every alert collapsed to the width
154
+ * of one character and the message broke one letter per line. A consumer
155
+ * writing `<Stack>` around three alerts would have found the same thing.
156
+ */
157
+ inline-size: 100%;
158
+
159
+ background-color: transparent;
160
+ border: var(--_border-width) solid transparent;
161
+ border-radius: var(--_radius);
162
+ box-sizing: border-box;
163
+ color: var(--_text);
164
+ /**
165
+ * The distance between the mark and the words belongs to the mark, not to
166
+ * the grid. A grid `column-gap` is drawn between two tracks whether or not
167
+ * either holds anything, so an alert with no mark was indented by a gap in
168
+ * front of an empty column — the words started one gap further in than the
169
+ * inset, which is visible the moment two alerts sit one above the other.
170
+ */
171
+ column-gap: 0;
172
+ display: grid;
173
+ font-family: var(--slotted-alert-font-family, var(--slotted-control-font-family, sans-serif));
174
+ font-size: var(--_font-size);
175
+ /**
176
+ * Three columns: the mark, the words, and the control that puts the
177
+ * message away. The row of actions goes under the words rather than beside
178
+ * them, so a second row exists only when there are actions to put in it —
179
+ * a fixed two-row template leaves a row gap under every alert that has
180
+ * none, which is the defect a `grid-template-areas` version of this had.
181
+ */
182
+ grid-template-columns: auto minmax(0, 1fr) auto;
183
+ /**
184
+ * The line box is rounded to an even number of pixels. At 13-pixel type
185
+ * the scale's 1.5 gave 19.5, so a message of one line was 45.5 pixels
186
+ * tall and every control centred on the line landed on a half pixel. An
187
+ * even line also centres the 24- and 26-pixel dismiss control and the
188
+ * 30-pixel actions on whole pixels: 20 at 13, 22 at 14, 18 at 12.
189
+ */
190
+ line-height: round(nearest, calc(var(--_line-height) * 1em), 2px);
191
+ padding-block: var(--_inset-block);
192
+ padding-inline: var(--_inset-inline);
193
+ row-gap: var(--_row-gap);
194
+ /**
195
+ * The entry, which runs only where an arrival is the message (see the
196
+ * `@starting-style` rule below). A transition is read from the state being
197
+ * entered, so the open box carries the entering curve and the closed box
198
+ * below carries the leaving one.
199
+ */
200
+ transition:
201
+ opacity var(--slotted-duration-short) var(--slotted-easing-enter),
202
+ translate var(--slotted-duration-short) var(--slotted-easing-enter),
203
+ display var(--slotted-duration-short) var(--slotted-easing-enter);
204
+ transition-behavior: allow-discrete;
205
+ }
206
+
207
+ /**
208
+ * A closed alert keeps its element: a live region has to be in the document
209
+ * before its text changes, so an alert that unmounted could not announce the
210
+ * second time the message arrived. `display: none` is restated because the
211
+ * root's own `display: grid` would otherwise win over the user agent's rule
212
+ * for `[hidden]`.
213
+ *
214
+ * The exit. Something the reader was reading is going away, which is a
215
+ * change the eye has to follow; `display` is carried with it so the box
216
+ * holds its place until the fade is over rather than snapping out from
217
+ * under the content below.
218
+ */
219
+ [data-ui='alert'][data-part='root'][hidden] {
220
+ display: none;
221
+ opacity: 0;
222
+ transition:
223
+ opacity var(--slotted-duration-short) var(--slotted-easing-exit),
224
+ translate var(--slotted-duration-short) var(--slotted-easing-exit),
225
+ display var(--slotted-duration-short) var(--slotted-easing-exit);
226
+ translate: 0 calc(-1 * var(--slotted-space-2, 0.25rem));
227
+ }
228
+
229
+ /**
230
+ * The entry belongs to a live alert only. `@starting-style` also applies on
231
+ * first paint, so an unconditional entry would fade in every alert already
232
+ * on a settings page at once — motion that carries no information. A
233
+ * `polite` or `assertive` alert is the one whose arrival is itself the
234
+ * message: it is closed or empty until something happens, and the motion
235
+ * marks the moment it does. It travels the path the exit takes, in reverse.
236
+ */
237
+ @starting-style {
238
+ [data-ui='alert'][data-part='root']:is([data-live='polite'], [data-live='assertive']) {
239
+ opacity: 0;
240
+ translate: 0 calc(-1 * var(--slotted-space-2, 0.25rem));
241
+ }
242
+ }
243
+
244
+ /* ── Tone: the colour, and the mark that says the same thing ───────────
245
+ *
246
+ * Five tones and five marks, and the map is
247
+ * `specs/components/alert/contract.json` rather than a decision taken here:
248
+ * both bindings draw the glyph from it and the contract test reads it, and
249
+ * this sheet paints only the colour. It is
250
+ * the map `status-indicator` declares, so a status light and an alert about
251
+ * the same thing draw the same silhouette.
252
+ *
253
+ * The mark is what satisfies WCAG 2.2 1.4.1. Under forced colours every tone
254
+ * ground becomes one system colour and the five boxes are identical; the
255
+ * circle, the triangle and the crossed circle are what is left.
256
+ */
257
+
258
+ [data-ui='alert'][data-part='root'][data-tone='accent'] {
259
+ --_solid: var(--slotted-tone-accent-solid);
260
+ --_on-solid: var(--slotted-tone-accent-on-solid);
261
+ --_surface: var(--slotted-tone-accent-surface);
262
+ --_border: var(--slotted-tone-accent-border);
263
+ --_text: var(--slotted-tone-accent-text);
264
+ }
265
+
266
+ [data-ui='alert'][data-part='root'][data-tone='secondary'] {
267
+ --_solid: var(--slotted-tone-secondary-solid);
268
+ --_on-solid: var(--slotted-tone-secondary-on-solid);
269
+ --_surface: var(--slotted-tone-secondary-surface);
270
+ --_border: var(--slotted-tone-secondary-border);
271
+ --_text: var(--slotted-tone-secondary-text);
272
+ }
273
+
274
+ [data-ui='alert'][data-part='root'][data-tone='success'] {
275
+ --_solid: var(--slotted-tone-success-solid);
276
+ --_on-solid: var(--slotted-tone-success-on-solid);
277
+ --_surface: var(--slotted-tone-success-surface);
278
+ --_border: var(--slotted-tone-success-border);
279
+ --_text: var(--slotted-tone-success-text);
280
+ }
281
+
282
+ [data-ui='alert'][data-part='root'][data-tone='warning'] {
283
+ --_solid: var(--slotted-tone-warning-solid);
284
+ --_on-solid: var(--slotted-tone-warning-on-solid);
285
+ --_surface: var(--slotted-tone-warning-surface);
286
+ --_border: var(--slotted-tone-warning-border);
287
+ --_text: var(--slotted-tone-warning-text);
288
+ }
289
+
290
+ [data-ui='alert'][data-part='root'][data-tone='danger'] {
291
+ --_solid: var(--slotted-tone-danger-solid);
292
+ --_on-solid: var(--slotted-tone-danger-on-solid);
293
+ --_surface: var(--slotted-tone-danger-surface);
294
+ --_border: var(--slotted-tone-danger-border);
295
+ --_text: var(--slotted-tone-danger-text);
296
+ }
297
+
298
+ /* ── Variant: the three ways one tone is painted ───────────────────────
299
+ *
300
+ * The words take the tone's own `text` role in all three, because that is
301
+ * the one pair `packages/themes/test/default-theme.test.mjs` measures
302
+ * against these grounds: the tone's text on the tone's surface, and the
303
+ * tone's text on each of the three neutral surfaces. Painting a tinted
304
+ * ground with `--slotted-text` would be an unheld ratio, and the theme's own
305
+ * comment records that the first family to depend on one is the family that
306
+ * discovers the shortfall in a screenshot.
307
+ */
308
+
309
+ /**
310
+ * The default, and the shape seven of the ten surveyed implementations draw.
311
+ * It carries the tone's border as well as its surface: in `high-contrast`
312
+ * the tone surfaces are the page's own white, so a soft alert with no edge
313
+ * is a run of coloured text with no box around it — the finding `kbd`
314
+ * recorded about its own soft variant.
315
+ */
316
+ [data-ui='alert'][data-part='root'][data-variant='soft'] {
317
+ background-color: var(--_surface);
318
+ border-color: var(--_border);
319
+ }
320
+
321
+ /**
322
+ * The quiet painting, for the three grounds the theme measures a tone's text
323
+ * against: the page's surface, a raised one, a sunken one. It carries no
324
+ * ground of its own, which is what lets it sit inside a card without drawing
325
+ * a second rectangle around the message.
326
+ *
327
+ * It is not the variant for an unknown ground, and the harness fragment shows
328
+ * why: read on `--slotted-surface-inverse`, the tone's text is a colour the
329
+ * theme never measured against that ground and the words go quiet. A dark
330
+ * region is a scheme rather than a surface — an application states
331
+ * `data-slotted-scheme` on it, every tone role is restated, and `outline`
332
+ * reads again. `soft` and `solid` survive the inverted surface only because
333
+ * they repaint the ground before they write on it.
334
+ */
335
+ [data-ui='alert'][data-part='root'][data-variant='outline'] {
336
+ background-color: transparent;
337
+ border-color: var(--_border);
338
+ }
339
+
340
+ /**
341
+ * The message that must not be missed. `on-solid` is the foreground the
342
+ * theme's contrast test holds against `solid`, `solid-hover` and
343
+ * `solid-active` in every scheme, so the whole box — words, mark and dismiss
344
+ * control — takes it.
345
+ */
346
+ [data-ui='alert'][data-part='root'][data-variant='solid'] {
347
+ background-color: var(--_solid);
348
+ border-color: var(--_solid);
349
+ color: var(--_on-solid);
350
+ }
351
+
352
+ /* ── Reach: the same message at three scopes ───────────────────────────
353
+ *
354
+ * The guide's three recipes as one axis. It is not a size: a size would say
355
+ * the same message can be drawn large or small, and this says which scope
356
+ * the message applies to, with the geometry following from that.
357
+ */
358
+
359
+ /** Beside the thing it is about — a field group, a table row, a card. */
360
+ [data-ui='alert'][data-part='root'][data-reach='inline'] {
361
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
362
+ --_description-font-size: var(--slotted-control-font-size-sm, 12px);
363
+ --_glyph-size: var(--slotted-control-icon-size-sm, 14px);
364
+ --_inner-size: var(--slotted-control-inner-size-sm, 22px);
365
+ --_inset-block: var(--slotted-alert-padding-block, var(--slotted-space-4, 0.5rem));
366
+ --_inset-inline: var(--slotted-alert-padding-inline, var(--slotted-space-5, 0.75rem));
367
+ --_row-gap: var(--slotted-alert-row-gap, var(--slotted-space-4, 0.5rem));
368
+ --_radius: var(--slotted-alert-radius, var(--slotted-radius-sm, 4px));
369
+ }
370
+
371
+ /**
372
+ * Across a page or an application shell. It runs to both edges of whatever
373
+ * holds it, so it draws no corner and no inline edge — there is nothing
374
+ * there for an edge to separate — and keeps the one edge that does separate
375
+ * it from the page below.
376
+ *
377
+ * The title steps up while the description stays where it was, which is the
378
+ * one place in the family where the two type sizes differ.
379
+ */
380
+ [data-ui='alert'][data-part='root'][data-reach='page'] {
381
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
382
+ --_glyph-size: var(--slotted-control-icon-size-lg, 18px);
383
+ --_inner-size: var(--slotted-control-inner-size-lg, 28px);
384
+ /**
385
+ * A banner is much wider than it is tall, so its two insets are not equal:
386
+ * the inline one is a step wider than a section's because it has a page's
387
+ * width to sit inside, and the block one is a step deeper because twelve
388
+ * pixels over a title this size read as a strip rather than as a band.
389
+ */
390
+ --_inset-block: var(--slotted-alert-padding-block, var(--slotted-space-6, 1rem));
391
+ --_inset-inline: var(--slotted-alert-padding-inline, var(--slotted-space-7, 1.25rem));
392
+ --_radius: var(--slotted-alert-radius, 0);
393
+
394
+ border-block-start-width: 0;
395
+ border-inline-width: 0;
396
+ }
397
+
398
+ /** A solid page banner has no edge to lose, so it keeps all four. */
399
+ [data-ui='alert'][data-part='root'][data-reach='page'][data-variant='solid'] {
400
+ border-block-start-width: var(--_border-width);
401
+ border-inline-width: var(--_border-width);
402
+ }
403
+
404
+ /* ── The mark ─────────────────────────────────────────────────────────
405
+ *
406
+ * A filled disc on the title's first line, with the tone's symbol cut out
407
+ * of it: the binding's `Glyph` in its `solid` form, which fills the glyph's
408
+ * ring and cuts the symbol out of the fill with an SVG mask, so the ground
409
+ * shows through the symbol on every variant. Every tone's glyph is a
410
+ * circle, so every mark is the same round silhouette on the same centre;
411
+ * the symbol inside is what tells the five apart when colour is gone. A
412
+ * triangle was the one mark whose visual centre is not its box's centre,
413
+ * and it did not sit on the line the way the four circles did.
414
+ *
415
+ * The glyph's filled ring reaches radius 11 of its 24-unit box, so the box
416
+ * is 12/11 of the mark and the disc is the mark's own size; the box
417
+ * overhangs the slot by half a unit on each side, centred.
418
+ *
419
+ * The disc is painted in `currentcolor`: the tone's text role on `soft` and
420
+ * `outline`, which the theme measures against those grounds, and `on-solid`
421
+ * on `solid`, where the cut-out shows the fill beneath.
422
+ *
423
+ * The mark hangs from the first line's baseline, not from the top of the
424
+ * row. Grid baseline alignment puts the mark's bottom edge on the words'
425
+ * baseline, and a relative offset lowers it by half of what the mark exceeds
426
+ * the capitals by, so its centre is the centre of the capitals in whatever
427
+ * face the title is set in. A margin cannot do this: a box with no baseline
428
+ * of its own is aligned by its border edge, so a margin moves nothing. The
429
+ * offset is rounded to a whole pixel, and the mark is a whole number of
430
+ * pixels, so the distance from the baseline to the mark's edge is an
431
+ * integer: the browser snaps the glyphs and the drawing by the same amount
432
+ * and they cannot land a pixel apart. The negative block-start margin takes
433
+ * the mark's own height out of the baseline group, so the words sit where
434
+ * they sit with no mark at all. Layout containment keeps the glyph's own
435
+ * baseline out of the box's, so the box is aligned by its border edge.
436
+ *
437
+ * A mark the page supplies instead of the tone's takes the same box and its
438
+ * place on the first line, centred in it.
439
+ */
440
+ [data-ui='alert'] > [data-part='icon'] {
441
+ --slotted-icon-size: calc(var(--_mark-size) * 12 / 11);
442
+
443
+ align-items: center;
444
+ align-self: baseline;
445
+ block-size: var(--_mark-size);
446
+ contain: layout;
447
+ display: flex;
448
+ grid-column: 1;
449
+ grid-row: 1;
450
+ inline-size: var(--_mark-size);
451
+ inset-block-start: round(nearest, (var(--_mark-size) - 1cap) / 2, 1px);
452
+ justify-content: center;
453
+ margin-block-start: calc(-1 * var(--_mark-size));
454
+ margin-inline-end: var(--_column-gap);
455
+ position: relative;
456
+ }
457
+
458
+ /* ── The words, which are the live region ─────────────────────────────
459
+ *
460
+ * `min-inline-size: 0` is what lets the column shrink below its content
461
+ * inside the root's grid, and it is the half of the pair that
462
+ * `minmax(0, 1fr)` on the track cannot supply on its own. The same pair is
463
+ * restated inside: the words are a grid of their own, and its one track has
464
+ * to be clamped too, or an unbreakable word in the title sizes the track to
465
+ * itself and the words run past the box that holds them.
466
+ */
467
+ [data-ui='alert'] > [data-part='content'] {
468
+ /* The other half of the mark's baseline group: see the mark above. */
469
+ align-self: baseline;
470
+ display: grid;
471
+ grid-column: 2;
472
+ grid-row: 1;
473
+ grid-template-columns: minmax(0, 1fr);
474
+ min-inline-size: 0;
475
+ row-gap: var(--_text-gap);
476
+ }
477
+
478
+ /**
479
+ * `anywhere` breaks the one string a message is likeliest to carry that no
480
+ * language can break — a deployment id, a container digest, a compound noun,
481
+ * a host name without a hyphen — and, unlike `break-word`, lowers the
482
+ * min-content size the grid reads, so the track never grows past the box to
483
+ * fit the word. Nothing is removed: the whole string stays in the DOM, in the
484
+ * accessibility tree, and findable by a page search.
485
+ *
486
+ * The 240-pixel capture is what asked for it: with `break-word` the title
487
+ * `Zahlungsbestätigung` set the track to its own width and the words stood
488
+ * outside the box by a few pixels in every scheme; the digest under it broke
489
+ * at the track's edge rather than at the box's. An ordinary word breaks only
490
+ * in a column narrower than the word, which at this type size is a column
491
+ * too narrow for the message anyway.
492
+ */
493
+ [data-ui='alert'] [data-part='title'] {
494
+ /* A title rendered as a heading keeps the root's type rather than the
495
+ user agent's `1.17em`: the mark is `1em` of the root, and a 15-pixel
496
+ title beside a 13-pixel mark put the mark a pixel above its capitals. */
497
+ font-family: var(
498
+ --slotted-alert-title-font-family,
499
+ var(
500
+ --slotted-font-family-heading,
501
+ var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif)
502
+ )
503
+ );
504
+ font-size: inherit;
505
+ font-weight: var(--slotted-alert-title-font-weight, var(--slotted-font-weight-semibold, 600));
506
+ line-height: inherit;
507
+ margin: 0;
508
+ min-inline-size: 0;
509
+ overflow-wrap: anywhere;
510
+ }
511
+
512
+ [data-ui='alert'] [data-part='description'] {
513
+ font-size: var(--_description-font-size);
514
+ /* Its own line, rounded against its own size: a page banner's description
515
+ is set a step below its title, and inheriting the title's 22 pixels
516
+ would open its lines by two. */
517
+ line-height: round(nearest, calc(var(--_line-height) * 1em), 2px);
518
+ margin: 0;
519
+ min-inline-size: 0;
520
+ overflow-wrap: anywhere;
521
+ }
522
+
523
+ /* ── The actions ──────────────────────────────────────────────────────
524
+ *
525
+ * Under the words rather than beside them, which is where Primer, Polaris,
526
+ * Carbon and MUI all put them, and the only place they can wrap without
527
+ * taking the message's own column with them. They are outside `content`, so
528
+ * they are outside the live region: a reader hearing the message is not read
529
+ * the labels of two buttons in the middle of it.
530
+ */
531
+ [data-ui='alert'] > [data-part='actions'] {
532
+ align-items: center;
533
+ display: flex;
534
+ flex-wrap: wrap;
535
+ gap: var(--slotted-alert-actions-gap, var(--slotted-space-3, 0.375rem));
536
+ grid-column: 2;
537
+ grid-row: 2;
538
+ min-inline-size: 0;
539
+ }
540
+
541
+ /* ── The dismiss control ──────────────────────────────────────────────
542
+ *
543
+ * A real button with a real name, sized from the smallest control height so
544
+ * it clears the WCAG 2.2 2.5.8 minimum at every density.
545
+ *
546
+ * The negative margins are optical rather than decorative. The button's box
547
+ * is taller than the line it sits beside, so aligning the two boxes puts the
548
+ * glyph below the title's centre; pulling the box up by half the difference
549
+ * puts the glyph on the title's own line. The same distance is taken off the
550
+ * inline end, so the glyph — rather than the invisible hit area around it —
551
+ * sits at the inset the rest of the box uses.
552
+ *
553
+ * The block end takes it too, which is the half that was missing. A grid row
554
+ * is as tall as its tallest item, so a control pulled up by half its overhang
555
+ * and left to hang by the other half made the row taller than the line it sat
556
+ * on — and a message of one line with no description ended up with more room
557
+ * under it than over it. With both margins the control's margin box is
558
+ * exactly one line, and the inset above the message equals the inset below.
559
+ */
560
+ [data-ui='alert'] > [data-part='dismiss'] {
561
+ /* The cross is the binding's `Glyph` in an `icon` box at the glyph size
562
+ the formula for this box reads. */
563
+ --slotted-icon-size: var(--_glyph-size);
564
+
565
+ align-items: center;
566
+ align-self: start;
567
+ appearance: none;
568
+ background-color: transparent;
569
+ block-size: var(--_dismiss-size);
570
+ border: 0;
571
+ border-radius: var(--slotted-alert-dismiss-radius, var(--slotted-control-radius, 4px));
572
+ color: inherit;
573
+ cursor: pointer;
574
+ display: inline-flex;
575
+ flex: none;
576
+ /* The user agent gives every button a font of its own, and `1lh` below is
577
+ measured against whatever that font resolves to. Inheriting is what puts
578
+ the control's optical correction on the same line box as the title. */
579
+ font: inherit;
580
+ grid-column: 3;
581
+ grid-row: 1;
582
+ inline-size: var(--_dismiss-size);
583
+ justify-content: center;
584
+ margin-block-end: calc((1lh - var(--_dismiss-size)) / 2);
585
+ margin-block-start: calc((1lh - var(--_dismiss-size)) / 2);
586
+ margin-inline-end: calc((1lh - var(--_dismiss-size)) / 2);
587
+ margin-inline-start: var(--_column-gap);
588
+ padding: 0;
589
+ transition:
590
+ background-color var(--slotted-control-transition-duration)
591
+ var(--slotted-control-transition-easing),
592
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
593
+ }
594
+
595
+ /**
596
+ * `:enabled` is the guard `packages/styles/src/states.test.mjs` requires, and
597
+ * the reason it exists: a control the browser has already refused must not
598
+ * answer a pointer.
599
+ */
600
+ [data-ui='alert'] > [data-part='dismiss']:enabled:hover {
601
+ background-color: var(--_state-hover);
602
+ }
603
+
604
+ [data-ui='alert'] > [data-part='dismiss']:enabled:active {
605
+ background-color: var(--_state-active);
606
+ }
607
+
608
+ [data-ui='alert'] > [data-part='dismiss']:disabled {
609
+ color: var(--slotted-disabled-foreground);
610
+ cursor: default;
611
+ }
612
+
613
+ /* On a finger the target is the comfortable size in both axes and the box
614
+ is the one a mouse sees: a transparent box centred on the control reaches
615
+ 44 pixels, and the ground and the ring stay square. */
616
+ @media (pointer: coarse) {
617
+ [data-ui='alert'] > [data-part='dismiss'] {
618
+ position: relative;
619
+ }
620
+
621
+ [data-ui='alert'] > [data-part='dismiss']::after {
622
+ content: '';
623
+ inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
624
+ position: absolute;
625
+ }
626
+ }
627
+
628
+ /* ── Focus ────────────────────────────────────────────────────────────
629
+ *
630
+ * The ring on the dismiss control is inset, because the control sits at the
631
+ * corner of the box and a ring outside it would be drawn over the border or
632
+ * clipped by it. The ring on the box itself is outside, and it exists
633
+ * because the root carries `tabindex="-1"`: the family never moves focus
634
+ * there on its own — the APG is explicit that an alert must not affect
635
+ * keyboard focus — but a page that deliberately sends focus to a message
636
+ * must not send it somewhere invisible.
637
+ */
638
+ [data-ui='alert'] > [data-part='dismiss']:focus-visible {
639
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
640
+ outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
641
+ }
642
+
643
+ /**
644
+ * Measured rather than assumed: `--slotted-focus-ring-color` reads 1.30 : 1
645
+ * against `--slotted-tone-accent-solid` in the light scheme, 1.03 against
646
+ * `success` and 1.07 against `danger`. The ring the theme calibrated against
647
+ * the page's surface disappears on the tone's own fill. The ring keeps the
648
+ * theme's colour in every scheme (R3); what a solid alert adds is a halo one
649
+ * ring-width wide just inside it, in the ink its own words are written in,
650
+ * so the ring is read against that halo rather than against the fill.
651
+ */
652
+ [data-ui='alert'][data-variant='solid'] > [data-part='dismiss']:focus-visible {
653
+ box-shadow: inset 0 0 0 calc(2 * var(--slotted-focus-ring-width, 2px)) var(--_on-solid);
654
+ }
655
+
656
+ [data-ui='alert'][data-part='root']:focus-visible {
657
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
658
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
659
+ }
660
+
661
+ /* ── Composition that answers the box, not the window ──────────────────
662
+ *
663
+ * Two container queries, and both change what the box is made of rather than
664
+ * how large it is. The breakpoints are literal because CSS forbids a custom
665
+ * property inside a container condition, which is the rule
666
+ * `docs/token-foundation.md` records for every reflowing family.
667
+ */
668
+
669
+ /**
670
+ * A narrow box. Two buttons wrapping into two ragged lines is worse than two
671
+ * buttons stacked, and at this width a control that fills the column is the
672
+ * one a thumb can hit. This is the composition change the 240-pixel capture
673
+ * exists to check.
674
+ */
675
+ @container slotted-alert (max-width: 22rem) {
676
+ [data-ui='alert'] > [data-part='actions'] {
677
+ /**
678
+ * Stacked, and each control still its own width. Stretching them is what
679
+ * the mobile banners in the field do, and it takes a decision that is not
680
+ * the family's: how wide a control is belongs to the control, and
681
+ * `button` already ships `fullWidth` for a consumer who wants it.
682
+ */
683
+ align-items: flex-start;
684
+ flex-direction: column;
685
+ }
686
+ }
687
+
688
+ /**
689
+ * A wide page banner. A system message is usually one line with one action,
690
+ * and stacking that action under it wastes the whole width the banner took;
691
+ * past this point the action rejoins the message's own line and the dismiss
692
+ * control moves out to a column of its own. Only `page` reflows: an alert
693
+ * beside a field group is narrow because the thing it is about is narrow,
694
+ * however wide the window is.
695
+ */
696
+ @container slotted-alert (min-width: 40rem) {
697
+ [data-ui='alert'][data-reach='page'] > [data-part='actions'] {
698
+ align-self: center;
699
+ grid-column: 3;
700
+ grid-row: 1;
701
+ margin-inline-start: var(--_column-gap);
702
+ }
703
+
704
+ /**
705
+ * Centred with the action beside it rather than pulled onto the title's
706
+ * line. On its own the control belongs at the message's first line; next to
707
+ * a control that is centred on the whole box, two marks at two heights read
708
+ * as an alignment mistake.
709
+ *
710
+ * Both block margins go, not only the top one: centring aligns the margin
711
+ * box, and the negative bottom margin left behind pulled the control's
712
+ * centre below the action's by half its overhang.
713
+ */
714
+ [data-ui='alert'][data-reach='page'] > [data-part='dismiss'] {
715
+ align-self: center;
716
+ grid-column: 4;
717
+ margin-block: 0;
718
+ }
719
+ }
720
+
721
+ /* ── Forced colours ───────────────────────────────────────────────────
722
+ *
723
+ * The operating system replaces every author colour, so all five tones
724
+ * become one box and all three variants become one painting. What is left to
725
+ * decide is whether the box still has an edge, and the theme cannot answer
726
+ * it: `solid` draws its border in its own fill, and a transparent border is
727
+ * not a colour the system remaps.
728
+ *
729
+ * The mark needs no rule: its glyph fills with `currentcolor`, which the
730
+ * system has already forced, and it keeps its silhouette, which is the whole
731
+ * reason a glyph is drawn per tone — a triangle and a crossed circle are
732
+ * still different from each other when neither of them is coloured.
733
+ */
734
+ @media (forced-colors: active) {
735
+ /**
736
+ * Stated per variant, because each variant's own rule outranks a rule on
737
+ * the root alone. The theme maps the accent fill to `Highlight` and its ink
738
+ * to `HighlightText`, and a system colour is one the browser keeps: the
739
+ * solid box stayed `Highlight` while the backplate the browser draws
740
+ * behind text stayed `Canvas`, and the words were white on white. The
741
+ * danger fill, not a system colour, was forced to `Canvas` and took its
742
+ * edge with it.
743
+ */
744
+ [data-ui='alert'][data-part='root'][data-variant] {
745
+ background-color: Canvas;
746
+ border-color: CanvasText;
747
+ color: CanvasText;
748
+ }
749
+
750
+ /**
751
+ * The control's glyph is stroked in `currentcolor`, so giving the control a
752
+ * system colour repaints the mark with it.
753
+ */
754
+ [data-ui='alert'] > [data-part='dismiss'] {
755
+ color: CanvasText;
756
+ }
757
+
758
+ [data-ui='alert'] > [data-part='dismiss']:enabled:hover,
759
+ [data-ui='alert'] > [data-part='dismiss']:enabled:active {
760
+ background-color: Highlight;
761
+ color: HighlightText;
762
+ }
763
+ }
764
+ }