@slotted/styles 0.0.0-stage → 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,937 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The row ────────────────────────────────────────────────────────────
4
+ *
5
+ * One entry of a `list`. The set decides four things — variant, size, marker
6
+ * and inset — and hands each one down through the binding as a `data-`
7
+ * attribute this sheet reads. **No selector here reaches `[data-ui='list']`,
8
+ * and none there reaches this family**, which is the rule `accordion` and
9
+ * `collapsible` set for the same relationship.
10
+ *
11
+ * **The root is never the interactive element.** `content` is: a `div` for a
12
+ * row that is read, a `button` for a row that is one command, an `a` for a
13
+ * row that is one destination. `actions` is `content`'s sibling and never its
14
+ * child, because interactive content may not nest inside `a` or `button` —
15
+ * the decision `tag` took for `remove`, taken once here for every row.
16
+ *
17
+ * The row draws its own marker. CSS has no `display` that is both
18
+ * `list-item` and a grid, so a row carrying a trailing control has already
19
+ * lost the native `::marker`; `list` writes `role="list"` for that reason and
20
+ * the mark becomes geometry from the glyph scale.
21
+ *
22
+ * **Every part is reached with child combinators**, along the anatomy's own
23
+ * chain: `root > content > start | body | end`, `body > label | description`,
24
+ * and `root > actions`. A row holds other families, and they reuse these
25
+ * names — a `tag` in `end` has a `body` and a `label` of its own. Reached by a
26
+ * descendant selector, the tag's body became a column with its text on the
27
+ * top edge and its label took the row's line height and weight. Angular puts
28
+ * each part on the element the page wrote and React renders it as that
29
+ * element, so the chain in the selectors is the chain in the document.
30
+ */
31
+ [data-ui='list-item'][data-part='root'] {
32
+ /* Geometry. Every value is the control scale's, because a row is a
33
+ control-sized thing: one theme decision retunes the rows, the buttons and
34
+ the fields together, and `@media (pointer: coarse)` grows all three at
35
+ once without this family writing a rule. */
36
+ --_min-block: var(--slotted-list-item-min-block-size, var(--slotted-control-height-md, 34px));
37
+ --_font-size: var(--slotted-list-item-font-size, var(--slotted-control-font-size-md, 13px));
38
+ --_icon-size: var(--slotted-list-item-icon-size, var(--slotted-control-icon-size-md, 16px));
39
+ /* The chooser's square, when the leading column holds one: the selection
40
+ scale `checkbox` draws in, which density does not move. */
41
+ --_control-size: var(--slotted-list-item-control-size, var(--slotted-selection-size-md, 16px));
42
+ --_gap: var(--slotted-list-item-gap, var(--slotted-control-gap-md, 8px));
43
+ --_pad-inline: var(
44
+ --slotted-list-item-padding-inline,
45
+ var(--slotted-control-padding-inline-md, 13px)
46
+ );
47
+ /**
48
+ * The block inset is what is left of the control height around one line
49
+ * of the title, so a one-line row is exactly the control height at every
50
+ * density — 34, 28 and 26 pixels at `md` — and a row of two lines grows by
51
+ * its second line and nothing else (LI3). Stated as a step of the space
52
+ * scale it was eight pixels at every density, and the row stood at 34px in
53
+ * `dense` as in `comfortable`: the one control on the page that density
54
+ * did not move. A floor of one space step keeps a row whose text is taller
55
+ * than its control from losing its inset.
56
+ */
57
+ --_pad-block: var(
58
+ --slotted-list-item-padding-block,
59
+ max(
60
+ var(--slotted-space-2, 0.25rem),
61
+ calc((var(--_min-block) - var(--_font-size) * var(--slotted-line-height-md, 1.5)) / 2)
62
+ )
63
+ );
64
+ --_radius: var(--slotted-list-item-radius, var(--slotted-radius-md, 6px));
65
+
66
+ /* Ink. `secondary` is the library's neutral, and a neutral row takes the
67
+ page's own text colour rather than the tone's: `--slotted-tone-secondary-text`
68
+ is calibrated on a chip, and a list of forty rows set in it reads as forty
69
+ pieces of metadata. The other four tones do take their own. */
70
+ --_text: var(--slotted-list-item-color, var(--slotted-text, CanvasText));
71
+ --_muted: var(--slotted-list-item-color-muted, var(--slotted-text-muted, GrayText));
72
+ /* The tone's four steps, named once so a state rule moves between them
73
+ instead of restating five tones. */
74
+ --_subtle-hover: var(--slotted-tone-secondary-subtle-hover, ButtonFace);
75
+ --_subtle-active: var(--slotted-tone-secondary-subtle-active, ButtonFace);
76
+ --_solid-hover: var(--slotted-tone-secondary-solid-hover, ButtonFace);
77
+ --_solid-active: var(--slotted-tone-secondary-solid-active, ButtonFace);
78
+ --_surface: var(--slotted-tone-secondary-surface, Canvas);
79
+ --_hover: var(--slotted-list-item-background-hover, var(--_subtle-hover));
80
+ --_active: var(--slotted-list-item-background-active, var(--_subtle-active));
81
+ /* A selected row is the accent ground with the accent ink, and the edge it
82
+ carries is a one-pixel ring in the accent border. Every tone surface is
83
+ white in the `high-contrast` scheme, so a selection said with a fill
84
+ alone is a selection that scheme cannot show — the defect the first
85
+ high-contrast capture found. `tag` reached for the same answer for the
86
+ same reason. The pointer states of a selected row are the accent pair's
87
+ own steps, so hovering a selected row never lands on the surface an
88
+ unselected hovered row has (R1). */
89
+ --_selected-edge: var(--slotted-tone-accent-border, ButtonBorder);
90
+ --_selected-surface: var(
91
+ --slotted-list-item-background-selected,
92
+ var(--slotted-tone-accent-surface, Canvas)
93
+ );
94
+ --_selected-text: var(--slotted-tone-accent-text, CanvasText);
95
+ --_selected-hover: var(--slotted-tone-accent-subtle-hover, ButtonFace);
96
+ --_selected-active: var(--slotted-tone-accent-subtle-active, ButtonFace);
97
+ /* A current row is the neutral ground with a one-pixel ring in the neutral
98
+ border, whatever the row's tone: it is where the reader is, not what
99
+ they chose, and it stays out of the tone's way so a row can be both at
100
+ once with the selection winning (R1). */
101
+ --_current-surface: var(
102
+ --slotted-list-item-background-current,
103
+ var(--slotted-tone-secondary-surface, Canvas)
104
+ );
105
+ --_current-edge: var(--slotted-tone-secondary-border, ButtonBorder);
106
+ --_rule: var(--slotted-list-item-border-color, var(--slotted-border-subtle, ButtonBorder));
107
+ --_rule-width: var(--slotted-list-item-border-width, var(--slotted-border-width-thin, 1px));
108
+ --_marker-size: calc(var(--_icon-size) * 0.75);
109
+
110
+ /* The row counts itself, whatever the marker is. Incrementing only under
111
+ `number` would restart the count the moment an application retuned the
112
+ axis, and the counter costs nothing when nothing reads it. */
113
+ box-sizing: border-box;
114
+ /**
115
+ * What the row is painted with, and it is the row that is painted rather
116
+ * than the box inside it. Read on `content`, a selected row with a trailing
117
+ * control showed a filled box that stopped in the middle of the row and a
118
+ * seam where `actions` began — half a row highlighted. The row is what is
119
+ * selected, hovered and pressed; `content` is only where the pointer lands.
120
+ */
121
+ --_fill: transparent;
122
+ /* Two shadows on one element: the rule above the row, and the edge a
123
+ selected row carries. Named separately so a selected ruled row keeps
124
+ both, which one `box-shadow` declaration overwriting another would not. */
125
+ --_rule-shadow: 0 0 0 0 transparent;
126
+ --_edge-shadow: 0 0 0 0 transparent;
127
+
128
+ background-color: var(--_fill);
129
+ border-radius: var(--_radius);
130
+ box-shadow: var(--_rule-shadow), var(--_edge-shadow);
131
+ counter-increment: slotted-list-item;
132
+ /* The fill and both shadows are one state change and move together. */
133
+ transition:
134
+ background-color var(--slotted-control-transition-duration, var(--slotted-duration-short))
135
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard)),
136
+ box-shadow var(--slotted-control-transition-duration, var(--slotted-duration-short))
137
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
138
+ /* The row answers its own inline size. The set does not: it has no layout
139
+ that depends on its width, and a container on both would make the row
140
+ query the set instead of the page. */
141
+ container-name: slotted-list-item;
142
+ container-type: inline-size;
143
+ display: flex;
144
+ align-items: stretch;
145
+ /* The row wraps, and only the container query ever makes it: `actions` is
146
+ given its own line below the breakpoint. Left on one line, a target
147
+ carrying a word rather than a glyph starved the box beside it — the
148
+ content shrank to eighty pixels and a one-word timestamp spilled out of
149
+ the column it was in. */
150
+ flex-wrap: wrap;
151
+ list-style: none;
152
+ min-inline-size: 0;
153
+ }
154
+
155
+ /**
156
+ * Size is the set's step, handed down. Density is the application's, and it
157
+ * arrives inside every control token above; the two never multiply, because
158
+ * this family reads the density-varying token and states no second scale.
159
+ */
160
+ [data-ui='list-item'][data-size='sm'] {
161
+ --_min-block: var(
162
+ --slotted-list-item-min-block-size-sm,
163
+ var(--slotted-control-height-sm, 30px)
164
+ );
165
+ --_font-size: var(--slotted-list-item-font-size-sm, var(--slotted-control-font-size-sm, 12px));
166
+ --_icon-size: var(--slotted-list-item-icon-size-sm, var(--slotted-control-icon-size-sm, 14px));
167
+ --_control-size: var(
168
+ --slotted-list-item-control-size-sm,
169
+ var(--slotted-selection-size-sm, 14px)
170
+ );
171
+ --_gap: var(--slotted-list-item-gap-sm, var(--slotted-control-gap-sm, 6px));
172
+ --_pad-inline: var(
173
+ --slotted-list-item-padding-inline-sm,
174
+ var(--slotted-control-padding-inline-sm, 10px)
175
+ );
176
+ --_pad-block: var(
177
+ --slotted-list-item-padding-block-sm,
178
+ max(
179
+ var(--slotted-space-2, 0.25rem),
180
+ calc((var(--_min-block) - var(--_font-size) * var(--slotted-line-height-md, 1.5)) / 2)
181
+ )
182
+ );
183
+ }
184
+
185
+ [data-ui='list-item'][data-size='lg'] {
186
+ --_min-block: var(
187
+ --slotted-list-item-min-block-size-lg,
188
+ var(--slotted-control-height-lg, 38px)
189
+ );
190
+ --_font-size: var(--slotted-list-item-font-size-lg, var(--slotted-control-font-size-lg, 14px));
191
+ --_icon-size: var(--slotted-list-item-icon-size-lg, var(--slotted-control-icon-size-lg, 18px));
192
+ --_control-size: var(
193
+ --slotted-list-item-control-size-lg,
194
+ var(--slotted-selection-size-lg, 18px)
195
+ );
196
+ --_gap: var(--slotted-list-item-gap-lg, var(--slotted-control-gap-lg, 8px));
197
+ --_pad-inline: var(
198
+ --slotted-list-item-padding-inline-lg,
199
+ var(--slotted-control-padding-inline-lg, 16px)
200
+ );
201
+ --_pad-block: var(
202
+ --slotted-list-item-padding-block-lg,
203
+ max(
204
+ var(--slotted-space-2, 0.25rem),
205
+ calc((var(--_min-block) - var(--_font-size) * var(--slotted-line-height-md, 1.5)) / 2)
206
+ )
207
+ );
208
+ }
209
+
210
+ /**
211
+ * ── Tone ───────────────────────────────────────────────────────────────
212
+ *
213
+ * Primer ships one of these — `danger`, for a destructive row — and hides the
214
+ * rest behind a boolean. The theme here carries all five with the same nine
215
+ * roles, so shipping one of a scale and calling the other four out of scope
216
+ * would be the invention rather than the restraint.
217
+ *
218
+ * A tone moves three things and no more: the ink of the title and the mark,
219
+ * the wash under the pointer, and the ground and ink of a selected row. It
220
+ * never fills a resting row, because a list of rows each filled with a tone
221
+ * is a list nobody can read. The current row is not the tone's to move.
222
+ */
223
+ [data-ui='list-item'][data-tone='accent'] {
224
+ --_text: var(--slotted-tone-accent-text, CanvasText);
225
+ --_subtle-hover: var(--slotted-tone-accent-subtle-hover, ButtonFace);
226
+ --_subtle-active: var(--slotted-tone-accent-subtle-active, ButtonFace);
227
+ --_solid-hover: var(--slotted-tone-accent-solid-hover, ButtonFace);
228
+ --_solid-active: var(--slotted-tone-accent-solid-active, ButtonFace);
229
+ --_surface: var(--slotted-tone-accent-surface, Canvas);
230
+ --_selected-edge: var(--slotted-tone-accent-border, ButtonBorder);
231
+ --_selected-surface: var(--slotted-list-item-background-selected, var(--_surface));
232
+ --_selected-text: var(--slotted-tone-accent-text, CanvasText);
233
+ --_selected-hover: var(--_subtle-hover);
234
+ --_selected-active: var(--_subtle-active);
235
+ }
236
+
237
+ [data-ui='list-item'][data-tone='success'] {
238
+ --_text: var(--slotted-tone-success-text, CanvasText);
239
+ --_subtle-hover: var(--slotted-tone-success-subtle-hover, ButtonFace);
240
+ --_subtle-active: var(--slotted-tone-success-subtle-active, ButtonFace);
241
+ --_solid-hover: var(--slotted-tone-success-solid-hover, ButtonFace);
242
+ --_solid-active: var(--slotted-tone-success-solid-active, ButtonFace);
243
+ --_surface: var(--slotted-tone-success-surface, Canvas);
244
+ --_selected-edge: var(--slotted-tone-success-border, ButtonBorder);
245
+ --_selected-surface: var(--slotted-list-item-background-selected, var(--_surface));
246
+ --_selected-text: var(--slotted-tone-success-text, CanvasText);
247
+ --_selected-hover: var(--_subtle-hover);
248
+ --_selected-active: var(--_subtle-active);
249
+ }
250
+
251
+ [data-ui='list-item'][data-tone='warning'] {
252
+ --_text: var(--slotted-tone-warning-text, CanvasText);
253
+ --_subtle-hover: var(--slotted-tone-warning-subtle-hover, ButtonFace);
254
+ --_subtle-active: var(--slotted-tone-warning-subtle-active, ButtonFace);
255
+ --_solid-hover: var(--slotted-tone-warning-solid-hover, ButtonFace);
256
+ --_solid-active: var(--slotted-tone-warning-solid-active, ButtonFace);
257
+ --_surface: var(--slotted-tone-warning-surface, Canvas);
258
+ --_selected-edge: var(--slotted-tone-warning-border, ButtonBorder);
259
+ --_selected-surface: var(--slotted-list-item-background-selected, var(--_surface));
260
+ --_selected-text: var(--slotted-tone-warning-text, CanvasText);
261
+ --_selected-hover: var(--_subtle-hover);
262
+ --_selected-active: var(--_subtle-active);
263
+ }
264
+
265
+ [data-ui='list-item'][data-tone='danger'] {
266
+ --_text: var(--slotted-tone-danger-text, CanvasText);
267
+ --_subtle-hover: var(--slotted-tone-danger-subtle-hover, ButtonFace);
268
+ --_subtle-active: var(--slotted-tone-danger-subtle-active, ButtonFace);
269
+ --_solid-hover: var(--slotted-tone-danger-solid-hover, ButtonFace);
270
+ --_solid-active: var(--slotted-tone-danger-solid-active, ButtonFace);
271
+ --_surface: var(--slotted-tone-danger-surface, Canvas);
272
+ --_selected-edge: var(--slotted-tone-danger-border, ButtonBorder);
273
+ --_selected-surface: var(--slotted-list-item-background-selected, var(--_surface));
274
+ --_selected-text: var(--slotted-tone-danger-text, CanvasText);
275
+ --_selected-hover: var(--_subtle-hover);
276
+ --_selected-active: var(--_subtle-active);
277
+ }
278
+
279
+ /**
280
+ * ── The marker ─────────────────────────────────────────────────────────
281
+ *
282
+ * A character is never an icon — `docs/token-foundation.md` rule 3 — so the
283
+ * bullet is the library's own dot `Glyph`, an `<svg>` the binding draws in a
284
+ * `marker` part before the row's content and in the row's ink, not a `•` in a
285
+ * `content` string that changes shape with the reader's font.
286
+ *
287
+ * The number is a counter and stays text, because it is text: a reader hears
288
+ * "one" before the row, which is what the native marker said. It is the
289
+ * root's own `::before`, which stands in the same column the bullet's part
290
+ * does.
291
+ *
292
+ * The column is one line tall rather than the row's own height, so the mark
293
+ * sits on the title's line in a row of three lines instead of floating in the
294
+ * middle of the block.
295
+ */
296
+ [data-ui='list-item'][data-marker='bullet'] > [data-part='marker'],
297
+ [data-ui='list-item'][data-marker='number']::before {
298
+ /* The mark sits on the title's first line and never in the middle of the
299
+ block, whatever `align` says. `align` is about the leading visual — an
300
+ avatar beside three lines of text belongs with the title or with the
301
+ block, and both readings are defensible — while a list marker has only
302
+ one reading, and it is the platform's own. The first capture centred a
303
+ bullet between two lines and read as a bullet nobody had placed. */
304
+ align-items: start;
305
+ /* One line tall, stated rather than stretched. The mark's own box holds no
306
+ text, so a row that wraps gave it the height of its padding alone and the
307
+ dot disappeared — the numbered marker survived only because its digits
308
+ gave it a height of their own. */
309
+ block-size: calc(
310
+ var(--_pad-block) * 2 + var(--_font-size) * var(--slotted-line-height-md, 1.5)
311
+ );
312
+ box-sizing: border-box;
313
+ /* The row's ink, not its muted step: a marker is part of what the row says,
314
+ which is why the browser's own `::marker` inherits `color`. */
315
+ color: var(--_text);
316
+ display: flex;
317
+ flex: none;
318
+ font-size: var(--_font-size);
319
+ /* A ten beside a nine is only a column if the digits are the same width. */
320
+ font-variant-numeric: tabular-nums;
321
+ /* The column is the glyph's own width and the gap beside it is the control
322
+ scale's, so the mark and the leading visual of the row below stand in one
323
+ column and the text starts on one line. Read as one box with the gap
324
+ inside it, the number and the title had nothing between them. */
325
+ inline-size: var(--_icon-size);
326
+ line-height: var(--slotted-line-height-md, 1.5);
327
+ margin-inline-end: var(--_gap);
328
+ margin-inline-start: var(--_pad-inline);
329
+ padding-block-start: var(--_pad-block);
330
+ }
331
+
332
+ [data-ui='list-item'][data-marker='bullet'] > [data-part='marker'] {
333
+ --slotted-icon-size: var(--_marker-size);
334
+ justify-content: center;
335
+ }
336
+
337
+ /* The dot is placed on the middle of the first line rather than on the
338
+ middle of the part, because the part is one line and its padding tall and
339
+ the dot has no baseline to sit on. Half the line less half the dot, so the
340
+ dot stands between the baseline and the cap line rather than on either. */
341
+ [data-ui='list-item'][data-marker='bullet'] > [data-part='marker'] > [data-ui='icon'] {
342
+ margin-block-start: calc(
343
+ var(--_font-size) * var(--slotted-line-height-md, 1.5) * 0.5 - var(--_marker-size) * 0.5
344
+ );
345
+ }
346
+
347
+ [data-ui='list-item'][data-marker='number']::before {
348
+ content: counter(slotted-list-item) '.';
349
+ justify-content: end;
350
+ }
351
+
352
+ /* The mark already stands in its own gutter, so the box beside it gives up
353
+ the padding it would otherwise repeat. */
354
+ [data-ui='list-item']:is([data-marker='bullet'], [data-marker='number']) > [data-part='content'] {
355
+ padding-inline-start: 0;
356
+ }
357
+
358
+ /* `inset` is the set's answer for a list inside a box that already pads its
359
+ own content. The mark gives up its gutter with everything else. */
360
+ [data-ui='list-item'][data-inset][data-marker='bullet'] > [data-part='marker'],
361
+ [data-ui='list-item'][data-inset][data-marker='number']::before {
362
+ margin-inline-start: 0;
363
+ }
364
+
365
+ /**
366
+ * ── The box that is read, and sometimes activated ──────────────────────
367
+ *
368
+ * Three columns: the leading visual, the text, and the metadata that belongs
369
+ * to what the row says. Each gap is a margin on the optional part rather than
370
+ * a `column-gap` on the grid, because a gap between two empty tracks is still
371
+ * a gap — a row with no leading visual would start one gap in from every row
372
+ * that has one.
373
+ *
374
+ * The reset is what makes a `button` and an `a` the same box as a `div`: a
375
+ * consumer choosing between the three chooses an accessibility tree, never a
376
+ * different picture.
377
+ */
378
+ [data-ui='list-item'] > [data-part='content'] {
379
+ appearance: none;
380
+ background-color: transparent;
381
+ border: none;
382
+ /* The row's corners, so the focus ring follows the shape the reader sees
383
+ rather than a rectangle inside it. */
384
+ border-radius: inherit;
385
+ /* The floor is a border-box height, not a content-box one. Read as content
386
+ the row stood fifty pixels tall for one line of thirteen-pixel text: the
387
+ padding was counted twice, once by the box and once by the floor. */
388
+ box-sizing: border-box;
389
+ color: var(--_text);
390
+ display: grid;
391
+ /* A basis of nought rather than of its content. Flex wraps before it
392
+ shrinks, so a box whose basis is its own text pushed itself onto a second
393
+ line the moment the row was narrow — the marker stood alone above its
394
+ own title at 240 pixels. With nothing to wrap, only the item that asks
395
+ for a whole line gets one, which is what `actions` does below the
396
+ breakpoint. */
397
+ flex: 1 1 0%;
398
+ font: inherit;
399
+ font-size: var(--_font-size);
400
+ grid-template-areas: 'start body end';
401
+ grid-template-columns: auto minmax(0, 1fr) auto;
402
+ align-items: center;
403
+ min-block-size: var(--_min-block);
404
+ min-inline-size: 0;
405
+ padding-block: var(--_pad-block);
406
+ padding-inline: var(--_pad-inline);
407
+ text-align: start;
408
+ text-decoration: none;
409
+ transition: color var(--slotted-control-transition-duration, var(--slotted-duration-short))
410
+ var(--slotted-control-transition-easing, var(--slotted-easing-standard));
411
+ }
412
+
413
+ /**
414
+ * A variant that draws a line between rows wants a square fill, because a
415
+ * rounded wash inside a full-bleed rule fights the line it sits on. The two
416
+ * variants that draw no line — a plain run of rows, and a row that is its own
417
+ * surface — round it, which is what a selected row in a sidebar looks like
418
+ * everywhere it is done well.
419
+ *
420
+ * `framed` clips its own corners at the set, so the first and last rows are
421
+ * rounded by the box rather than by themselves.
422
+ */
423
+ [data-ui='list-item']:is([data-variant='ruled'], [data-variant='framed']) {
424
+ border-radius: 0;
425
+ }
426
+
427
+ /* A row whose whole box resolves somewhere, or runs a command, is a target
428
+ and the pointer says so. A row that is only read keeps the arrow. */
429
+ [data-ui='list-item']:not([data-disabled]) > :is(a, button)[data-part='content']:not(:disabled) {
430
+ cursor: pointer;
431
+ }
432
+
433
+ /* `inset` takes the row's inline padding away so a list inside a padded box
434
+ lines its titles up with the box. The fill and the rule still run edge to
435
+ edge, which is the half MUI's `disableGutters` leaves out. */
436
+ [data-ui='list-item'][data-inset] > [data-part='content'] {
437
+ padding-inline: 0;
438
+ }
439
+
440
+ /**
441
+ * `start` for a row of one line, and for a row of three the leading visual
442
+ * belongs beside the title rather than in the middle of the block. MUI calls
443
+ * the same decision `alignItems`; Angular Material derives it from counting
444
+ * the lines, which is a count a translated string can change.
445
+ */
446
+ [data-ui='list-item'][data-align='start'] > [data-part='content'] {
447
+ align-items: start;
448
+ }
449
+
450
+ [data-ui='list-item'] > [data-part='content'] > [data-part='start'] {
451
+ align-items: center;
452
+ color: var(--_text);
453
+ display: flex;
454
+ flex: none;
455
+ grid-area: start;
456
+ margin-inline-end: var(--_gap);
457
+ /* The column is the control glyph's, so an icon, an avatar and a checkbox
458
+ each land on the same line however tall they are. */
459
+ min-inline-size: var(--_icon-size);
460
+ }
461
+
462
+ /**
463
+ * A chooser in the leading column (LI2). `checkbox` lifts its own box onto
464
+ * the first line of the text beside it — the same line box this row sets
465
+ * its title in, at the same step — so the column only has to stand at the
466
+ * top of that line and be as tall as it: the box then sits on the title's
467
+ * centre by the chooser's own arithmetic rather than by a second one here.
468
+ * Centred on the block, as an icon is, a one-line row put the box a pixel
469
+ * low and a two-line row put it between the lines.
470
+ *
471
+ * The column is the chooser's own square, fixed across densities as the
472
+ * chooser is (CK1); the gap beside it is the row's control gap, the same
473
+ * distance the chooser puts between its box and its own label.
474
+ */
475
+ [data-ui='list-item'] > [data-part='content'] > [data-part='start']:has(> [data-ui='checkbox']) {
476
+ align-items: start;
477
+ inline-size: var(--_control-size);
478
+ min-block-size: calc(var(--_font-size) * var(--slotted-line-height-md, 1.5));
479
+ }
480
+
481
+ /* A leading visual beside a three-line row sits on the title's line, and the
482
+ line is the box's own, not the block's. */
483
+ [data-ui='list-item'][data-align='start'] > [data-part='content'] > [data-part='start'] {
484
+ min-block-size: calc(var(--_font-size) * var(--slotted-line-height-md, 1.5));
485
+ }
486
+
487
+ /**
488
+ * The text column, and the one box in the row that may shrink. Without
489
+ * `min-inline-size: 0` a flex or grid item refuses to go below its longest
490
+ * word, so a title that is one unbroken token — a URL, a commit hash — pushes
491
+ * the metadata out of the row instead of breaking inside it.
492
+ */
493
+ [data-ui='list-item'] > [data-part='content'] > [data-part='body'] {
494
+ display: flex;
495
+ flex-direction: column;
496
+ grid-area: body;
497
+ min-inline-size: 0;
498
+ row-gap: var(--slotted-list-item-body-gap, var(--slotted-space-1, 0.125rem));
499
+ }
500
+
501
+ [data-ui='list-item'] > [data-part='content'] > [data-part='body'] > [data-part='label'] {
502
+ color: inherit;
503
+ font-size: var(--_font-size);
504
+ font-weight: var(--slotted-list-item-font-weight, var(--slotted-font-weight-regular, 400));
505
+ line-height: var(--slotted-line-height-md, 1.5);
506
+ margin-block: 0;
507
+ min-inline-size: 0;
508
+ /* A single unbroken token breaks inside the row rather than widening it.
509
+ No surveyed implementation does this; MUI's `ListItemText` overflows. */
510
+ overflow-wrap: anywhere;
511
+ /* `plaintext` rather than `isolate`: a row is a run of content, so a title
512
+ in another script takes its own paragraph direction and leaves the row
513
+ around it alone. */
514
+ unicode-bidi: plaintext;
515
+ }
516
+
517
+ [data-ui='list-item'] > [data-part='content'] > [data-part='body'] > [data-part='description'] {
518
+ color: var(--_muted);
519
+ font-size: var(--slotted-list-item-description-font-size, var(--slotted-font-size-xs, 0.75rem));
520
+ line-height: var(--slotted-line-height-sm, 1.45);
521
+ margin-block: 0;
522
+ min-inline-size: 0;
523
+ overflow-wrap: anywhere;
524
+ unicode-bidi: plaintext;
525
+ }
526
+
527
+ /**
528
+ * `truncate` clips the title and the lines under it to one line each. Both,
529
+ * rather than the title alone: a clipped title over a description wrapping to
530
+ * four lines is a row that gave up on one measure and not the other.
531
+ *
532
+ * The content stays in the DOM, which is the briefing's standing decision — a
533
+ * truncated string is still readable to assistive technology and still
534
+ * reachable in full when the row is opened.
535
+ */
536
+ [data-ui='list-item'][data-truncate]
537
+ > [data-part='content']
538
+ > [data-part='body']
539
+ > :is([data-part='label'], [data-part='description']) {
540
+ overflow: hidden;
541
+ overflow-wrap: normal;
542
+ text-overflow: ellipsis;
543
+ white-space: nowrap;
544
+ }
545
+
546
+ /**
547
+ * The metadata that belongs to what the row says — a timestamp, a count, a
548
+ * badge. It is inside `content`, so it is part of the row's own name for a
549
+ * reader and part of its target for a pointer. Anything that acts is
550
+ * `actions` instead.
551
+ */
552
+ [data-ui='list-item'] > [data-part='content'] > [data-part='end'] {
553
+ align-items: center;
554
+ color: var(--_muted);
555
+ display: flex;
556
+ flex: none;
557
+ gap: var(--_gap);
558
+ grid-area: end;
559
+ margin-inline-start: var(--_gap);
560
+ }
561
+
562
+ /**
563
+ * ── Independent targets ────────────────────────────────────────────────
564
+ *
565
+ * A sibling of `content` and never its child: a `button` cannot contain a
566
+ * `button` and an `a` can contain neither, so a row that navigates and also
567
+ * deletes has to put its two controls side by side. Every surveyed
568
+ * implementation lets a consumer nest them instead, and produces HTML no
569
+ * browser can make focusable.
570
+ *
571
+ * The box is above the row's own fill, so a control here is operable on a row
572
+ * whose whole surface resolves somewhere else.
573
+ */
574
+ [data-ui='list-item'] > [data-part='actions'] {
575
+ align-items: center;
576
+ /* The box counts its own inset (LI4). Below the breakpoint it takes the
577
+ whole line and the row's inline inset on both sides, and read as
578
+ content it stood the row's width plus twenty-six pixels: a 266px line in
579
+ a 240px list, in every scheme. */
580
+ box-sizing: border-box;
581
+ display: flex;
582
+ flex: none;
583
+ gap: var(--_gap);
584
+ padding-inline-end: var(--_pad-inline);
585
+ position: relative;
586
+ }
587
+
588
+ [data-ui='list-item'][data-inset] > [data-part='actions'] {
589
+ padding-inline-end: 0;
590
+ }
591
+
592
+ /**
593
+ * The independent targets follow `align` for the same reason the leading
594
+ * visual and the metadata do. Left centred on the block while `end` sat on
595
+ * the title's line, a row of two lines put its badge and its delete control
596
+ * on two different lines — the one thing a trailing pair must not do.
597
+ */
598
+ [data-ui='list-item'][data-align='start'] > [data-part='actions'] {
599
+ align-items: start;
600
+ padding-block-start: var(--_pad-block);
601
+ }
602
+
603
+ /**
604
+ * ── Ruled, framed, separated ───────────────────────────────────────────
605
+ *
606
+ * Before the states, and the order is the point: a separated row states its
607
+ * own resting fill and a selected row replaces it, and the two selectors
608
+ * carry the same weight. Read the other way round the resting surface won
609
+ * and a selected card kept its edge and lost its tint.
610
+ *
611
+ * The line between two rows is drawn by the row below, because the row is the
612
+ * only element that knows whether it is the first of its list. A rule drawn
613
+ * by the set would need a selector reaching into this family, which is what
614
+ * the two sheets do not do.
615
+ *
616
+ * `framed` rules its rows exactly as `ruled` does; what it adds is the box
617
+ * around them, and that box belongs to the set.
618
+ */
619
+ [data-ui='list-item']:is([data-variant='ruled'], [data-variant='framed']):not(:first-child) {
620
+ /* A shadow rather than a border, and the reason is the pointer. A border is
621
+ part of the row's box and outside the box that paints hover, so the top
622
+ pixel of every ruled row answered nothing: the reader saw a one-pixel
623
+ strip where the fill stopped short of the rule. Drawn outward the rule
624
+ takes no layout at all — it lands on the last pixel of the row above,
625
+ which is painted before it — so `content` fills the row and every pixel
626
+ of it is hoverable.
627
+
628
+ The offset is physical because `box-shadow` has no logical form. It costs
629
+ nothing here: the rule runs along the block axis, which is vertical in
630
+ both directions this library supports. */
631
+ --_rule-shadow: 0 calc(var(--_rule-width) * -1) 0 0 var(--_rule);
632
+ }
633
+
634
+ /**
635
+ * A separated row is its own surface. It keeps the frame the set would have
636
+ * drawn once, at row scale, so a list of cards and a list of rows are the
637
+ * same family in two arrangements rather than two families.
638
+ */
639
+ [data-ui='list-item'][data-variant='separated'] {
640
+ --_fill: var(--slotted-list-item-background, var(--slotted-surface-raised, Canvas));
641
+
642
+ border: var(--_rule-width) solid var(--_rule);
643
+ }
644
+
645
+ /* The row's own corners clip the fill its content paints, so a hover wash
646
+ inside a rounded row does not square off its edges. `clip` rather than
647
+ `hidden`: a row is not a scroll container, and `hidden` would make it one. */
648
+ [data-ui='list-item'][data-variant='separated'] {
649
+ overflow: clip;
650
+ }
651
+
652
+ /**
653
+ * ── The states ─────────────────────────────────────────────────────────
654
+ *
655
+ * Hover lays a wash under the box and press goes one step further. Both hold
656
+ * themselves back for `disabled` and for `loading`, which are the two states
657
+ * above them in `STATE_PRECEDENCE`; the library learned that from a defect,
658
+ * and `states.test.mjs` is what keeps it from being relearned the same way.
659
+ *
660
+ * Only a row that acts answers the pointer. A row that is only read has
661
+ * nothing to say about being pointed at, and washing it would promise an
662
+ * interaction that never arrives.
663
+ */
664
+ [data-ui='list-item']:not([data-disabled]):not([data-loading]):has(
665
+ > :is(a, button)[data-part='content']:not(:disabled):hover
666
+ ) {
667
+ --_fill: var(--_hover);
668
+ }
669
+
670
+ [data-ui='list-item']:not([data-disabled]):not([data-loading]):has(
671
+ > :is(a, button)[data-part='content']:not(:disabled):active
672
+ ) {
673
+ --_fill: var(--_active);
674
+ }
675
+
676
+ /**
677
+ * The ring sits inside the row rather than around it. A row is full width, so
678
+ * an outward ring on the first row of a framed list is drawn under the frame
679
+ * and on the last row it is drawn outside the page; a negative offset keeps
680
+ * the whole ring on the row it is about.
681
+ */
682
+ [data-ui='list-item'] > [data-part='content']:focus-visible {
683
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
684
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
685
+ }
686
+
687
+ /**
688
+ * Current is a ground, and the two states are told apart by which ground: a
689
+ * row can be the one the reader is on **and** one of the rows they have
690
+ * chosen, and when it is both the selection is the one that shows, because
691
+ * it is declared after this and the reader already knows where they are.
692
+ *
693
+ * `aria-current` is the one selection-shaped state ARIA 1.2 lets a `listitem`
694
+ * announce for itself, so this is also the only one of the two the row says
695
+ * out loud.
696
+ */
697
+ [data-ui='list-item'][data-current] {
698
+ --_fill: var(--_current-surface);
699
+ /* The edge is an inset shadow rather than a border, so the state changes
700
+ no measurement and moves no text. It is what carries the state in
701
+ `high-contrast`, where every tone surface is the page's own white. */
702
+ --_edge-shadow: inset 0 0 0 var(--_rule-width) var(--_current-edge);
703
+ }
704
+
705
+ /**
706
+ * Selected is a fill, and it is a paint rather than a model: `aria-selected`
707
+ * is not valid on `listitem`, so the row mirrors a selection the page owns
708
+ * and the control the page puts in `start` — a `checkbox` — is what announces
709
+ * it. `card` reached the same conclusion for the same reason.
710
+ */
711
+ [data-ui='list-item'][data-selected] {
712
+ /* A selected row that is hovered has to darken, not lighten. The resting
713
+ fill is already the tone's subtle step, so the two pointer states move
714
+ one step along the same subtle pair rather than staying where an
715
+ unselected row leaves them — otherwise hovering a selected row washed it
716
+ out. */
717
+ --_hover: var(--_selected-hover);
718
+ --_active: var(--_selected-active);
719
+ }
720
+
721
+ [data-ui='list-item'][data-selected] {
722
+ --_fill: var(--_selected-surface);
723
+ --_edge-shadow: inset 0 0 0 var(--_rule-width) var(--_selected-edge);
724
+ }
725
+
726
+ /**
727
+ * The rule of a divided list is drawn by the row below, outward, on the
728
+ * last pixel of the row above — the pixel a selected or current row's inset
729
+ * edge ends on. Painted later, it covered that pixel, and a selected row in
730
+ * a ruled list was a box open at the bottom. So neither the row after one of
731
+ * those nor the row itself draws a rule: the edge is the line on both sides,
732
+ * and a rule drawn above the edge made its top two pixels in `high-contrast`,
733
+ * where both are black.
734
+ */
735
+ [data-ui='list-item']:is([data-selected], [data-current]):not(:first-child),
736
+ [data-ui='list-item']:is([data-selected], [data-current]) + [data-ui='list-item'] {
737
+ --_rule-shadow: 0 0 0 0 transparent;
738
+ }
739
+
740
+ /* A run of selected rows is one box. The second of two draws its sides and
741
+ its bottom, and the bottom edge of the row above is the one line between
742
+ them; both edges drawn made that line two pixels. The offsets are
743
+ physical because `box-shadow` has no logical form, and the two sides are
744
+ the same width, so the mirrored row is the same picture. */
745
+ [data-ui='list-item'][data-selected] + [data-ui='list-item'][data-selected] {
746
+ --_edge-shadow:
747
+ inset 0 calc(var(--_rule-width) * -1) 0 0 var(--_selected-edge),
748
+ inset var(--_rule-width) 0 0 0 var(--_selected-edge),
749
+ inset calc(var(--_rule-width) * -1) 0 0 0 var(--_selected-edge);
750
+ }
751
+
752
+ [data-ui='list-item'][data-selected] > [data-part='content'] {
753
+ color: var(--_selected-text);
754
+ font-weight: var(
755
+ --slotted-list-item-font-weight-selected,
756
+ var(--slotted-font-weight-medium, 500)
757
+ );
758
+ }
759
+
760
+ [data-ui='list-item'][data-current] > [data-part='content'] {
761
+ font-weight: var(
762
+ --slotted-list-item-font-weight-current,
763
+ var(--slotted-font-weight-medium, 500)
764
+ );
765
+ }
766
+
767
+ /**
768
+ * ── Loading ────────────────────────────────────────────────────────────
769
+ *
770
+ * The binding writes `aria-busy` and takes the activation away — the native
771
+ * `disabled` from a button, the `href` from an anchor — so the row cannot run
772
+ * the same command twice. What is left for the sheet is to say so without
773
+ * changing the row's size, which `docs/component-features.md` section 2.5
774
+ * requires: the ink recedes and the pointer changes, and nothing moves.
775
+ *
776
+ * No placeholder is drawn. `skeleton` owns placeholder geometry, and a second
777
+ * answer to that question is what `collapsible` already refused.
778
+ */
779
+ [data-ui='list-item'][data-loading] {
780
+ /* One retarget rather than a rule per part: the title, the description, the
781
+ metadata and the mark all read the same two custom properties, so the
782
+ whole row recedes together and nothing changes size. */
783
+ --_text: var(--_muted);
784
+ }
785
+
786
+ [data-ui='list-item'][data-loading] > [data-part='content'] {
787
+ cursor: progress;
788
+ }
789
+
790
+ /**
791
+ * ── Disabled ───────────────────────────────────────────────────────────
792
+ *
793
+ * The row dims and stops answering the pointer. The attribute that means it
794
+ * is on `content` — `disabled` on a button, `aria-disabled` on an anchor that
795
+ * has also lost its `href` — and never on the `li`: ARIA 1.2 deprecates
796
+ * `aria-disabled` on `listitem`, and no surveyed implementation says so.
797
+ *
798
+ * A control the page put in `actions` is the page's to disable. Only the
799
+ * control can refuse an interaction, which is the rule `card` states for the
800
+ * same shape.
801
+ */
802
+ [data-ui='list-item'][data-disabled] {
803
+ /* Both, stated from one alias rather than one pointing at the other: a row
804
+ that is loading retargets `--_text` at `--_muted`, and a disabled loading
805
+ row would otherwise be a cycle CSS resolves by dropping both. Disabled is
806
+ the stronger state and its rule comes last, so it wins the pair. */
807
+ --_disabled-ink: var(
808
+ --slotted-list-item-color-disabled,
809
+ var(--slotted-disabled-foreground, GrayText)
810
+ );
811
+ --_text: var(--_disabled-ink);
812
+ --_muted: var(--_disabled-ink);
813
+ }
814
+
815
+ [data-ui='list-item'][data-disabled] > [data-part='content'] {
816
+ cursor: default;
817
+ }
818
+
819
+ /**
820
+ * A separated row recedes onto the sunken surface rather than onto the
821
+ * control's disabled ground, which is `card`'s rule for the same shape. The
822
+ * ground is worth naming: in the light scheme it is the same hex as the tone
823
+ * surface a selected row is filled with, so a disabled card and a selected
824
+ * one were one rectangle told apart only by their edge.
825
+ */
826
+ [data-ui='list-item'][data-disabled][data-variant='separated'] {
827
+ --_fill: var(--slotted-surface-sunken, Canvas);
828
+
829
+ border-color: var(--slotted-disabled-border, GrayText);
830
+ }
831
+
832
+ /**
833
+ * ── The narrow row ─────────────────────────────────────────────────────
834
+ *
835
+ * Below 22rem the trailing metadata leaves the row's inline axis and sits
836
+ * under the title, in the title's own column, rather than squeezing it. The
837
+ * leading visual and the independent targets stay where they are: one is the
838
+ * row's identity and the other is its controls, and neither becomes easier to
839
+ * use by moving.
840
+ *
841
+ * The breakpoint is a literal because `@container (max-inline-size: var(--x))`
842
+ * is invalid CSS — `docs/token-foundation.md` rule 4.
843
+ */
844
+ @container slotted-list-item (max-inline-size: 22rem) {
845
+ [data-ui='list-item'] > [data-part='content'] {
846
+ grid-template-areas:
847
+ 'start body'
848
+ 'start end';
849
+ grid-template-columns: auto minmax(0, 1fr);
850
+ }
851
+
852
+ [data-ui='list-item'] > [data-part='content'] > [data-part='end'] {
853
+ justify-content: start;
854
+ margin-block-start: var(--slotted-list-item-body-gap, var(--slotted-space-1, 0.125rem));
855
+ margin-inline-start: 0;
856
+ }
857
+
858
+ /* The leading visual now spans two rows, so it stops being centred on the
859
+ block and returns to the title's line. */
860
+ [data-ui='list-item'] > [data-part='content'] > [data-part='start'] {
861
+ align-self: start;
862
+ min-block-size: calc(var(--_font-size) * var(--slotted-line-height-md, 1.5));
863
+ }
864
+
865
+ /**
866
+ * The independent targets take their own line. On one line with the box
867
+ * they never shrink — a target is a target — so the box shrank instead,
868
+ * and a row whose target carried a word rather than a glyph left its
869
+ * metadata spilling out of a column eighty pixels wide.
870
+ *
871
+ * They stay at the end of the row, where they were, and take the row's own
872
+ * inline inset so they line up with the content above them.
873
+ */
874
+ [data-ui='list-item'] > [data-part='actions'] {
875
+ flex-basis: 100%;
876
+ justify-content: flex-end;
877
+ padding-block-end: var(--_pad-block);
878
+ padding-inline: var(--_pad-inline);
879
+ }
880
+
881
+ [data-ui='list-item'][data-inset] > [data-part='actions'] {
882
+ padding-inline: 0;
883
+ }
884
+
885
+ /**
886
+ * Unless every target is a glyph. Two icon-only controls are a column
887
+ * narrower than the visual at the start of the row, and a line of their
888
+ * own under the words put a row's two smallest things at the end of a
889
+ * line that belonged to nothing — a notification list on a phone read as
890
+ * a list of rows with a stray pair of marks under each. A glyph pair
891
+ * keeps the line it had; a target that carries a word still takes its
892
+ * own, because a word is what does not fit.
893
+ */
894
+ [data-ui='list-item'] > [data-part='actions']:not(:has(> :not([data-shape]))) {
895
+ flex-basis: auto;
896
+ padding-block-end: 0;
897
+ padding-inline: 0 var(--_pad-inline);
898
+ }
899
+
900
+ [data-ui='list-item'][data-inset] > [data-part='actions']:not(:has(> :not([data-shape]))) {
901
+ padding-inline-end: 0;
902
+ }
903
+ }
904
+
905
+ /**
906
+ * ── Forced colours ─────────────────────────────────────────────────────
907
+ *
908
+ * The theme remaps every semantic token once, so the row needs a rule only
909
+ * for what it paints itself. The two grounds and the two rings are all
910
+ * dropped here, so both states have to be said again in the system's own
911
+ * colours.
912
+ */
913
+ @media (forced-colors: active) {
914
+ /**
915
+ * Every tone surface is remapped to `Canvas`, so a selected row and a
916
+ * resting one would be the same rectangle. Selection is the accent state,
917
+ * and the system's own selected pair is what says so.
918
+ */
919
+ [data-ui='list-item'][data-selected] {
920
+ background-color: Highlight;
921
+ }
922
+
923
+ [data-ui='list-item'][data-selected] > [data-part='content'] {
924
+ color: HighlightText;
925
+ }
926
+
927
+ /**
928
+ * The current row keeps its ring as a one-pixel outline in the selection
929
+ * colour, drawn inward so it changes no measurement: the inset shadow that
930
+ * says it elsewhere is decoration the theme cannot reach (R1).
931
+ */
932
+ [data-ui='list-item'][data-current]:not([data-selected]) {
933
+ outline: var(--slotted-border-width-thin, 1px) solid Highlight;
934
+ outline-offset: calc(-1 * var(--slotted-border-width-thin, 1px));
935
+ }
936
+ }
937
+ }