@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,817 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * A list of commands on a layer, opened from a control, from an item of
4
+ * another menu, or from a trigger on a menubar.
5
+ *
6
+ * The two boxes are `tooltip`'s and `popover`'s: the **layer** is what
7
+ * `packages/core/src/positioner` writes `position`, `left` and `top` onto and
8
+ * what slides and fades, because the module leaves `transform` alone; the
9
+ * **content** is the painted surface and the only thing that takes the
10
+ * pointer. The gap between the trigger and the surface is the layer's own
11
+ * padding rather than the positioner's offset, so the pointer never crosses
12
+ * empty page on its way from a parent item into a submenu — which is half of
13
+ * what makes the grace area work.
14
+ *
15
+ * ┌─ root ─ the layer ─────────────────────────────────────┐
16
+ * │ ┌─ content ─ role="menu" ─ scrolls ───────────────┐ │
17
+ * │ │ group ▸ group-label │ │
18
+ * │ │ item ▸ indicator or icon │ label │ shortcut │ │
19
+ * │ │ description │ │
20
+ * │ │ separator │ │
21
+ * │ │ empty │ │
22
+ * │ └─────────────────────────────────────────────────┘ │
23
+ * └────────────────────────────────────────────────────────┘
24
+ *
25
+ * ── Why the surface is a grid and the item is a subgrid ──────────────
26
+ *
27
+ * Every surveyed implementation lays an item out as a flex row with the
28
+ * shortcut pushed to the end, which lets a long translated label eat the
29
+ * shortcut's space and leaves two items' labels out of line whenever one has
30
+ * an icon and the other does not. Here the surface owns four columns — the
31
+ * mark, the label, the meta, the shortcut — and every item, group and group label
32
+ * passes through them with `grid-template-columns: subgrid`. Labels line up
33
+ * across the whole menu, the shortcut column is as wide as the widest
34
+ * shortcut and never narrower, and the label column is the only one that
35
+ * gives way.
36
+ *
37
+ * The mark column holds the indicator or the icon, and not both. A checkbox
38
+ * item's mark is its icon: an item that carried a check in one gutter and a
39
+ * glyph in a second would open every menu with two empty columns down its
40
+ * start edge, which is the shape a menu inherits from a desktop menu bar and
41
+ * not one a product needs. Radix, Base UI and Ark all draw the two in one
42
+ * place for the same reason.
43
+ *
44
+ * The columns carry no `column-gap`. A gap would be drawn between two tracks
45
+ * even when the first is empty, so a menu with no icons at all would open
46
+ * with a gutter down its start edge. Each optional cell carries its own
47
+ * margin instead, and a column nothing occupies collapses to nothing.
48
+ *
49
+ * ── Density ─────────────────────────────────────────────────────────
50
+ *
51
+ * An item is a control, so every measurement it makes reads control
52
+ * geometry: its height, its inset, its type, its glyph and its gap. A dense
53
+ * menu holds more commands and is the same component; only the surface's own
54
+ * inset and radius come off the space and radius scales, as `popover`'s do.
55
+ */
56
+ [data-ui='menu'] {
57
+ --_gap: var(--slotted-menu-gap, var(--slotted-space-2, 0.25rem));
58
+
59
+ --_surface: var(--slotted-menu-surface, var(--slotted-surface-raised, #ffffff));
60
+ --_text: var(--slotted-menu-text, var(--slotted-text, #0f172a));
61
+ --_muted: var(--slotted-menu-text-muted, var(--slotted-text-muted, #5b6b7f));
62
+ --_border-color: var(--slotted-menu-border-color, var(--slotted-border-subtle, #e2e8f0));
63
+ --_border-width: var(--slotted-border-width-thin, 1px);
64
+ --_radius: var(--slotted-menu-radius, var(--slotted-radius-lg, 8px));
65
+ --_shadow: var(--slotted-menu-shadow, var(--slotted-shadow-md));
66
+
67
+ /* The highlight is one step of the neutral tone at rest and the next one
68
+ under the press, which is the same pair every control in this library
69
+ uses for a subtle surface. A tone on an item retargets both. */
70
+ --_highlight: var(--slotted-menu-highlight, var(--slotted-tone-secondary-subtle-hover));
71
+ --_highlight-active: var(
72
+ --slotted-menu-highlight-active,
73
+ var(--slotted-tone-secondary-subtle-active)
74
+ );
75
+ --_accent: var(--_text);
76
+
77
+ --_inset-block: var(--slotted-menu-padding-block, var(--slotted-space-2, 0.25rem));
78
+ --_inset-inline: var(--slotted-menu-padding-inline, var(--slotted-space-2, 0.25rem));
79
+ /**
80
+ * The item's own inset is a control's inline padding less the surface's,
81
+ * so the first column starts where the text of the control that opened
82
+ * the menu starts: both sit one border and one control padding in from
83
+ * the shared start edge. With the full control padding the column stood
84
+ * four pixels further in (x 55 against the trigger's 51).
85
+ */
86
+ --_item-padding: var(--slotted-control-padding-inline-md, 0.8125rem);
87
+ --_item-inset: calc(var(--_item-padding) - var(--_inset-inline));
88
+ --_item-height: var(--slotted-control-height-md, 34px);
89
+ --_item-radius: var(--slotted-menu-item-radius, var(--slotted-control-radius));
90
+ --_font-size: var(--slotted-control-font-size-md, 13px);
91
+ --_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
92
+ --_icon-size: var(--slotted-control-icon-size-md, 16px);
93
+ --_column-gap: var(--slotted-control-gap-md, 0.5rem);
94
+
95
+ /**
96
+ * Line boxes on whole pixels. The scale's ratios give 19.5 at 13px and
97
+ * 17.4 at 12px, and a surface holding a group label and a description
98
+ * summed them to 270.28 pixels tall, so its bottom border sat on a
99
+ * fractional row. Rounding up keeps each line at least its ratio.
100
+ */
101
+ --_leading: round(up, calc(var(--_font-size) * var(--slotted-line-height-md, 1.5)), 1px);
102
+ --_secondary-leading: round(
103
+ up,
104
+ calc(var(--_secondary-font-size) * var(--slotted-line-height-sm, 1.45)),
105
+ 1px
106
+ );
107
+
108
+ /**
109
+ * The cap. Measure belongs to `container`, so the family offers no axis for
110
+ * it and still has to stop a `max-content` surface from setting one command
111
+ * the width of the page. Wide enough for a label and a shortcut beside it,
112
+ * narrow enough to hang off a button.
113
+ */
114
+ --_measure: var(--slotted-menu-measure, 16rem);
115
+ /**
116
+ * And the floor, which the family had and put on the wrong element (M3).
117
+ *
118
+ * A `max-content` surface is as wide as its longest command, so a menu of
119
+ * short words is a sliver: "Inbox, Archive, Drafts, Sent" measured 91px at
120
+ * `md`, 81px at `compact` and 77px at `dense`, against the 228px the *empty*
121
+ * menu stood at — because the empty row carried a `min-inline-size` of its
122
+ * own and the surface carried none. The surface therefore grew when the
123
+ * list was filtered away and shrank when the commands came back, which is
124
+ * the one thing a menu must not do under a pointer that is already moving
125
+ * toward it.
126
+ *
127
+ * The floor is the surface's now and the empty row inherits it, so the two
128
+ * cannot disagree. It is subordinate to the room the positioner reports:
129
+ * `min-inline-size` beats `max-inline-size`, so a floor stated flat would
130
+ * push a menu out of a column too narrow to hold it.
131
+ */
132
+ --_min-measure: min(
133
+ var(--slotted-menu-min-measure, 12rem),
134
+ var(--slotted-positioner-available-width, 100vw)
135
+ );
136
+
137
+ /* Only the block inset: the positioner writes `left` and never `right`, and
138
+ `inset-inline-start` would resolve to `right` in a right-to-left document
139
+ and win over the measured value. */
140
+ position: absolute;
141
+ inset-block-start: 0;
142
+
143
+ z-index: calc(var(--slotted-z-index-popover, 1100) + var(--slotted-layer-depth, 0));
144
+ isolation: isolate;
145
+
146
+ box-sizing: border-box;
147
+ inline-size: max-content;
148
+ max-inline-size: min(var(--_measure), var(--slotted-positioner-available-width, 100vw));
149
+ min-inline-size: var(--_min-measure);
150
+
151
+ /**
152
+ * The gap sits on the axis that faces the anchor and on no other. A layer
153
+ * padded on all four sides drops a submenu's first item below the item that
154
+ * opened it by the padding it does not need, and the reader reads a list
155
+ * that starts under its own parent rather than beside it. `data-side` is
156
+ * physical, because the engine measures in physical coordinates.
157
+ */
158
+ padding-block: var(--_gap);
159
+ padding-inline: 0;
160
+
161
+ /* The layer is only a frame around the surface; the surface takes the
162
+ pointer, and the frame must not swallow a press meant for the page. */
163
+ pointer-events: none;
164
+
165
+ opacity: 0;
166
+ /**
167
+ * R7: the layer arrives along the axis it opens on — from one space step
168
+ * above its place when it opens downward, from one step below when it
169
+ * opens upward — and never along the inline axis. A `scale` from the
170
+ * corner the positioner named as origin moved the far edge by four per
171
+ * cent of the width, which read as an entrance from the left.
172
+ */
173
+ --_enter-distance: var(--slotted-space-2, 0.25rem);
174
+ translate: 0 calc(-1 * var(--_enter-distance));
175
+ transition:
176
+ opacity var(--slotted-duration-short) var(--slotted-easing-exit),
177
+ translate var(--slotted-duration-short) var(--slotted-easing-exit);
178
+ }
179
+
180
+ [data-ui='menu'][data-side='top'] {
181
+ translate: 0 var(--_enter-distance);
182
+ }
183
+
184
+ /* A submenu, or a menu placed beside its anchor, arrives along the inline
185
+ axis it opens on: from one step inside the parent's edge (M1). */
186
+ [data-ui='menu'][data-side='right'] {
187
+ translate: calc(-1 * var(--_enter-distance)) 0;
188
+ }
189
+
190
+ [data-ui='menu'][data-side='left'] {
191
+ translate: var(--_enter-distance) 0;
192
+ }
193
+
194
+ [data-ui='menu']:is([data-side='left'], [data-side='right']) {
195
+ padding-block: 0;
196
+ padding-inline: var(--_gap);
197
+ }
198
+
199
+ /**
200
+ * The entrance is a keyframe animation and not a transition. A transition
201
+ * starts from the style the browser computed last, and the positioner
202
+ * writes `data-side` in the same style update as the family opens the
203
+ * layer, so a transition set out from the closed style of the side the
204
+ * layer had before it had one — the bottom's — whatever side it opens on.
205
+ * A keyframe states where it starts. The exit stays a transition: it
206
+ * leaves from wherever the layer is, on the closed rule's durations.
207
+ */
208
+ [data-ui='menu'][data-open] {
209
+ opacity: 1;
210
+ translate: 0 0;
211
+ transition: none;
212
+ }
213
+
214
+ /**
215
+ * Only a layer the positioner placed arrives: it writes `data-positioned`
216
+ * with the side, and a layer written open in static markup — the harness,
217
+ * a docs snippet — is simply there, and is photographed where it stands.
218
+ */
219
+ [data-ui='menu'][data-open][data-positioned] {
220
+ animation: slotted-menu-enter-down var(--slotted-duration-medium) var(--slotted-easing-enter);
221
+ }
222
+
223
+ [data-ui='menu'][data-open][data-positioned][data-side='top'] {
224
+ animation-name: slotted-menu-enter-up;
225
+ }
226
+
227
+ [data-ui='menu'][data-open][data-positioned][data-side='right'] {
228
+ animation-name: slotted-menu-enter-from-start;
229
+ }
230
+
231
+ [data-ui='menu'][data-open][data-positioned][data-side='left'] {
232
+ animation-name: slotted-menu-enter-from-end;
233
+ }
234
+
235
+ @keyframes slotted-menu-enter-down {
236
+ from {
237
+ opacity: 0;
238
+ translate: 0 calc(-1 * var(--_enter-distance));
239
+ }
240
+ }
241
+
242
+ @keyframes slotted-menu-enter-up {
243
+ from {
244
+ opacity: 0;
245
+ translate: 0 var(--_enter-distance);
246
+ }
247
+ }
248
+
249
+ @keyframes slotted-menu-enter-from-start {
250
+ from {
251
+ opacity: 0;
252
+ translate: calc(-1 * var(--_enter-distance)) 0;
253
+ }
254
+ }
255
+
256
+ @keyframes slotted-menu-enter-from-end {
257
+ from {
258
+ opacity: 0;
259
+ translate: var(--_enter-distance) 0;
260
+ }
261
+ }
262
+
263
+ @keyframes slotted-menu-fade {
264
+ from {
265
+ opacity: 0;
266
+ }
267
+ }
268
+
269
+ /* Stillness keeps the fade and drops the movement (R7). */
270
+ @media (prefers-reduced-motion: reduce) {
271
+ [data-ui='menu'],
272
+ [data-ui='menu'][data-side='top'],
273
+ [data-ui='menu'][data-side='left'],
274
+ [data-ui='menu'][data-side='right'] {
275
+ translate: 0 0;
276
+ transition: opacity var(--slotted-duration-short) var(--slotted-easing-exit);
277
+ }
278
+
279
+ [data-ui='menu'][data-open][data-positioned],
280
+ [data-ui='menu'][data-open][data-positioned][data-side='top'],
281
+ [data-ui='menu'][data-open][data-positioned][data-side='left'],
282
+ [data-ui='menu'][data-open][data-positioned][data-side='right'] {
283
+ animation-name: slotted-menu-fade;
284
+ }
285
+ }
286
+
287
+ /* The anchor scrolled out of its own clipper. The menu is still open and its
288
+ commands still belong to a control the reader can no longer see. */
289
+ [data-ui='menu'][data-detached] {
290
+ visibility: hidden;
291
+ }
292
+
293
+ [data-ui='menu'][data-size='sm'] {
294
+ --_item-padding: var(--slotted-control-padding-inline-sm, 0.625rem);
295
+ --_item-height: var(--slotted-control-height-sm, 30px);
296
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
297
+ --_secondary-font-size: var(--slotted-font-size-xs, 0.75rem);
298
+ --_icon-size: var(--slotted-control-icon-size-sm, 14px);
299
+ --_column-gap: var(--slotted-control-gap-sm, 0.375rem);
300
+ --_measure: var(--slotted-menu-measure-sm, 14rem);
301
+ }
302
+
303
+ [data-ui='menu'][data-size='lg'] {
304
+ --_item-padding: var(--slotted-control-padding-inline-lg, 1rem);
305
+ --_item-height: var(--slotted-control-height-lg, 38px);
306
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
307
+ --_secondary-font-size: var(--slotted-control-font-size-sm, 12px);
308
+ --_icon-size: var(--slotted-control-icon-size-lg, 18px);
309
+ --_column-gap: var(--slotted-control-gap-lg, 0.5rem);
310
+ --_measure: var(--slotted-menu-measure-lg, 20rem);
311
+ }
312
+
313
+ /* ── The surface ──────────────────────────────────────────────────────── */
314
+
315
+ /**
316
+ * Four columns, and the height the positioner says is left. Radix, Base UI
317
+ * and React Aria each publish the remaining height as a custom property and
318
+ * none of them bounds itself by default, so a menu of thirty commands runs
319
+ * off the bottom of the screen. `overscroll-behavior` keeps the page still
320
+ * once the surface has scrolled to its end.
321
+ */
322
+ [data-ui='menu'] > [data-part='content'] {
323
+ background: var(--_surface);
324
+ border: var(--_border-width) solid var(--_border-color);
325
+ border-radius: var(--_radius);
326
+ box-shadow: var(--_shadow);
327
+ box-sizing: border-box;
328
+ color: var(--_text);
329
+ display: grid;
330
+ font-family: var(--slotted-font-family-sans);
331
+ font-size: var(--_font-size);
332
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
333
+ max-block-size: calc(var(--slotted-positioner-available-height, 100vh) - 2 * var(--_gap));
334
+ outline: none;
335
+ overflow-y: auto;
336
+ overscroll-behavior: contain;
337
+ padding-block: var(--_inset-block);
338
+ padding-inline: var(--_inset-inline);
339
+ pointer-events: auto;
340
+ }
341
+
342
+ /**
343
+ * The surface itself never takes a focus ring. Focus lives on an item from
344
+ * the moment the menu opens, and a ring around the whole surface would say
345
+ * the surface is the thing being operated.
346
+ */
347
+ [data-ui='menu'] > [data-part='content']:focus-visible {
348
+ outline: none;
349
+ }
350
+
351
+ /* ── Groups ───────────────────────────────────────────────────────────── */
352
+
353
+ /**
354
+ * A group is a grid area spanning every column, and a grid of its own that
355
+ * inherits them, so its items keep the menu's columns instead of starting a
356
+ * second set. Without the subgrid a group would align its own labels and
357
+ * disagree with the items outside it.
358
+ */
359
+ [data-ui='menu'] [data-part='group'] {
360
+ display: grid;
361
+ grid-column: 1 / -1;
362
+ grid-template-columns: subgrid;
363
+ }
364
+
365
+ [data-ui='menu'] [data-part='group-label'] {
366
+ color: var(--_muted);
367
+ font-size: var(--_secondary-font-size);
368
+ font-weight: var(--slotted-font-weight-semibold, 600);
369
+ grid-column: 1 / -1;
370
+ line-height: var(--_secondary-leading);
371
+ padding-block: var(--slotted-space-2, 0.25rem);
372
+ padding-inline: var(--_item-inset);
373
+ }
374
+
375
+ /* ── Header ───────────────────────────────────────────────────────────── */
376
+
377
+ /**
378
+ * Who the menu belongs to: an avatar or a mark, a name, a line under it. It
379
+ * spans every column and keeps a grid of its own rather than the surface's
380
+ * subgrid, because a 32-pixel avatar in the mark column would widen that
381
+ * column for every item below it. The media takes the first track and both
382
+ * lines; the name and its line reuse `label` and `description`. It starts
383
+ * at the item inset, so the avatar's edge is the icons' edge.
384
+ */
385
+ [data-ui='menu'] [data-part='header'] {
386
+ align-items: center;
387
+ column-gap: var(--slotted-space-5, 0.75rem);
388
+ display: grid;
389
+ grid-column: 1 / -1;
390
+ grid-template-columns: auto minmax(0, 1fr);
391
+ line-height: var(--_leading);
392
+ padding-block: var(--slotted-space-2, 0.25rem);
393
+ padding-inline: var(--_item-inset);
394
+ }
395
+
396
+ [data-ui='menu'] [data-part='header'] > :not([data-part='label'], [data-part='description']) {
397
+ grid-column: 1;
398
+ grid-row: 1 / span 2;
399
+ }
400
+
401
+ [data-ui='menu'] [data-part='header'] > [data-part='label'] {
402
+ font-weight: var(--slotted-font-weight-semibold, 600);
403
+ }
404
+
405
+ /* ── Items ────────────────────────────────────────────────────────────── */
406
+
407
+ /**
408
+ * A native `button` carrying `role="menuitem"`: the platform gives the press,
409
+ * the Enter and Space handling and the pointer semantics, and the role
410
+ * replaces `button` so a reader hears an item. Base UI and Ark render a `div`
411
+ * and reimplement all three.
412
+ *
413
+ * `min-block-size` rather than a height, because an item with a description
414
+ * is two lines tall and still has to clear the target minimum.
415
+ *
416
+ * The hand, as on every button in the library and on every list row of
417
+ * `select`, `combobox`, `listbox` and `command-palette`. The arrow it wore
418
+ * before followed Radix, Base UI and Ark, which follow the operating
419
+ * system's own menus; this surface is a `div` on the page, and the argument
420
+ * `select` used to take the hand for its options holds here unchanged. An
421
+ * unavailable item keeps the hand and refuses the press, as `button` does.
422
+ */
423
+ [data-ui='menu'] [data-part='item'] {
424
+ appearance: none;
425
+ background: transparent;
426
+ border: 0;
427
+ border-radius: var(--_item-radius);
428
+ box-sizing: border-box;
429
+ color: inherit;
430
+ cursor: pointer;
431
+ display: grid;
432
+ font: inherit;
433
+ grid-column: 1 / -1;
434
+ grid-template-columns: subgrid;
435
+ align-content: center;
436
+ /**
437
+ * The line box belongs to the item, and every cell inherits it. `1lh` on a
438
+ * mark resolves against the mark's *own* line-height, so a mark inside an
439
+ * item that left the line box at `normal` measured 15 pixels while the
440
+ * label beside it measured 19.5 — and start-aligned in the same row, the
441
+ * mark sat two pixels above the text it annotates. Stating it once here is
442
+ * what makes `1lh` mean the same thing in all four columns.
443
+ */
444
+ line-height: var(--_leading);
445
+ min-block-size: var(--_item-height);
446
+ padding-block: var(--slotted-space-2, 0.25rem);
447
+ padding-inline: var(--_item-inset);
448
+ /* The one gap inside an item, between a label and the line under it. The
449
+ two lines set solid read as one wrapped sentence rather than as a
450
+ command and its explanation. */
451
+ row-gap: var(--slotted-space-1, 0.125rem);
452
+ text-align: start;
453
+ text-decoration: none;
454
+ transition:
455
+ background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
456
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
457
+ color var(--slotted-control-transition-duration, var(--slotted-duration-short))
458
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
459
+ }
460
+
461
+ /**
462
+ * The highlight is painted on focus rather than on hover, and that is the
463
+ * decision the family turns on: the pointer entering an item moves real focus
464
+ * to it, so exactly one item is highlighted whichever device is driving.
465
+ * Hovering one item while the keyboard has moved to another is the state
466
+ * `data-highlighted` exists to prevent in Radix and Ark; here the platform
467
+ * prevents it, because there is only ever one focused element.
468
+ */
469
+ [data-ui='menu'] [data-part='item']:focus {
470
+ background-color: var(--_highlight);
471
+ color: var(--_accent);
472
+ }
473
+
474
+ /**
475
+ * The ring only when the keyboard put focus there. A pointer-driven highlight
476
+ * is already unambiguous, and a ring under the pointer would draw a box the
477
+ * reader did not ask for. Inset, so it never overflows the surface's own
478
+ * rounded edge.
479
+ */
480
+ [data-ui='menu'] [data-part='item']:focus-visible {
481
+ outline: var(--slotted-focus-ring-width, 2px) solid
482
+ var(--slotted-focus-ring-color, currentColor);
483
+ outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
484
+ }
485
+
486
+ [data-ui='menu'] [data-part='item']:not([data-disabled]):active {
487
+ background-color: var(--_highlight-active);
488
+ }
489
+
490
+ /**
491
+ * A parent item stays highlighted while its submenu is open, so the path from
492
+ * the bar to the surface under the pointer reads as one line.
493
+ */
494
+ [data-ui='menu'] [data-part='item'][data-open] {
495
+ background-color: var(--_highlight);
496
+ }
497
+
498
+ /**
499
+ * Disabled items are focusable and cannot be activated, which the pattern
500
+ * requires. They therefore have to answer focus with something, and answer it
501
+ * more quietly than an item that would do anything: the ground is the same
502
+ * highlight, so the reader still sees where they are, and the text keeps the
503
+ * disabled colour, so the item still reads as unavailable. The theme's
504
+ * disabled surface stood where the highlight stands now and measured ΔL 0.005
505
+ * against the dark surface and 0 against the high-contrast one: the pointer
506
+ * landed on an item and nothing answered.
507
+ */
508
+ [data-ui='menu'] [data-part='item'][data-disabled] {
509
+ color: var(--slotted-disabled-foreground, #64748b);
510
+ cursor: not-allowed;
511
+ }
512
+
513
+ [data-ui='menu'] [data-part='item'][data-disabled]:focus {
514
+ background-color: var(--_highlight);
515
+ color: var(--slotted-disabled-foreground, #64748b);
516
+ }
517
+
518
+ /* ── The cells ────────────────────────────────────────────────────────── */
519
+
520
+ /**
521
+ * Column one, and the reason a checkbox item and a command item line their
522
+ * labels up: the mark's box exists whether or not the mark is drawn, so a
523
+ * menu of options does not shift by sixteen pixels as one is checked.
524
+ */
525
+ [data-ui='menu'] :is([data-part='indicator'], [data-part='icon']),
526
+ [data-ui='menu'] [data-part='item'][aria-haspopup='menu'] > [data-ui='icon'] {
527
+ /**
528
+ * One line box tall, so a mark beside a label that wrapped onto three
529
+ * lines sits on the first of them rather than in the middle of the item —
530
+ * and, because the item states the line box every cell inherits, exactly
531
+ * as tall as the line it sits beside. The mark is then centred on the text
532
+ * by construction rather than by a nudge that has to be retuned at every
533
+ * size and density.
534
+ */
535
+ block-size: 1lh;
536
+ }
537
+
538
+ /**
539
+ * The mark is the Glyph element the binding renders in the box — `check` for
540
+ * a checkbox item, `dot` for a radio item — and it is shown while the item
541
+ * is checked.
542
+ */
543
+ [data-ui='menu'] [data-part='indicator'] {
544
+ --slotted-icon-size: var(--_icon-size);
545
+
546
+ align-items: center;
547
+ display: flex;
548
+ grid-column: 1;
549
+ grid-row: 1;
550
+ inline-size: var(--_icon-size);
551
+ justify-content: center;
552
+ justify-self: center;
553
+ margin-inline-end: var(--_column-gap);
554
+ opacity: 0;
555
+ /**
556
+ * The mark arrives and leaves: a fade with a small growth from the centre
557
+ * of its column. 0.8 rather than the 0.95 a panel starts from, because at
558
+ * sixteen pixels 0.95 moves the glyph's edge by under half a pixel and the
559
+ * growth is not seen; 0.8 moves it by 1.6. Leaving takes the exit curve.
560
+ */
561
+ scale: 0.8;
562
+ transition:
563
+ opacity var(--slotted-duration-short) var(--slotted-easing-exit),
564
+ scale var(--slotted-duration-short) var(--slotted-easing-exit);
565
+ }
566
+
567
+ [data-ui='menu'] [data-part='item'][data-checked] > [data-part='indicator'] {
568
+ opacity: 1;
569
+ scale: 1;
570
+ transition:
571
+ opacity var(--slotted-duration-short) var(--slotted-easing-enter),
572
+ scale var(--slotted-duration-short) var(--slotted-easing-enter);
573
+ }
574
+
575
+ /* The dot is drawn smaller than the check, at seven tenths of the column's glyph. */
576
+ [data-ui='menu'] [data-part='item'][role='menuitemradio'] > [data-part='indicator'] {
577
+ --slotted-icon-size: calc(var(--_icon-size) * 0.7);
578
+ }
579
+
580
+ /**
581
+ * The same column the indicator uses, and never both on one item. The
582
+ * consumer supplies the icon and the family supplies the column, so a menu
583
+ * whose second and fifth items have icons still reads as one list rather
584
+ * than as two.
585
+ *
586
+ * The mark takes the item's own colour rather than the muted one. Painted
587
+ * muted, a disabled item's icon was the same grey as an enabled item's —
588
+ * `--slotted-text-muted` and `--slotted-disabled-foreground` are four points
589
+ * apart in the light scheme — so the state the item was in could be read from
590
+ * its label and not from its mark.
591
+ *
592
+ * What fills it is content: the library's own `Glyph` in an icon box, or an
593
+ * element of the consumer's own, centred. The stylesheet paints nothing
594
+ * here.
595
+ *
596
+ * The box sizes what it holds: an icon box reads `--slotted-icon-size` from
597
+ * its container, and a bare glyph host such as `ng-icon` — which sizes
598
+ * itself at `1em` — reads the font-size the child rule below gives it.
599
+ * Nothing placed here is drawn at the item's type size. The font-size is on
600
+ * the child and not on the box, because the box's `1lh` reads the item's
601
+ * line box.
602
+ */
603
+ [data-ui='menu'] [data-part='icon'] {
604
+ --slotted-icon-size: var(--_icon-size);
605
+
606
+ align-items: center;
607
+ display: flex;
608
+ grid-column: 1;
609
+ grid-row: 1;
610
+ inline-size: var(--_icon-size);
611
+ justify-content: center;
612
+ justify-self: center;
613
+ margin-inline-end: var(--_column-gap);
614
+ }
615
+
616
+ [data-ui='menu'] [data-part='icon'] > * {
617
+ font-size: var(--_icon-size);
618
+ }
619
+
620
+ /* Column two, and the only column that gives way. */
621
+ [data-ui='menu'] [data-part='label'] {
622
+ grid-column: 2;
623
+ grid-row: 1;
624
+ min-inline-size: 0;
625
+ overflow-wrap: anywhere;
626
+ }
627
+
628
+ [data-ui='menu'] [data-part='description'] {
629
+ color: var(--_muted);
630
+ font-size: var(--_secondary-font-size);
631
+ grid-column: 2;
632
+ grid-row: 2;
633
+ line-height: var(--_secondary-leading);
634
+ min-inline-size: 0;
635
+ overflow-wrap: anywhere;
636
+ }
637
+
638
+ [data-ui='menu'] [data-part='item'][data-disabled] > [data-part='description'] {
639
+ color: inherit;
640
+ }
641
+
642
+ /**
643
+ * Column three: what the item holds or counts — "12", a status, the value a
644
+ * submenu is set to. Its own column, so the metas of a menu line up with
645
+ * each other rather than each stopping wherever its label ended, which is
646
+ * where a `margin-inline-start: auto` badge lands. End-aligned in the
647
+ * column, so counts stack on their last digit and a word ends where the
648
+ * numbers end; tabular figures, muted like the shortcut, never wrapped.
649
+ */
650
+ [data-ui='menu'] [data-part='meta'] {
651
+ align-items: center;
652
+ color: var(--_muted);
653
+ column-gap: var(--slotted-space-2, 0.25rem);
654
+ display: flex;
655
+ font-variant-numeric: tabular-nums;
656
+ grid-column: 3;
657
+ grid-row: 1;
658
+ justify-self: end;
659
+ margin-inline-start: var(--slotted-space-6, 1rem);
660
+ white-space: nowrap;
661
+ }
662
+
663
+ /**
664
+ * Column four. The shortcut is set in the same face as the label and one step
665
+ * quieter, so it reads as an annotation rather than as a second command; a
666
+ * `kbd` placed here instead paints its own keys and this rule leaves it
667
+ * alone. Never narrower than its own content: a long label wraps in column
668
+ * two rather than pushing the binding off the surface.
669
+ */
670
+ [data-ui='menu'] [data-part='shortcut'] {
671
+ color: var(--_muted);
672
+ grid-column: 4;
673
+ grid-row: 1;
674
+ justify-self: end;
675
+ margin-inline-start: var(--slotted-space-6, 1rem);
676
+ white-space: nowrap;
677
+ }
678
+
679
+ [data-ui='menu']
680
+ [data-part='item'][data-disabled]
681
+ > :is([data-part='shortcut'], [data-part='meta']),
682
+ [data-ui='menu'] [data-part='item'][data-disabled][aria-haspopup='menu'] > [data-ui='icon'] {
683
+ color: inherit;
684
+ }
685
+
686
+ /**
687
+ * The mark that says an item opens a submenu: the `chevron-right` Glyph
688
+ * element the binding puts at the end of the item, in a `directional` box so
689
+ * a menu inside a mirrored region turns without an ancestor saying so. It
690
+ * takes the shortcut's column, because an item that opens a submenu runs no
691
+ * command and so carries no binding.
692
+ */
693
+ [data-ui='menu'] [data-part='item'][aria-haspopup='menu'] > [data-ui='icon'] {
694
+ --slotted-icon-size: var(--_icon-size);
695
+
696
+ grid-column: 4;
697
+ grid-row: 1;
698
+ justify-self: end;
699
+ /* The box sets its own line-height to 1 for inline alignment, and `1lh`
700
+ above reads it: the item's line box is taken back, or the chevron is a
701
+ type size tall and sits above the label it annotates. */
702
+ line-height: inherit;
703
+ margin-inline-start: var(--_column-gap);
704
+ /* Quieter than the label by the same step as the shortcut it replaces. */
705
+ color: var(--_muted);
706
+ }
707
+
708
+ /* ── Separator, empty, loading ────────────────────────────────────────── */
709
+
710
+ [data-ui='menu'] [data-part='separator'] {
711
+ background-color: var(--slotted-menu-separator-color, var(--_border-color));
712
+ block-size: var(--_border-width);
713
+ border: 0;
714
+ grid-column: 1 / -1;
715
+ margin-block: var(--slotted-space-2, 0.25rem);
716
+ margin-inline: 0;
717
+ }
718
+
719
+ /**
720
+ * A menu whose items were all filtered away, and a menu still waiting for
721
+ * them. Both keep the surface's measure rather than collapsing to a
722
+ * two-pixel box, which is what a consumer sees flash when the state is not
723
+ * painted at all.
724
+ *
725
+ * The measure they keep is the surface's own floor and no longer a second one
726
+ * stated here: a row that was wider than the menu around it made the menu
727
+ * grow when its list emptied (M3).
728
+ */
729
+ [data-ui='menu'] [data-part='empty'] {
730
+ color: var(--_muted);
731
+ grid-column: 1 / -1;
732
+ padding-block: var(--slotted-space-5, 0.75rem);
733
+ padding-inline: var(--_item-inset);
734
+ text-align: center;
735
+ }
736
+
737
+ [data-ui='menu'] > [data-part='content'][data-loading] {
738
+ cursor: progress;
739
+ }
740
+
741
+ /* ── Tones ────────────────────────────────────────────────────────────── */
742
+
743
+ /**
744
+ * A destructive command reads as destructive before it is read at all. The
745
+ * tone paints the label, the icon and the highlight together, from the five
746
+ * the theme already carries at the contrast every scheme needs; `danger` is
747
+ * the one a product reaches for, and the other four cost one line each.
748
+ */
749
+ [data-ui='menu'] [data-part='item'][data-tone='accent'] {
750
+ --_accent: var(--slotted-tone-accent-text);
751
+ --_highlight: var(--slotted-tone-accent-surface);
752
+ --_highlight-active: var(--slotted-tone-accent-subtle-active);
753
+
754
+ color: var(--slotted-tone-accent-text);
755
+ }
756
+
757
+ [data-ui='menu'] [data-part='item'][data-tone='secondary'] {
758
+ --_accent: var(--slotted-tone-secondary-text);
759
+ --_highlight: var(--slotted-tone-secondary-surface);
760
+ --_highlight-active: var(--slotted-tone-secondary-subtle-active);
761
+
762
+ color: var(--slotted-tone-secondary-text);
763
+ }
764
+
765
+ [data-ui='menu'] [data-part='item'][data-tone='success'] {
766
+ --_accent: var(--slotted-tone-success-text);
767
+ --_highlight: var(--slotted-tone-success-surface);
768
+ --_highlight-active: var(--slotted-tone-success-subtle-active);
769
+
770
+ color: var(--slotted-tone-success-text);
771
+ }
772
+
773
+ [data-ui='menu'] [data-part='item'][data-tone='warning'] {
774
+ --_accent: var(--slotted-tone-warning-text);
775
+ --_highlight: var(--slotted-tone-warning-surface);
776
+ --_highlight-active: var(--slotted-tone-warning-subtle-active);
777
+
778
+ color: var(--slotted-tone-warning-text);
779
+ }
780
+
781
+ [data-ui='menu'] [data-part='item'][data-tone='danger'] {
782
+ --_accent: var(--slotted-tone-danger-text);
783
+ --_highlight: var(--slotted-tone-danger-surface);
784
+ --_highlight-active: var(--slotted-tone-danger-subtle-active);
785
+
786
+ color: var(--slotted-tone-danger-text);
787
+ }
788
+
789
+ /* An unavailable item keeps no tone: its highlight is the neutral one. */
790
+ [data-ui='menu'] [data-part='item'][data-tone][data-disabled] {
791
+ --_accent: var(--slotted-disabled-foreground, #64748b);
792
+ --_highlight: var(--slotted-menu-highlight, var(--slotted-tone-secondary-subtle-hover));
793
+
794
+ color: var(--slotted-disabled-foreground, #64748b);
795
+ }
796
+
797
+ /* ── Forced colours ───────────────────────────────────────────────────── */
798
+
799
+ /**
800
+ * The one thing the theme cannot reach. Every tone surface becomes `Canvas`
801
+ * under forced colours, so the highlight — which is this family's selection
802
+ * indicator, not decoration — would disappear and leave a reader with no way
803
+ * to tell which command they are on.
804
+ */
805
+ @media (forced-colors: active) {
806
+ [data-ui='menu'] [data-part='item']:focus {
807
+ background-color: Highlight;
808
+ color: HighlightText;
809
+ forced-color-adjust: none;
810
+ }
811
+
812
+ [data-ui='menu'] [data-part='item'][data-disabled]:focus {
813
+ background-color: Highlight;
814
+ color: GrayText;
815
+ }
816
+ }
817
+ }