@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,943 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The region, and the messages in it ──────────────────────────────────
4
+ *
5
+ * Transient feedback about something that already happened: a tone, a mark,
6
+ * a sentence, sometimes a control that acts on it, and a timer.
7
+ *
8
+ * ┌─ root ─ section, role="region", named ──────────────┐
9
+ * │ ┌─ list ─ ol ────────────────────────────────────┐ │
10
+ * │ │ ┌─ item ─ li ────────────────────────────────┐ │ │
11
+ * │ │ │ ◆ icon │ content ▸ title │ ✕ │ │ │
12
+ * │ │ │ │ description │close │ │ │
13
+ * │ │ │ │ actions │ │ │ │
14
+ * │ │ │ ▁▁▁▁▁▁▁▁ progress ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │ │ │
15
+ * │ │ └────────────────────────────────────────────┘ │ │
16
+ * │ └────────────────────────────────────────────────┘ │
17
+ * └─────────────────────────────────────────────────────┘
18
+ *
19
+ * ── The root is the region and not a message ────────────────────────────
20
+ *
21
+ * A message cannot own the corner of the screen. The region does, and it is
22
+ * the part that carries the family's identity, the placement, the container
23
+ * query and the accessible name a reader finds it by. `list` is a second
24
+ * element because `container-type: inline-size` makes an element a query
25
+ * container for its descendants and not for itself — the decision
26
+ * `docs/build-briefing.md` records, and the split `stepper` already had.
27
+ *
28
+ * ── The region takes no pointer ─────────────────────────────────────────
29
+ *
30
+ * A box pinned over a page's content must not eat a press meant for the
31
+ * page. Every surveyed implementation does this and none of them states it,
32
+ * which is how a consumer discovers the rule by finding a dead corner of
33
+ * their own application.
34
+ *
35
+ * ── Nothing here is a live region ───────────────────────────────────────
36
+ *
37
+ * The announcement is made by `packages/core/src/live-announcer`, and the
38
+ * reason is in `specs/components/toast/contract.json` under `departures`.
39
+ * The stylesheet's part in it is that no rule assumes a role: the same
40
+ * picture is drawn whether a reader hears the words or reads them.
41
+ */
42
+ [data-ui='toast'][data-part='root'] {
43
+ /**
44
+ * The distance from the edge of the screen, and the greatest width a
45
+ * message is allowed. The width is the smaller of the two, so at 320
46
+ * pixels and at 200% zoom the region is the screen less two offsets rather
47
+ * than a stated width running off the side.
48
+ */
49
+ --_offset: var(--slotted-toast-offset, var(--slotted-space-6, 1rem));
50
+ --_measure: var(--slotted-toast-measure, 24rem);
51
+ /**
52
+ * Twelve pixels rather than eight, and the elevation a step below the one
53
+ * a single message wanted. Read as a run of five, `--slotted-shadow-lg` at
54
+ * an eight-pixel gap laid each message's shadow across the top of the next
55
+ * one, and the column read as five boxes with dirt between them. The pair
56
+ * is one decision: the gap is what a shadow needs to fall into, and the
57
+ * shadow is what says the message is not part of the page under it.
58
+ */
59
+ --_gap: var(--slotted-toast-gap, var(--slotted-space-5, 0.75rem));
60
+
61
+ /**
62
+ * How far a collapsed message behind the front one peeks out, and how much
63
+ * of its ink each step back gives up. The fade is a bare number rather than
64
+ * a length because it multiplies an index, and both are tokens so a theme
65
+ * that wants a flatter stack has one name to set for each.
66
+ */
67
+ --_stack-step: var(--slotted-toast-stack-step, var(--slotted-space-3, 0.375rem));
68
+ --_stack-fade: var(--slotted-toast-stack-fade, 0.15);
69
+
70
+ /**
71
+ * Where a message comes from. It is the block axis in both directions: a
72
+ * message at the block end rises into place and one at the block start
73
+ * drops in, which is the one motion that says *this arrived* rather than
74
+ * *this moved*.
75
+ */
76
+ --_enter-shift: var(--slotted-space-7, 1.25rem);
77
+
78
+ box-sizing: border-box;
79
+ container-name: slotted-toast;
80
+ container-type: inline-size;
81
+ inline-size: min(var(--_measure), calc(100% - 2 * var(--_offset)));
82
+ /* The single stack, above every overlay: a message about what just
83
+ happened has to be readable over the dialog that caused it. */
84
+ position: fixed;
85
+ z-index: var(--slotted-z-index-toast, 1200);
86
+
87
+ pointer-events: none;
88
+ }
89
+
90
+ /**
91
+ * The region rendered inside a panel rather than against the screen. It is
92
+ * the axis Fluent calls `inline`, and it is what makes the family
93
+ * demonstrable on a documentation page: everything else about it is the
94
+ * same, including the placement, which now names the edges of the panel.
95
+ */
96
+ [data-ui='toast'][data-part='root'][data-attach='inline'] {
97
+ position: absolute;
98
+ }
99
+
100
+ /* ── Placement ─────────────────────────────────────────────────────────
101
+ *
102
+ * Six values, and every one of them written in logical properties, so
103
+ * `bottom-end` is the bottom right of an English page and the bottom left of
104
+ * an Arabic one. Sonner's `bottom-right` is `bottom-right` in both.
105
+ */
106
+
107
+ [data-ui='toast'][data-part='root'][data-placement^='top'] {
108
+ inset-block-start: var(--_offset);
109
+ }
110
+
111
+ [data-ui='toast'][data-part='root'][data-placement^='bottom'] {
112
+ inset-block-end: var(--_offset);
113
+ }
114
+
115
+ [data-ui='toast'][data-part='root'][data-placement$='-start'] {
116
+ inset-inline-start: var(--_offset);
117
+ }
118
+
119
+ [data-ui='toast'][data-part='root'][data-placement$='-end'] {
120
+ inset-inline-end: var(--_offset);
121
+ }
122
+
123
+ /**
124
+ * Centred by giving the box both insets and letting the automatic margins
125
+ * divide what is left. It is the one centring that needs no transform, so
126
+ * the transform stays free for the enter, the exit and the swipe.
127
+ */
128
+ [data-ui='toast'][data-part='root'][data-placement='top'],
129
+ [data-ui='toast'][data-part='root'][data-placement='bottom'] {
130
+ inset-inline: var(--_offset);
131
+ margin-inline: auto;
132
+ }
133
+
134
+ /* ── The list ──────────────────────────────────────────────────────────
135
+ *
136
+ * Newest first in the document, so `F6` and the reading order both reach the
137
+ * message that just arrived before the ones the reader has already seen. A
138
+ * region at the block end reverses the visual order instead of the document
139
+ * order, which puts the newest nearest the edge of the screen — where the
140
+ * reader's eye already is — without moving it in the accessibility tree.
141
+ */
142
+ [data-ui='toast'] > [data-part='list'] {
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: var(--_gap);
146
+ list-style: none;
147
+ margin: 0;
148
+ padding: 0;
149
+ pointer-events: none;
150
+ }
151
+
152
+ [data-ui='toast'][data-placement^='bottom'] > [data-part='list'] {
153
+ flex-direction: column-reverse;
154
+ }
155
+
156
+ /**
157
+ * While the reader is in the region, the gaps between messages belong to
158
+ * it. The list takes no pointer, so a pointer crossing the twelve pixels
159
+ * between two messages left the region: the pause cleared, the timers ran
160
+ * again, and a pile opened by the pointer closed under it. The list takes
161
+ * the pointer only while the region is expanded, which only a pointer or
162
+ * focus already inside a message can cause, so no press meant for the page
163
+ * lands on an empty region.
164
+ */
165
+ [data-ui='toast'][data-expanded] > [data-part='list'] {
166
+ pointer-events: auto;
167
+ }
168
+
169
+ /* ── One message ───────────────────────────────────────────────────────
170
+ *
171
+ * The grid `alert` settled: the mark, the words, and the control that puts
172
+ * the message away, with the actions under the words rather than beside
173
+ * them so a second row exists only when there are actions to put in it.
174
+ */
175
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] {
176
+ /* The tone's five roles, defaulted to `accent`, restated by each tone
177
+ below. Every one of them is a role the theme's contrast test measures. */
178
+ --_solid: var(--slotted-tone-accent-solid);
179
+ --_on-solid: var(--slotted-tone-accent-on-solid);
180
+ --_tone-surface: var(--slotted-tone-accent-surface);
181
+ --_tone-border: var(--slotted-tone-accent-border);
182
+ --_tone-text: var(--slotted-tone-accent-text);
183
+
184
+ /* What the variant resolves the five into: a ground, an edge, the ink the
185
+ words are written in, the quieter ink under them, and the mark. */
186
+ --_ground: var(--slotted-toast-surface, var(--slotted-surface-raised, #ffffff));
187
+ --_edge: var(--slotted-toast-border-color, var(--slotted-border-subtle, #e2e8f0));
188
+ --_ink: var(--slotted-toast-text, var(--slotted-text, #0f172a));
189
+ --_ink-muted: var(--slotted-toast-text-muted, var(--slotted-text-muted, #5b6b7f));
190
+ --_mark: var(--_tone-text);
191
+
192
+ /**
193
+ * A state layer rather than the tone's own hover ramp, for the reason
194
+ * `alert` measured: on a tinted ground `subtle-hover` is lighter than the
195
+ * ground it sits on, and on a solid one the ramp is calibrated against the
196
+ * page instead. `currentcolor` is legible on whatever ground the control
197
+ * already has, because it is the colour its own words are read in.
198
+ *
199
+ * Ten and sixteen per cent, the pair `alert` settled: white at eight per
200
+ * cent over the `danger` fill moved OKLab lightness by 0.019 in the light
201
+ * and dark schemes, under the 0.02 a surface step has to reach
202
+ * (`docs/history/designs/2026-09-29-toast-reui-dossier.md` § 4).
203
+ */
204
+ --_state-hover: color-mix(
205
+ in oklab,
206
+ currentcolor var(--slotted-toast-state-layer-hover, 10%),
207
+ transparent
208
+ );
209
+ --_state-active: color-mix(
210
+ in oklab,
211
+ currentcolor var(--slotted-toast-state-layer-active, 16%),
212
+ transparent
213
+ );
214
+
215
+ /* Type and the close glyph from the control scales, the only ones the
216
+ theme restates per density, so a message inside a dense application
217
+ shrinks with it. The box's own inset is on the space scale, which does
218
+ not — the cost `card` and `alert` both accepted and recorded. */
219
+ --_font-size: var(--slotted-control-font-size-md, 13px);
220
+ --_glyph-size: var(--slotted-control-icon-size-md, 16px);
221
+ --_inner-size: var(--slotted-control-inner-size-md, 24px);
222
+ /**
223
+ * The status mark is `alert`'s: a filled disc a step above the title's
224
+ * `1em`, rounded to an even number of pixels so its centre falls on a
225
+ * whole pixel — 16 at 13-pixel type, 14 at 12. A message about a failure
226
+ * draws the same silhouette as the alert about it. A filled mark carries
227
+ * more weight per pixel than an outline, so it stays within a few pixels
228
+ * of the capitals and still reads as the loudest thing on the line.
229
+ */
230
+ --_mark-size: round(nearest, 1.2em, 2px);
231
+ --_line-height: var(--slotted-line-height-md, 1.5);
232
+
233
+ --_inset-block: var(--slotted-toast-padding-block, var(--slotted-space-5, 0.75rem));
234
+ --_inset-inline: var(--slotted-toast-padding-inline, var(--slotted-space-6, 1rem));
235
+ --_column-gap: var(--slotted-toast-column-gap, var(--slotted-space-4, 0.5rem));
236
+ --_row-gap: var(--slotted-toast-row-gap, var(--slotted-space-5, 0.75rem));
237
+ --_text-gap: var(--slotted-toast-text-gap, var(--slotted-space-2, 0.25rem));
238
+ --_radius: var(--slotted-toast-radius, var(--slotted-radius-lg, 8px));
239
+ --_border-width: var(--slotted-toast-border-width, var(--slotted-border-width-thin, 1px));
240
+ /* An inner control, not a button (R2): the glyph plus a step of air on
241
+ each side, floored at WCAG 2.2 SC 2.5.8's minimum so the target holds at
242
+ every density. It took the smallest control height before, a thirty-pixel
243
+ square beside a sixteen-pixel mark. */
244
+ --_close-size: var(
245
+ --slotted-toast-close-size,
246
+ max(
247
+ var(--slotted-target-min, 24px),
248
+ min(calc(var(--_glyph-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
249
+ )
250
+ );
251
+ --_progress-size: var(--slotted-toast-progress-size, var(--slotted-space-1, 0.125rem));
252
+
253
+ background-color: var(--_ground);
254
+ border: var(--_border-width) solid var(--_edge);
255
+ border-radius: var(--_radius);
256
+ box-shadow: var(--slotted-toast-shadow, var(--slotted-shadow-md));
257
+ box-sizing: border-box;
258
+ color: var(--_ink);
259
+ /* The distance from the mark to the words belongs to the mark, not to the
260
+ grid: a column gap is drawn between two tracks whether or not either
261
+ holds anything, so a message with no mark would start one gap further
262
+ in than its own inset. */
263
+ column-gap: 0;
264
+ display: grid;
265
+ font-family: var(--slotted-toast-font-family, var(--slotted-control-font-family, sans-serif));
266
+ font-size: var(--_font-size);
267
+ grid-template-columns: auto minmax(0, 1fr) auto;
268
+ /**
269
+ * The line box is rounded to an even number of pixels, the rule `alert`
270
+ * draws its lines with. At 13-pixel type the scale's 1.5 gave 19.5, so a
271
+ * message of one line was 45.5 pixels tall and one with a description
272
+ * 88.5 in the `content` scene; an even line also centres the 24-pixel
273
+ * close control on a whole pixel: 20 at 13, 18 at 12.
274
+ */
275
+ line-height: round(nearest, calc(var(--_line-height) * 1em), 2px);
276
+ /* The message takes the pointer the region refuses. */
277
+ pointer-events: auto;
278
+ /* The remaining-time bar is positioned against this box, so its width
279
+ changing never moves a word. */
280
+ position: relative;
281
+ /* Nothing inside a message may be drawn past its own rounded corner, which
282
+ is what the bar at its block end would do. */
283
+ overflow: hidden;
284
+ padding-block: var(--_inset-block);
285
+ padding-inline: var(--_inset-inline);
286
+ row-gap: var(--_row-gap);
287
+
288
+ /**
289
+ * Closed is the state the element is born in, so a message that mounts
290
+ * already open has a style to transition from. The exit is the same
291
+ * declaration, which is why it is written here and not under `[data-open]`.
292
+ */
293
+ opacity: 0;
294
+ translate: 0 var(--_enter-shift);
295
+ transition:
296
+ opacity var(--slotted-duration-short) var(--slotted-easing-exit),
297
+ translate var(--slotted-duration-short) var(--slotted-easing-exit);
298
+ }
299
+
300
+ /**
301
+ * A message at the block start of the screen arrives from above it. The
302
+ * shift is the same distance in the other direction rather than a second
303
+ * number, so the two placements are one motion seen twice.
304
+ */
305
+ [data-ui='toast'][data-placement^='top'] > [data-part='list'] > [data-part='item'] {
306
+ translate: 0 calc(-1 * var(--_enter-shift));
307
+ }
308
+
309
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-open] {
310
+ opacity: 1;
311
+ translate: 0;
312
+ transition:
313
+ opacity var(--slotted-duration-medium) var(--slotted-easing-enter),
314
+ translate var(--slotted-duration-medium) var(--slotted-easing-enter);
315
+ }
316
+
317
+ /* ── Tone: the colour, and the mark that says the same thing ───────────
318
+ *
319
+ * The map is `specs/components/toast/contract.json`, and it is the map
320
+ * `alert` and `status-indicator` already share — so a message about a
321
+ * failure draws the same silhouette as the alert about it. The binding
322
+ * draws the mark as a `Glyph` from that map; this sheet gives it only the
323
+ * tone's colour. Under forced
324
+ * colours every ground becomes one system colour and the shape is all that
325
+ * is left, which is WCAG 2.2 SC 1.4.1 as well as a preference.
326
+ */
327
+
328
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='accent'] {
329
+ --_solid: var(--slotted-tone-accent-solid);
330
+ --_on-solid: var(--slotted-tone-accent-on-solid);
331
+ --_tone-surface: var(--slotted-tone-accent-surface);
332
+ --_tone-border: var(--slotted-tone-accent-border);
333
+ --_tone-text: var(--slotted-tone-accent-text);
334
+ }
335
+
336
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='secondary'] {
337
+ --_solid: var(--slotted-tone-secondary-solid);
338
+ --_on-solid: var(--slotted-tone-secondary-on-solid);
339
+ --_tone-surface: var(--slotted-tone-secondary-surface);
340
+ --_tone-border: var(--slotted-tone-secondary-border);
341
+ --_tone-text: var(--slotted-tone-secondary-text);
342
+ }
343
+
344
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='success'] {
345
+ --_solid: var(--slotted-tone-success-solid);
346
+ --_on-solid: var(--slotted-tone-success-on-solid);
347
+ --_tone-surface: var(--slotted-tone-success-surface);
348
+ --_tone-border: var(--slotted-tone-success-border);
349
+ --_tone-text: var(--slotted-tone-success-text);
350
+ }
351
+
352
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='warning'] {
353
+ --_solid: var(--slotted-tone-warning-solid);
354
+ --_on-solid: var(--slotted-tone-warning-on-solid);
355
+ --_tone-surface: var(--slotted-tone-warning-surface);
356
+ --_tone-border: var(--slotted-tone-warning-border);
357
+ --_tone-text: var(--slotted-tone-warning-text);
358
+ }
359
+
360
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-tone='danger'] {
361
+ --_solid: var(--slotted-tone-danger-solid);
362
+ --_on-solid: var(--slotted-tone-danger-on-solid);
363
+ --_tone-surface: var(--slotted-tone-danger-surface);
364
+ --_tone-border: var(--slotted-tone-danger-border);
365
+ --_tone-text: var(--slotted-tone-danger-text);
366
+ }
367
+
368
+ /* ── Variant: the three ways one tone is painted ───────────────────────
369
+ *
370
+ * `raised` is the default and the one the other families do not have: a
371
+ * message floats over content this family does not own, and tinting that
372
+ * content's corner is a decision the message has no standing to take. The
373
+ * tone is in the mark and in nothing else, which is enough — it is the
374
+ * silhouette that survives forced colours anyway.
375
+ */
376
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-variant='soft'] {
377
+ --_ground: var(--_tone-surface);
378
+ --_edge: var(--_tone-border);
379
+ /* The tone's own text on the tone's own surface is the pair the theme
380
+ measures. Painting a tinted ground with the neutral ink would be a ratio
381
+ nobody holds, which is the shortfall `alert` records. */
382
+ --_ink: var(--_tone-text);
383
+ --_ink-muted: var(--_tone-text);
384
+ }
385
+
386
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-variant='solid'] {
387
+ --_ground: var(--_solid);
388
+ --_edge: var(--_solid);
389
+ --_ink: var(--_on-solid);
390
+ --_ink-muted: var(--_on-solid);
391
+ --_mark: var(--_on-solid);
392
+ }
393
+
394
+ /* ── The mark ─────────────────────────────────────────────────────────
395
+ *
396
+ * `alert`'s mark, rule for rule: a filled disc on the title's first line,
397
+ * the binding's `Glyph` in its `solid` form, which fills the glyph's ring
398
+ * and cuts the tone's symbol out of the fill, so the ground shows through
399
+ * the symbol on every variant. Every tone's glyph is a circle, so every
400
+ * mark is the same round silhouette on the same centre, and the symbol
401
+ * inside is what tells the five apart when colour is gone.
402
+ *
403
+ * The glyph's filled ring reaches radius 11 of its 24-unit box, so the box
404
+ * is 12/11 of the mark and the disc is the mark's own size.
405
+ *
406
+ * The mark hangs from the first line's baseline. Grid baseline alignment
407
+ * puts the mark's bottom edge on the words' baseline, and a relative offset
408
+ * lowers it by half of what the mark exceeds the capitals by, so its centre
409
+ * is the centre of the capitals in whatever face the title is set in. The
410
+ * offset is rounded to a whole pixel and the mark is a whole number of
411
+ * pixels, so the glyphs and the drawing snap by the same amount. The
412
+ * negative block-start margin takes the mark's own height out of the
413
+ * baseline group, so the words sit where they sit with no mark at all, and
414
+ * layout containment keeps the glyph's own baseline out of the box's.
415
+ *
416
+ * The slot carries the colour, and the disc is painted in `currentcolor`,
417
+ * so on a `solid` message the mark takes the on-solid ink with the words,
418
+ * and under forced colours one declaration repaints it.
419
+ *
420
+ * A mark the page supplied instead of the tone's own — a spinner from
421
+ * `progress-indicator` while a promise is in flight, a product's own
422
+ * artwork — takes the same place: an icon a consumer wrote is an icon, and
423
+ * the line it belongs to has not moved. The artwork is centred in the box,
424
+ * so an icon drawn at the mark's size fills it and a larger one overhangs it
425
+ * equally on both sides of the same axis rather than pushing the words along. A
426
+ * message waiting on a promise with no mark of its own draws none, and the
427
+ * box stays, so the layout does not jump when the spinner becomes a tick.
428
+ */
429
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='icon'] {
430
+ --slotted-icon-size: calc(var(--_mark-size) * 12 / 11);
431
+ /**
432
+ * The loading mark is `progress-indicator`'s contained loader, and these
433
+ * three are the whole of what this family says to it: the disc is the
434
+ * mark's size, so the loader and the tick that replaces it are one
435
+ * silhouette, and it is drawn in the mark's ink with the track the
436
+ * loader's own 24 per cent of that ink over this message's ground — on a
437
+ * `solid` message that is the on-solid ink over the fill, which the
438
+ * loader's tone could not know.
439
+ */
440
+ --slotted-progress-indicator-size: var(--_mark-size);
441
+ --slotted-progress-indicator-indicator-color: var(--_mark);
442
+ --slotted-progress-indicator-track-color: color-mix(in oklab, var(--_mark) 24%, var(--_ground));
443
+
444
+ align-items: center;
445
+ align-self: baseline;
446
+ block-size: var(--_mark-size);
447
+ color: var(--_mark);
448
+ contain: layout;
449
+ display: flex;
450
+ grid-column: 1;
451
+ grid-row: 1;
452
+ inline-size: var(--_mark-size);
453
+ inset-block-start: round(nearest, (var(--_mark-size) - 1cap) / 2, 1px);
454
+ justify-content: center;
455
+ margin-block-start: calc(-1 * var(--_mark-size));
456
+ margin-inline-end: var(--_column-gap);
457
+ position: relative;
458
+ }
459
+
460
+ /* ── The words ────────────────────────────────────────────────────────
461
+ *
462
+ * `min-inline-size: 0` is what lets the column shrink below its content
463
+ * inside the message's grid, and it is the half of the pair that
464
+ * `minmax(0, 1fr)` on the track cannot supply on its own. The same pair is
465
+ * restated inside: the words are a grid of their own, and its one track has
466
+ * to be clamped too, or an unbreakable word in the title sizes the track to
467
+ * itself and the words run past the box that holds them.
468
+ */
469
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='content'] {
470
+ /* The other half of the mark's baseline group: see the mark above. */
471
+ align-self: baseline;
472
+ display: grid;
473
+ grid-column: 2;
474
+ grid-row: 1;
475
+ grid-template-columns: minmax(0, 1fr);
476
+ min-inline-size: 0;
477
+ row-gap: var(--_text-gap);
478
+ }
479
+
480
+ /**
481
+ * `anywhere` breaks the one string a message is likeliest to carry that no
482
+ * language can break — a deployment id, a container digest, a compound noun,
483
+ * a host name without a hyphen — and, unlike `break-word`, lowers the
484
+ * min-content size the grid reads, so the track never grows past the box to
485
+ * fit the word. Nothing is removed: the whole string stays in the DOM, in the
486
+ * accessibility tree, and findable by a page search.
487
+ *
488
+ * The 240-pixel capture is what asked for it: with `break-word` the title
489
+ * `Zwischenspeicherung` set the track to 183 pixels inside a box of 155, and
490
+ * the words stood outside the message in every scheme.
491
+ */
492
+ [data-ui='toast']
493
+ > [data-part='list']
494
+ > [data-part='item']
495
+ > [data-part='content']
496
+ > [data-part='title'] {
497
+ font-family: var(
498
+ --slotted-toast-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-weight: var(--slotted-toast-title-font-weight, var(--slotted-font-weight-semibold, 600));
505
+ margin: 0;
506
+ min-inline-size: 0;
507
+ overflow-wrap: anywhere;
508
+ }
509
+
510
+ [data-ui='toast']
511
+ > [data-part='list']
512
+ > [data-part='item']
513
+ > [data-part='content']
514
+ > [data-part='description'] {
515
+ color: var(--_ink-muted);
516
+ margin: 0;
517
+ min-inline-size: 0;
518
+ overflow-wrap: anywhere;
519
+ }
520
+
521
+ /* ── The actions ──────────────────────────────────────────────────────
522
+ *
523
+ * Under the words rather than beside them, which is the only place they can
524
+ * wrap without taking the message's own column with them. What goes in is
525
+ * the consumer's own buttons: `button` already owns what a button is, and
526
+ * naming one of two controls `cancel` would privilege one arrangement.
527
+ */
528
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='actions'] {
529
+ align-items: center;
530
+ display: flex;
531
+ flex-wrap: wrap;
532
+ gap: var(--slotted-toast-actions-gap, var(--slotted-space-3, 0.375rem));
533
+ grid-column: 2;
534
+ grid-row: 2;
535
+ min-inline-size: 0;
536
+ }
537
+
538
+ /* ── The control that puts the message away ───────────────────────────
539
+ *
540
+ * The negative margins are optical rather than decorative, and they are the
541
+ * pair `alert` arrived at: the button's box is taller than the line it sits
542
+ * beside, so half the overhang comes off each end. With both, the control's
543
+ * margin box is exactly one line and the inset above the message equals the
544
+ * inset below.
545
+ */
546
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close'] {
547
+ /* The cross is the binding's `Glyph` in an `icon` box at the glyph size
548
+ the formula for this box reads. */
549
+ --slotted-icon-size: var(--_glyph-size);
550
+
551
+ align-items: center;
552
+ align-self: start;
553
+ appearance: none;
554
+ background-color: transparent;
555
+ block-size: var(--_close-size);
556
+ border: 0;
557
+ border-radius: var(--slotted-toast-close-radius, var(--slotted-control-radius, 4px));
558
+ color: inherit;
559
+ cursor: pointer;
560
+ display: inline-flex;
561
+ flex: none;
562
+ /* The user agent gives every button a font of its own, and the `1lh` below
563
+ is measured against whatever that font resolves to. */
564
+ font: inherit;
565
+ grid-column: 3;
566
+ grid-row: 1;
567
+ inline-size: var(--_close-size);
568
+ justify-content: center;
569
+ margin-block-end: calc((1lh - var(--_close-size)) / 2);
570
+ margin-block-start: calc((1lh - var(--_close-size)) / 2);
571
+ margin-inline-end: calc((1lh - var(--_close-size)) / 2);
572
+ margin-inline-start: var(--_column-gap);
573
+ padding: 0;
574
+ transition:
575
+ background-color var(--slotted-control-transition-duration)
576
+ var(--slotted-control-transition-easing),
577
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
578
+ }
579
+
580
+ /* `:enabled` is the guard the shared state test requires, and the reason it
581
+ exists: a control the browser has already refused answers no pointer. */
582
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:enabled:hover {
583
+ background-color: var(--_state-hover);
584
+ }
585
+
586
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:enabled:active {
587
+ background-color: var(--_state-active);
588
+ }
589
+
590
+ /* On a finger the target is the comfortable size in both axes and the box
591
+ is the one a mouse sees: a transparent box centred on the control reaches
592
+ 44 pixels, and the ground and the ring stay square. The message clips its
593
+ overflow, so the target ends at the message's padding edge. */
594
+ @media (pointer: coarse) {
595
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close'] {
596
+ position: relative;
597
+ }
598
+
599
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']::after {
600
+ content: '';
601
+ inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
602
+ position: absolute;
603
+ }
604
+ }
605
+
606
+ /* ── Focus ────────────────────────────────────────────────────────────
607
+ *
608
+ * Two rings, because two things can hold focus. The control's is inset: it
609
+ * sits at the corner of a box that clips its own overflow, so a ring outside
610
+ * it would be cut in half. The message's is inset for the same reason, and
611
+ * it exists because `F6` sends the keyboard here — a message the reader
612
+ * cannot see they have arrived at is a message they cannot act on.
613
+ */
614
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:focus-visible,
615
+ [data-ui='toast'] > [data-part='list'] > [data-part='item']:focus-visible {
616
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
617
+ }
618
+
619
+ /**
620
+ * The control's ring is inset because the message clips its own overflow —
621
+ * the remaining-time bar has to stop at the rounded corner — and a ring
622
+ * outside a control at that corner would be cut in half by it.
623
+ */
624
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:focus-visible {
625
+ outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
626
+ }
627
+
628
+ /**
629
+ * The message's own ring sits outside it. Nothing clips a layer at the edge
630
+ * of the screen, and read on the box itself the ring was a thick blue border
631
+ * rather than a ring around a card.
632
+ */
633
+ [data-ui='toast'] > [data-part='list'] > [data-part='item']:focus-visible {
634
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
635
+ }
636
+
637
+ /**
638
+ * Measured rather than assumed, and `alert` measured it first: the ring the
639
+ * theme calibrated against the page's surface reads about 1.3 : 1 against a
640
+ * tone's own fill. The ring keeps the theme's colour in every scheme (R3).
641
+ * The message's own ring sits outside it, against the page the theme
642
+ * calibrated for, and needs nothing more. The control's ring is inset in the
643
+ * fill, so a solid message adds a halo one ring-width wide just inside it,
644
+ * in the ink its own words are in, for the ring to be read against.
645
+ */
646
+ [data-ui='toast']
647
+ > [data-part='list']
648
+ > [data-part='item'][data-variant='solid']
649
+ > [data-part='close']:focus-visible {
650
+ box-shadow: inset 0 0 0 calc(2 * var(--slotted-focus-ring-width, 2px)) var(--_on-solid);
651
+ }
652
+
653
+ /* ── How much time is left ────────────────────────────────────────────
654
+ *
655
+ * The bar runs for exactly as long as the message it belongs to, because it
656
+ * reads the same number the timer does — `--slotted-toast-duration`, written
657
+ * per message by `packages/core/src/toast`. `--slotted-toast-play-state`
658
+ * carries the other half: the timer pauses on hover, on focus inside the
659
+ * region, and on a hidden document, and only the first two of those are
660
+ * things CSS can see. Resolving all three in script and publishing one value
661
+ * is what makes it impossible for the picture and the timer to disagree.
662
+ *
663
+ * It animates its own inline size rather than a transform, so it shortens
664
+ * from the inline start in every writing direction with no origin to mirror.
665
+ * The box is positioned, so nothing it does moves a word.
666
+ */
667
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='progress'] {
668
+ animation-duration: var(--slotted-toast-duration, var(--slotted-duration-long));
669
+ animation-fill-mode: forwards;
670
+ animation-name: slotted-toast-countdown;
671
+ animation-play-state: var(--slotted-toast-play-state, running);
672
+ animation-timing-function: linear;
673
+ /**
674
+ * The tone's saturated step rather than the ink its words are in. Read at
675
+ * half opacity in the ink colour the bar was a grey smear under a message;
676
+ * two pixels of the tone's own fill is the one mark on the box that is
677
+ * allowed to be loud, because what it says is *this is about to go*.
678
+ */
679
+ background-color: var(--_solid);
680
+ block-size: var(--_progress-size);
681
+ inset-block-end: 0;
682
+ inset-inline-start: 0;
683
+ position: absolute;
684
+ }
685
+
686
+ /**
687
+ * On a solid message the fill is already the tone, so the bar is drawn in
688
+ * the one ink the contrast test holds against it, held back so it reads as a
689
+ * measure rather than as a second border.
690
+ */
691
+ [data-ui='toast']
692
+ > [data-part='list']
693
+ > [data-part='item'][data-variant='solid']
694
+ > [data-part='progress'] {
695
+ background-color: var(--_on-solid);
696
+ opacity: 0.7;
697
+ }
698
+
699
+ @keyframes slotted-toast-countdown {
700
+ from {
701
+ inline-size: 100%;
702
+ }
703
+
704
+ to {
705
+ inline-size: 0%;
706
+ }
707
+ }
708
+
709
+ /* ── The collapsed stack ──────────────────────────────────────────────
710
+ *
711
+ * Four messages at full height fill a phone. `stacking="stack"` puts them
712
+ * one behind another at the edge `placement` names, each stepped back and
713
+ * drawn slightly smaller, and expands the pile when the pointer is over the
714
+ * region or focus is inside it — the same two conditions that pause the
715
+ * timer, because both mean the reader is reading.
716
+ *
717
+ * Every message is positioned in both states and moves only by `translate`,
718
+ * which is what lets one become the other rather than snap. The numbers it
719
+ * moves by are measured — `--slotted-toast-height` per message,
720
+ * `--slotted-toast-stack-offset` for the messages in front of it,
721
+ * `--slotted-toast-front-height` for the one at the edge — because the pile
722
+ * has to be readable when it opens, and a pile of messages that are not all
723
+ * the same height cannot be opened from arithmetic alone.
724
+ *
725
+ * Depth is said by the step back and by the ink, and it was said by a scale
726
+ * per index until R7: a message is a box of words, and drawing it at
727
+ * ninety-six per cent resamples every letter in it while the pile opens and
728
+ * closes. What replaced it is the one property that says *further away*
729
+ * without touching the type — each message behind the front one is held
730
+ * back in opacity, and the front one, which is the one being read, is at
731
+ * full strength.
732
+ */
733
+ [data-ui='toast'][data-stacking='stack'] > [data-part='list'] {
734
+ display: block;
735
+ position: relative;
736
+ }
737
+
738
+ [data-ui='toast'][data-stacking='stack'] > [data-part='list'] > [data-part='item'] {
739
+ block-size: var(--slotted-toast-front-height, auto);
740
+ inset-inline: 0;
741
+ position: absolute;
742
+ /* Newest in front. The count is what lets the rule read the same at any
743
+ depth without a rule per index. */
744
+ z-index: calc(var(--slotted-toast-count, 1) - var(--slotted-toast-index, 0));
745
+ transition:
746
+ block-size var(--slotted-duration-medium) var(--slotted-easing-standard),
747
+ opacity var(--slotted-duration-medium) var(--slotted-easing-standard),
748
+ translate var(--slotted-duration-medium) var(--slotted-easing-standard);
749
+ }
750
+
751
+ /**
752
+ * How far back a message is, said in ink. It is written on the open message
753
+ * rather than on every message, because the closed style is `opacity: 0` —
754
+ * the state a message arrives from and leaves into — and a rule that dimmed
755
+ * it there would be a message that never faded in. The third message and
756
+ * everything behind it share one strength: below that the pile is a stack of
757
+ * ghosts, and only the front two edges are showing anyway.
758
+ */
759
+ [data-ui='toast'][data-stacking='stack'] > [data-part='list'] > [data-part='item'][data-open] {
760
+ opacity: calc(1 - min(var(--slotted-toast-index, 0), 2) * var(--_stack-fade));
761
+ }
762
+
763
+ [data-ui='toast'][data-stacking='stack'][data-placement^='bottom']
764
+ > [data-part='list']
765
+ > [data-part='item'] {
766
+ inset-block-end: 0;
767
+ translate: 0 calc(-1 * var(--slotted-toast-index, 0) * var(--_stack-step));
768
+ }
769
+
770
+ [data-ui='toast'][data-stacking='stack'][data-placement^='top']
771
+ > [data-part='list']
772
+ > [data-part='item'] {
773
+ inset-block-start: 0;
774
+ translate: 0 calc(var(--slotted-toast-index, 0) * var(--_stack-step));
775
+ }
776
+
777
+ /**
778
+ * Opened. Every message returns to its own height and its own place in the
779
+ * column, and the gap between two of them is the region's own gap — the
780
+ * offset script writes already carries it.
781
+ */
782
+ [data-ui='toast'][data-stacking='stack'][data-expanded]
783
+ > [data-part='list']
784
+ > [data-part='item'] {
785
+ block-size: var(--slotted-toast-height, auto);
786
+ }
787
+
788
+ /**
789
+ * The messages of a pile are positioned, so the list is as tall as none of
790
+ * them, and the rule that gives an expanded list the pointer covered no gap
791
+ * at all. Opened, it is as tall as the open pile — the measured sum of every
792
+ * message and the gaps between them — and the gaps are the region's. The
793
+ * messages hang from the list's edge at `placement`, so the list grows away
794
+ * from that edge and nothing it holds moves.
795
+ */
796
+ [data-ui='toast'][data-stacking='stack'][data-expanded] > [data-part='list'] {
797
+ block-size: var(--slotted-toast-stack-height, auto);
798
+ }
799
+
800
+ [data-ui='toast'][data-stacking='stack'][data-expanded]
801
+ > [data-part='list']
802
+ > [data-part='item'][data-open] {
803
+ opacity: 1;
804
+ }
805
+
806
+ [data-ui='toast'][data-stacking='stack'][data-expanded][data-placement^='bottom']
807
+ > [data-part='list']
808
+ > [data-part='item'] {
809
+ translate: 0 calc(-1 * var(--slotted-toast-stack-offset, 0px));
810
+ }
811
+
812
+ [data-ui='toast'][data-stacking='stack'][data-expanded][data-placement^='top']
813
+ > [data-part='list']
814
+ > [data-part='item'] {
815
+ translate: 0 var(--slotted-toast-stack-offset, 0px);
816
+ }
817
+
818
+ /**
819
+ * While the pile is closed, everything behind the front message shows only
820
+ * its own edge: its words are held back rather than clipped mid-sentence.
821
+ * `min()` is what turns an index into that rule without a rule per depth —
822
+ * the front message resolves to one and every other to zero.
823
+ */
824
+ [data-ui='toast'][data-stacking='stack'] > [data-part='list'] > [data-part='item'] > * {
825
+ transition: opacity var(--slotted-duration-medium) var(--slotted-easing-standard);
826
+ }
827
+
828
+ [data-ui='toast'][data-stacking='stack']:not([data-expanded]) [data-part='item'] > * {
829
+ opacity: calc(1 - min(1, var(--slotted-toast-index, 0)));
830
+ }
831
+
832
+ /* ── Swiping it away ──────────────────────────────────────────────────
833
+ *
834
+ * The gesture the platform trained the reader to expect, and the answer to a
835
+ * dismiss control that is a small target on a box that is moving. The
836
+ * pointer's distance is published by `packages/core/src/toast` and the
837
+ * message follows it exactly, with no transition in the way: a drag that
838
+ * lags the finger reads as a stuck interface.
839
+ */
840
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'][data-dragging] {
841
+ cursor: grabbing;
842
+ transition: none;
843
+ translate: var(--slotted-toast-swipe-x, 0px) var(--slotted-toast-swipe-y, 0px);
844
+ /* The browser's own scroll gesture and this one are the same finger. */
845
+ touch-action: none;
846
+ user-select: none;
847
+ }
848
+
849
+ [data-ui='toast'][data-swipe='true'] > [data-part='list'] > [data-part='item'] {
850
+ touch-action: none;
851
+ }
852
+
853
+ /* ── A narrow region ──────────────────────────────────────────────────
854
+ *
855
+ * The query is on the region's own inline size, which is what a message
856
+ * actually has to fit inside — the region is already the screen less two
857
+ * offsets, so a phone and a narrow panel reach this the same way. The
858
+ * breakpoint is a literal because CSS forbids a custom property in a
859
+ * container condition.
860
+ */
861
+ @container slotted-toast (max-width: 20rem) {
862
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] {
863
+ --_inset-block: var(--slotted-toast-padding-block, var(--slotted-space-4, 0.5rem));
864
+ --_inset-inline: var(--slotted-toast-padding-inline, var(--slotted-space-5, 0.75rem));
865
+ }
866
+
867
+ /**
868
+ * Two buttons wrapping into two ragged lines is worse than two buttons
869
+ * stacked. Each one keeps its own width: how wide a control is belongs to
870
+ * the control, and `button` already ships `fullWidth` for a consumer who
871
+ * wants otherwise.
872
+ */
873
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='actions'] {
874
+ align-items: flex-start;
875
+ flex-direction: column;
876
+ }
877
+ }
878
+
879
+ /* ── Forced colours ───────────────────────────────────────────────────
880
+ *
881
+ * The operating system replaces every author colour, so five tones become
882
+ * one box and three variants become one painting. What is left to decide is
883
+ * whether the box still has an edge and whether the mark is still there —
884
+ * and the theme can answer neither, because `solid` draws its border in its
885
+ * own fill and a transparent border is not a colour the system remaps.
886
+ */
887
+ @media (forced-colors: active) {
888
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] {
889
+ border-color: CanvasText;
890
+ }
891
+
892
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='icon'] {
893
+ color: CanvasText;
894
+ }
895
+
896
+ /**
897
+ * The control's glyph is stroked in `currentcolor`, so giving the control
898
+ * a system colour repaints it, and the mark's slot the same.
899
+ */
900
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close'] {
901
+ color: CanvasText;
902
+ }
903
+
904
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='close']:enabled:hover,
905
+ [data-ui='toast']
906
+ > [data-part='list']
907
+ > [data-part='item']
908
+ > [data-part='close']:enabled:active {
909
+ background-color: Highlight;
910
+ color: HighlightText;
911
+ }
912
+
913
+ /**
914
+ * A bar of system ink under every message, shortening, is a moving line
915
+ * with no colour to say what it belongs to. The message still expires.
916
+ */
917
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='progress'] {
918
+ background-color: CanvasText;
919
+ opacity: 1;
920
+ }
921
+ }
922
+
923
+ /* ── Reduced motion ───────────────────────────────────────────────────
924
+ *
925
+ * Every duration in this sheet comes from the motion scale, which the theme
926
+ * collapses under this query, so the arrival, the exit and the opening of
927
+ * the pile all become instant without a rule here.
928
+ *
929
+ * The countdown does not, because its length is the message's own and no
930
+ * theme can reach it. It is removed instead: a reader who asked the system
931
+ * for stillness is not given a bar to watch for five seconds, and the bar
932
+ * becomes a rule under the message. Nothing is lost — the bar was never the
933
+ * only warning, and the message expires either way. Pausing it was not
934
+ * enough: an animation that fills forwards holds whatever width it had
935
+ * reached over the declared one, and the `timing` scene froze at 80 per cent.
936
+ */
937
+ @media (prefers-reduced-motion: reduce) {
938
+ [data-ui='toast'] > [data-part='list'] > [data-part='item'] > [data-part='progress'] {
939
+ animation-name: none;
940
+ inline-size: 100%;
941
+ }
942
+ }
943
+ }