@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,67 @@
1
+ [
2
+ "--slotted-border-subtle",
3
+ "--slotted-border-width-thin",
4
+ "--slotted-calendar-active-background",
5
+ "--slotted-calendar-background",
6
+ "--slotted-calendar-cell-size",
7
+ "--slotted-calendar-color",
8
+ "--slotted-calendar-day-radius",
9
+ "--slotted-calendar-disabled-color",
10
+ "--slotted-calendar-hover-background",
11
+ "--slotted-calendar-in-range-color",
12
+ "--slotted-calendar-invalid-edge-width",
13
+ "--slotted-calendar-invalid-focus-ring-color",
14
+ "--slotted-calendar-outside-color",
15
+ "--slotted-calendar-preview-background",
16
+ "--slotted-calendar-preview-border-color",
17
+ "--slotted-calendar-radius",
18
+ "--slotted-calendar-range-active-background",
19
+ "--slotted-calendar-range-background",
20
+ "--slotted-calendar-range-border-color",
21
+ "--slotted-calendar-range-hover-background",
22
+ "--slotted-calendar-selected-background",
23
+ "--slotted-calendar-selected-color",
24
+ "--slotted-calendar-today-color",
25
+ "--slotted-calendar-trigger-color",
26
+ "--slotted-calendar-unavailable-color",
27
+ "--slotted-calendar-week-number-border-color",
28
+ "--slotted-calendar-week-number-color",
29
+ "--slotted-calendar-weekday-color",
30
+ "--slotted-control-font-family",
31
+ "--slotted-control-font-size-md",
32
+ "--slotted-control-gap-md",
33
+ "--slotted-control-height-md",
34
+ "--slotted-control-icon-size-md",
35
+ "--slotted-control-invalid-edge-width",
36
+ "--slotted-control-radius",
37
+ "--slotted-control-transition-duration",
38
+ "--slotted-control-transition-easing",
39
+ "--slotted-disabled-background",
40
+ "--slotted-disabled-foreground",
41
+ "--slotted-focus-ring-color",
42
+ "--slotted-focus-ring-color-invalid",
43
+ "--slotted-focus-ring-offset",
44
+ "--slotted-focus-ring-width",
45
+ "--slotted-font-size-xs",
46
+ "--slotted-font-weight-medium",
47
+ "--slotted-font-weight-regular",
48
+ "--slotted-font-weight-semibold",
49
+ "--slotted-highlight-tint-opacity",
50
+ "--slotted-radius-full",
51
+ "--slotted-radius-sm",
52
+ "--slotted-space-1",
53
+ "--slotted-space-2",
54
+ "--slotted-space-4",
55
+ "--slotted-space-6",
56
+ "--slotted-target-min",
57
+ "--slotted-text",
58
+ "--slotted-text-muted",
59
+ "--slotted-tone-accent-border",
60
+ "--slotted-tone-accent-on-solid",
61
+ "--slotted-tone-accent-solid",
62
+ "--slotted-tone-accent-subtle-active",
63
+ "--slotted-tone-accent-subtle-hover",
64
+ "--slotted-tone-accent-text",
65
+ "--slotted-tone-secondary-subtle-active",
66
+ "--slotted-tone-secondary-subtle-hover"
67
+ ]
@@ -0,0 +1,602 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * The surface wave 0 named. A card reads `--slotted-surface-raised` instead
4
+ * of painting a neutral of its own.
5
+ *
6
+ * Resting and state values are declared as custom properties, per fill, and
7
+ * one gated rule per state swaps them. That is what lets each fill answer a
8
+ * state in the property that fill is made of — an elevated card raises its
9
+ * shadow where an outlined card darkens its edge — instead of all three
10
+ * sharing one grey. It is Fluent 2's model, and the review found the library
11
+ * had the axis without the answers.
12
+ *
13
+ * The root does not clip. Media rounds the corners it touches and clips
14
+ * itself, so an overlay opened inside a card is not cut off by the card.
15
+ */
16
+ [data-ui='card'] {
17
+ --_card-edge-width: var(--slotted-card-border-width, var(--slotted-border-width-thin, 1px));
18
+ --_card-radius: var(--slotted-card-radius, var(--slotted-radius-lg, 8px));
19
+ --_card-inset-block: var(--slotted-card-padding-block, var(--slotted-space-6, 1rem));
20
+ --_card-inset-inline: var(--slotted-card-padding-inline, var(--slotted-space-6, 1rem));
21
+ /* One step below the inset, never equal to it. At every size the region
22
+ gap used to resolve to exactly the outer inset, so the header, the body
23
+ and the footer sat as far from each other as from the card's own edge and
24
+ nothing grouped. */
25
+ --_card-region-gap: var(--slotted-card-region-gap, var(--slotted-space-3, 0.375rem));
26
+
27
+ /**
28
+ * Resting, state, and current — three names, in that order, and a state
29
+ * value may only ever reference a resting one.
30
+ *
31
+ * Writing `--_card-edge-hover: var(--_card-edge)` to mean "hold the edge
32
+ * still" is a cycle: the hover rule sets `--_card-edge` from
33
+ * `--_card-edge-hover`. CSS resolves a cycle to the guaranteed-invalid
34
+ * value, the `border` shorthand becomes invalid at computed-value time, and
35
+ * the edge does not hold still — it disappears. Every fill that held its
36
+ * edge on hover lost it instead, and so did every selected card.
37
+ */
38
+ --_card-surface-rest: var(--slotted-card-background, var(--slotted-surface-raised, Canvas));
39
+ --_card-edge-rest: var(--slotted-card-border-color, var(--slotted-border-subtle, ButtonBorder));
40
+ --_card-shadow-rest: var(--slotted-card-shadow, none);
41
+
42
+ /* What this fill answers with. Read by the gated state rules below. */
43
+ --_card-surface-hover: var(--_card-surface-rest);
44
+ --_card-edge-hover: var(
45
+ --slotted-card-border-color-hover,
46
+ var(--slotted-border-strong, ButtonBorder)
47
+ );
48
+ --_card-shadow-hover: var(--slotted-card-shadow-hover, var(--slotted-shadow-xs));
49
+ /* Press fills, where hover does not. Hover holding the surface still is
50
+ what keeps it from reading as disabled; a press is momentary and arrives
51
+ with the pointer already down, so the fill is unambiguous — and it is the
52
+ one state all three fills answer the same way, which is what makes a
53
+ press feel like the same gesture across a page. Fluent and Material 3
54
+ both darken the ground on press for every appearance they ship. */
55
+ --_card-surface-active: var(--slotted-tone-secondary-subtle-active);
56
+ --_card-edge-active: var(--_card-edge-hover);
57
+ --_card-shadow-active: none;
58
+
59
+ /* Current. The gated state rules below move these and nothing else. */
60
+ --_card-surface: var(--_card-surface-rest);
61
+ --_card-edge: var(--_card-edge-rest);
62
+ --_card-shadow: var(--_card-shadow-rest);
63
+ /* Disabled recedes from the raised surface. The control's disabled ground
64
+ is a heavier grey than a card can carry: at card area it drew more
65
+ attention than the operable cards around it, which is the same error as
66
+ filling a card with a tone surface calibrated on a badge. */
67
+ --_card-surface-disabled: var(
68
+ --slotted-card-background-disabled,
69
+ var(--slotted-surface-sunken, Canvas)
70
+ );
71
+
72
+ background-color: var(--_card-surface);
73
+ /* The ground a family inside states its own ground from — a table's rows,
74
+ a grid's cells — so they stand on the card and not on the page. */
75
+ --slotted-ground: var(--_card-surface);
76
+ /* The edge is drawn by `::before` over the border box, never in flow, so a
77
+ hairline becoming a selected 2px edge moves no text. A real border would
78
+ have to reserve its own width, and reserving the resting width is what
79
+ makes the jump shift every region by a pixel. Five of the six surveyed
80
+ systems that solve this solve it on a pseudo-element, three of them with
81
+ a comment naming the reason. */
82
+ border: none;
83
+ border-radius: var(--_card-radius);
84
+ box-shadow: var(--_card-shadow);
85
+ box-sizing: border-box;
86
+ color: var(--slotted-card-color, var(--slotted-text, CanvasText));
87
+ display: flex;
88
+ flex-direction: column;
89
+ min-inline-size: 0;
90
+ /* The containing block for the edge and for the stretched hit area. */
91
+ position: relative;
92
+ transition:
93
+ background-color var(--slotted-duration-short) var(--slotted-easing-standard),
94
+ box-shadow var(--slotted-duration-short) var(--slotted-easing-standard);
95
+ }
96
+
97
+ /**
98
+ * The edge, at every state. It is a border rather than a box-shadow so that
99
+ * it survives `forced-colors`, where a shadow is removed outright, and it
100
+ * changes width without moving a pixel of content because it is out of flow.
101
+ */
102
+ [data-ui='card']::before {
103
+ border: var(--_card-edge-width) solid var(--_card-edge);
104
+ border-radius: inherit;
105
+ content: '';
106
+ inset: 0;
107
+ pointer-events: none;
108
+ position: absolute;
109
+ transition: border-color var(--slotted-duration-short) var(--slotted-easing-standard);
110
+ }
111
+
112
+ /* Density is the application's choice and governs control geometry; `size`
113
+ is this card's own inset, so a summary card and a card in a dense list can
114
+ stand on the same page. */
115
+ [data-ui='card'][data-size='sm'] {
116
+ --_card-inset-block: var(--slotted-card-padding-block-sm, var(--slotted-space-5, 0.75rem));
117
+ --_card-inset-inline: var(--slotted-card-padding-inline-sm, var(--slotted-space-5, 0.75rem));
118
+ --_card-region-gap: var(--slotted-card-region-gap-sm, var(--slotted-space-2, 0.25rem));
119
+ }
120
+
121
+ [data-ui='card'][data-size='lg'] {
122
+ --_card-inset-block: var(--slotted-card-padding-block-lg, var(--slotted-space-8, 1.5rem));
123
+ --_card-inset-inline: var(--slotted-card-padding-inline-lg, var(--slotted-space-8, 1.5rem));
124
+ --_card-region-gap: var(--slotted-card-region-gap-lg, var(--slotted-space-4, 0.5rem));
125
+ }
126
+
127
+ /**
128
+ * Three ways to bound a surface, and a page uses more than one: a grid of
129
+ * outlined list cards, a summary that lifts off the page, and a filled block
130
+ * that recedes into it.
131
+ *
132
+ * `elevated` rests at the medium step. The small step under a hairline does
133
+ * not separate on a white page, and `0 1px 3px rgb(0 0 0 / 0.5)` on a
134
+ * near-black one is invisible, so `elevated` and `outline` were one picture
135
+ * in both schemes. It keeps its hairline, because a shadow is removed under
136
+ * forced colours and an edge is not.
137
+ */
138
+ [data-ui='card'][data-fill='elevated'] {
139
+ --_card-shadow-rest: var(--slotted-card-shadow, var(--slotted-shadow-md));
140
+ /* The shadow moves and the edge holds still: elevation is what this fill
141
+ is made of. */
142
+ --_card-edge-hover: var(--_card-edge-rest);
143
+ --_card-shadow-hover: var(--slotted-card-shadow-hover, var(--slotted-shadow-lg));
144
+ --_card-edge-active: var(--_card-edge-rest);
145
+ /* The elevation falls back under the finger rather than to nothing: an
146
+ elevated card that flattens completely reads as removed, not pressed. */
147
+ --_card-shadow-active: var(--slotted-shadow-sm);
148
+ }
149
+
150
+ /**
151
+ * `subtle` is a fill, and it keeps the same hairline the outlined card has.
152
+ *
153
+ * Dropping the edge was tried, on Fluent's rule for the same appearance —
154
+ * "similar to filled-alternative, but no border is applied" — and reverted.
155
+ * Fluent's subtle background differs from its page; the high-contrast scheme
156
+ * here paints `--slotted-surface-sunken` and `--slotted-surface` the same
157
+ * white, so an edgeless subtle card had neither fill nor edge and stopped
158
+ * being a card at all. A hairline that reads as slightly redundant in two
159
+ * schemes fails more loudly than a card that disappears in the third.
160
+ */
161
+ [data-ui='card'][data-fill='subtle'] {
162
+ --_card-surface-rest: var(
163
+ --slotted-card-background-subtle,
164
+ var(--slotted-surface-sunken, Canvas)
165
+ );
166
+ /* The fill is what this variant is made of, so the fill is what moves —
167
+ toward the raised surface, which is a card coming forward rather than a
168
+ card greying out. The edge holds still. */
169
+ --_card-surface-hover: var(--slotted-surface-raised, Canvas);
170
+ --_card-edge-hover: var(--_card-edge-rest);
171
+ --_card-shadow-hover: none;
172
+ --_card-surface-active: var(--slotted-tone-secondary-subtle-active);
173
+ --_card-edge-active: var(--_card-edge-rest);
174
+ /* This fill already rests on the sunken surface, so receding onto it would
175
+ leave a disabled card and an operable one the same picture. It is the one
176
+ fill that takes the control's disabled ground. */
177
+ --_card-surface-disabled: var(
178
+ --slotted-card-background-disabled,
179
+ var(--slotted-disabled-background, Canvas)
180
+ );
181
+ }
182
+
183
+ /**
184
+ * A card is interactive when it holds a destination or a choice. Every state
185
+ * below was previously gated on `[data-part='link']` alone, so the chooser —
186
+ * a `<label>` around a native input, which is the whole-card selection this
187
+ * family documents — answered no hover, no press and no ring.
188
+ *
189
+ * The two guards are the state precedence: a disabled card does not answer
190
+ * the pointer, and neither does a card whose only control is disabled.
191
+ */
192
+ [data-ui='card']:not([data-disabled]):has(
193
+ :is([data-part='link'], [data-part='selection'] :is(input, button)):not(:disabled)
194
+ ):hover {
195
+ --_card-surface: var(--_card-surface-hover);
196
+ --_card-edge: var(--_card-edge-hover);
197
+ --_card-shadow: var(--_card-shadow-hover);
198
+ }
199
+
200
+ /**
201
+ * A card that resolves somewhere or holds a choice is a target, and the
202
+ * pointer says so over the whole surface. The link's own hit area already
203
+ * carried the pointer, so only the chooser was left with the default arrow
204
+ * over a surface that is entirely clickable.
205
+ *
206
+ * Descendants inherit it rather than reverting to a text caret: the surface
207
+ * is one target, and a caret over it offers a selection the `<label>` will
208
+ * not make. Anything a consumer raises above the hit area — `actions`, the
209
+ * `footer` — sets its own cursor, because it is its own target.
210
+ */
211
+ [data-ui='card']:not([data-disabled]):has(
212
+ :is([data-part='link'], [data-part='selection'] :is(input, button)):not(:disabled)
213
+ ) {
214
+ cursor: pointer;
215
+ }
216
+
217
+ [data-ui='card']:not([data-disabled]):has(
218
+ [data-part='selection'] :is(input, button):not(:disabled)
219
+ )
220
+ :where(*) {
221
+ cursor: inherit;
222
+ }
223
+
224
+ [data-ui='card']:not([data-disabled]):has(
225
+ :is([data-part='link'], [data-part='selection'] :is(input, button)):not(:disabled)
226
+ ):active {
227
+ --_card-surface: var(--_card-surface-active);
228
+ --_card-edge: var(--_card-edge-active);
229
+ --_card-shadow: var(--_card-shadow-active);
230
+ }
231
+
232
+ /**
233
+ * Vertical rhythm is carried by each region's own block padding rather than
234
+ * by a gap on the root, which is what lets an absent region collapse to
235
+ * nothing instead of leaving its gap behind — the case a three-region layout
236
+ * usually gets wrong.
237
+ */
238
+ [data-ui='card'] > :is([data-part='header'], [data-part='body'], [data-part='footer']) {
239
+ min-inline-size: 0;
240
+ padding-block: var(--_card-region-gap);
241
+ padding-inline: var(--_card-inset-inline);
242
+ }
243
+
244
+ /* A region at either end absorbs the padding its missing neighbour would
245
+ have carried, so a card of only a body is not visibly tighter than a full
246
+ one. Media bleeds, so the region next to it counts as an end. */
247
+ [data-ui='card']
248
+ > :is([data-part='header'], [data-part='body'], [data-part='footer']):first-child,
249
+ [data-ui='card']
250
+ > [data-part='media']
251
+ + :is([data-part='header'], [data-part='body'], [data-part='footer']) {
252
+ padding-block-start: var(--_card-inset-block);
253
+ }
254
+
255
+ [data-ui='card'] > :is([data-part='header'], [data-part='body'], [data-part='footer']):last-child,
256
+ [data-ui='card']
257
+ > :is([data-part='header'], [data-part='body'], [data-part='footer']):has(
258
+ + [data-part='media']
259
+ ) {
260
+ padding-block-end: var(--_card-inset-block);
261
+ }
262
+
263
+ /**
264
+ * The header stacks a title and the line that supports it in the first
265
+ * column, and keeps a second column for whatever belongs in the corner: a
266
+ * badge, a status, a menu button. Both are Typography and both are the
267
+ * consumer's, so the header decides only the two distances and the corner.
268
+ */
269
+ [data-ui='card'] > [data-part='header'] {
270
+ align-items: start;
271
+ column-gap: var(--slotted-card-header-column-gap, var(--slotted-space-5, 0.75rem));
272
+ display: grid;
273
+ grid-template-columns: minmax(0, 1fr) auto;
274
+ row-gap: var(--slotted-card-header-gap, var(--slotted-space-2, 0.25rem));
275
+ }
276
+
277
+ [data-ui='card'] > [data-part='header'] > * {
278
+ grid-column: 1;
279
+ min-inline-size: 0;
280
+ }
281
+
282
+ /* The corner. It stays on the first row, so a title that wraps to two lines
283
+ does not drag the badge down with it, and it takes the last column whether
284
+ the header has two or three. */
285
+ [data-ui='card'] > [data-part='header'] > [data-part='actions'] {
286
+ align-items: center;
287
+ display: flex;
288
+ flex-wrap: wrap;
289
+ /* Centred on the title's own row rather than on the row's start edge: a
290
+ badge aligned to the line box sits optically high against cap height. */
291
+ align-self: center;
292
+ gap: var(--slotted-card-actions-gap, var(--slotted-space-3, 0.375rem));
293
+ grid-column: -2 / -1;
294
+ grid-row: 1;
295
+ /* Above the stretched hit area, like the footer: a control in the corner
296
+ of a card that resolves elsewhere still has to be operable. */
297
+ position: relative;
298
+ }
299
+
300
+ /**
301
+ * The place a choice card keeps for its control. It is the slot and not the
302
+ * glyph: `checkbox radio-group` owns where a check and a dot are drawn, and a
303
+ * card drawing its own would be the second answer to that question.
304
+ *
305
+ * It leads rather than trails, because `actions` already owns the corner.
306
+ * Spectrum and Mantine put selection at the inline start for the same reason;
307
+ * Carbon, Fluent and Cloudscape trail only because none of them keeps a
308
+ * corner in the header.
309
+ */
310
+ [data-ui='card'] > [data-part='header']:has([data-part='selection']) {
311
+ grid-template-columns: auto minmax(0, 1fr) auto;
312
+ }
313
+
314
+ [data-ui='card'] > [data-part='header']:has([data-part='selection']) > * {
315
+ grid-column: 2;
316
+ }
317
+
318
+ [data-ui='card'] > [data-part='header'] > [data-part='selection'] {
319
+ /**
320
+ * On the title's first line, not in the middle of its block (C1, the rule
321
+ * R5 gives every mark beside a title): the slot is one line of the title's
322
+ * step tall and centres the control inside it, so a title that wraps to
323
+ * two lines keeps its control beside the first. The step is the large one,
324
+ * which is the title a card header holds; a header set at another step
325
+ * retunes the line with the family token.
326
+ */
327
+ --_selection-line: var(
328
+ --slotted-card-selection-line,
329
+ calc(var(--slotted-font-size-lg, 1rem) * var(--slotted-line-height-lg, 1.5))
330
+ );
331
+
332
+ align-items: center;
333
+ align-self: start;
334
+ block-size: var(--_selection-line);
335
+ display: flex;
336
+ grid-column: 1;
337
+ grid-row: 1;
338
+ /* Above the stretched hit area: a card holding a choice is not a
339
+ destination, but a consumer may still raise something over the surface. */
340
+ position: relative;
341
+ }
342
+
343
+ /**
344
+ * The control is the selection scale's square, the one `checkbox` and
345
+ * `radio-group` draw in, and it does not follow density: the control glyph
346
+ * scale it read before fell to thirteen pixels at `dense`, and a chooser
347
+ * smaller than the check beside it in the same form was two answers.
348
+ *
349
+ * The card sizes and places the control and paints none of it. Tinting it
350
+ * with `accent-color` was tried and reverted: the accent is a dark blue, so
351
+ * on a dark card the platform had to draw its mark and ring in white against
352
+ * it, and the checked radio came out ringed in white — brighter than the
353
+ * card's own selected edge. Left alone, the control follows `color-scheme`
354
+ * and reads correctly in both. It is the same boundary the part is drawn on:
355
+ * `checkbox radio-group` owns what the control looks like, and a brand-tinted
356
+ * check arrives with the family that owns the geometry.
357
+ *
358
+ * The control's box is below the WCAG minimum target on its own. The target
359
+ * is the card: the surface is the `<label>`, so the hit area is the whole
360
+ * card, which is the correction the token foundation asks a family to make.
361
+ */
362
+ [data-ui='card'] > [data-part='header'] > [data-part='selection'] > :is(input, button) {
363
+ block-size: var(--slotted-card-selection-size, var(--slotted-selection-size-md, 16px));
364
+ inline-size: var(--slotted-card-selection-size, var(--slotted-selection-size-md, 16px));
365
+ margin: 0;
366
+ }
367
+
368
+ /* Cards stretched to a common height in a grid keep their footers on one
369
+ line, because the body takes the leftover. */
370
+ [data-ui='card'] > [data-part='body'] {
371
+ flex: 1 1 auto;
372
+ min-block-size: 0;
373
+ }
374
+
375
+ [data-ui='card'] > [data-part='footer'] {
376
+ align-items: center;
377
+ display: flex;
378
+ flex-wrap: wrap;
379
+ gap: var(--slotted-card-footer-gap, var(--slotted-space-4, 0.5rem));
380
+ /* Above the stretched hit area: a secondary action belongs in the footer
381
+ and has to stay operable while the card resolves to somewhere else. */
382
+ position: relative;
383
+ }
384
+
385
+ /**
386
+ * A rule between two regions is a Divider written between them, not an axis
387
+ * of this family. It runs edge to edge because the region padding is the
388
+ * region's own, and `inset` on the rule lines it up with the text instead.
389
+ * The regions on either side keep the distance they already had.
390
+ */
391
+ [data-ui='card'] > [data-ui='divider'] {
392
+ flex-shrink: 0;
393
+ }
394
+
395
+ [data-ui='card'][data-orientation='horizontal'] > [data-ui='divider'] {
396
+ grid-column: 2;
397
+ }
398
+
399
+ [data-ui='card'] > [data-part='media'] {
400
+ display: block;
401
+ min-inline-size: 0;
402
+ overflow: hidden;
403
+ }
404
+
405
+ [data-ui='card'] > [data-part='media'] > :is(img, video, canvas, picture, svg) {
406
+ block-size: 100%;
407
+ display: block;
408
+ inline-size: 100%;
409
+ object-fit: cover;
410
+ }
411
+
412
+ /* The media rounds the corners it touches and no others. The radius is the
413
+ card's own, less the edge it sits inside. */
414
+ [data-ui='card'] > [data-part='media']:first-child {
415
+ border-start-end-radius: var(--_card-radius);
416
+ border-start-start-radius: var(--_card-radius);
417
+ }
418
+
419
+ [data-ui='card'] > [data-part='media']:last-child {
420
+ border-end-end-radius: var(--_card-radius);
421
+ border-end-start-radius: var(--_card-radius);
422
+ }
423
+
424
+ /**
425
+ * A horizontal card is a grid of two columns and four rows: the media takes
426
+ * the first column across every row, and the three regions take rows one to
427
+ * three of the second. The fourth row is empty until the card reflows, and
428
+ * an empty row costs nothing because the rhythm is padding, not a gap.
429
+ */
430
+ [data-ui='card'][data-orientation='horizontal'] {
431
+ container-name: slotted-card;
432
+ container-type: inline-size;
433
+ display: grid;
434
+ /* A card that reflows on its own inline size cannot also be sized by its
435
+ content: `container-type` removes the inline contribution, so a
436
+ horizontal card in a flex row would collapse to one word per line. It
437
+ fills the inline size it is given instead, and shrinks with its row. */
438
+ inline-size: 100%;
439
+ /* A ratio rather than a length: a card decides how much of itself the
440
+ media may take, and a theme that wants a fixed strip sets the token to
441
+ one. Neither is a scale the family invents. */
442
+ grid-template-columns: var(--slotted-card-media-size, minmax(0, 1fr)) minmax(0, 2fr);
443
+ grid-template-rows: repeat(4, auto);
444
+ }
445
+
446
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='media'] {
447
+ border-end-end-radius: 0;
448
+ border-end-start-radius: var(--_card-radius);
449
+ border-start-end-radius: 0;
450
+ border-start-start-radius: var(--_card-radius);
451
+ grid-area: 1 / 1 / -1 / 2;
452
+ }
453
+
454
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='header'] {
455
+ grid-area: 1 / 2;
456
+ }
457
+
458
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='body'] {
459
+ grid-area: 2 / 2;
460
+ }
461
+
462
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='footer'] {
463
+ grid-area: 3 / 2;
464
+ }
465
+
466
+ /**
467
+ * The card answers its own inline size, not the viewport's. Below the
468
+ * breakpoint every part spans both columns, which collapses the media
469
+ * column and returns the media to the top of a single column. The value is
470
+ * a literal because CSS forbids a custom property in a container condition.
471
+ */
472
+ @container slotted-card (max-inline-size: 24rem) {
473
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='media'] {
474
+ border-end-start-radius: 0;
475
+ border-start-end-radius: var(--_card-radius);
476
+ grid-area: 1 / 1 / 2 / -1;
477
+ }
478
+
479
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='header'] {
480
+ grid-area: 2 / 1 / 3 / -1;
481
+ }
482
+
483
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='body'] {
484
+ grid-area: 3 / 1 / 4 / -1;
485
+ }
486
+
487
+ [data-ui='card'][data-orientation='horizontal'] > [data-part='footer'] {
488
+ grid-area: 4 / 1 / 5 / -1;
489
+ }
490
+ }
491
+
492
+ /**
493
+ * A card that resolves to one destination stretches that anchor's hit area
494
+ * across the whole surface. The element stays an `<a href>`, so the name,
495
+ * Enter, the context menu and middle-click are the platform's, and the name
496
+ * is the title rather than every string in the card.
497
+ */
498
+ [data-ui='card'] [data-part='link']::after {
499
+ content: '';
500
+ inset: 0;
501
+ position: absolute;
502
+ }
503
+
504
+ /**
505
+ * Focus keeps the accent and stays outside the card; selection takes the
506
+ * inverse surface and sits on the edge. In this theme
507
+ * `--slotted-focus-ring-color` and `--slotted-tone-accent-border` resolve to
508
+ * the same hex, so a selected card and a focused card were the same image.
509
+ * They now differ in colour, in width and in position.
510
+ */
511
+ [data-ui='card']:has(
512
+ :is([data-part='link'], [data-part='selection'] :is(input, button)):focus-visible
513
+ ) {
514
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
515
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
516
+ }
517
+
518
+ /**
519
+ * Written inside `:has()` on purpose: a browser without it drops the whole
520
+ * rule and keeps the native outline, rather than removing the only ring.
521
+ *
522
+ * The control's own ring goes for the same reason the link's does. The card
523
+ * is the label and the click target, so the card is what the ring is about;
524
+ * drawing both left a ring around a 16px radio inside a ring around the whole
525
+ * surface. Carbon and Fluent avoid the pair by hiding the control outright,
526
+ * which is not open here — the control is meant to be seen.
527
+ */
528
+ [data-ui='card']:has([data-part='link']) [data-part='link']:focus-visible,
529
+ [data-ui='card']:has([data-part='selection'])
530
+ [data-part='selection']
531
+ :is(input, button):focus-visible {
532
+ outline: none;
533
+ }
534
+
535
+ /**
536
+ * Selection is the accent surface under a one-pixel accent edge (C1, by R1:
537
+ * `selected` is the `accent-subtle` ground, never a rail or a heavier
538
+ * stroke), the same pair `list-item` paints on a selected row, so a chosen
539
+ * card and a chosen row in one page are one state. The edge keeps the
540
+ * resting width; the ground is what says chosen.
541
+ *
542
+ * The `high-contrast` scheme paints the accent surface white and the accent
543
+ * edge black, like every other border, so there the card itself says
544
+ * nothing — and does not have to: the control inside is checked, and the
545
+ * check is what carries the state, as it does for assistive technology. The
546
+ * inverse-surface edge this replaced survived that scheme by being thicker,
547
+ * and was the rail R1 removes.
548
+ *
549
+ * A selected card still answers the pointer: the ground steps to the
550
+ * accent's hover and active tints, as a selected row's does, and the edge
551
+ * holds, because it is already saying the stronger thing.
552
+ */
553
+ [data-ui='card'][data-selected] {
554
+ --_card-surface-rest: var(
555
+ --slotted-card-background-selected,
556
+ var(--slotted-tone-accent-surface, Canvas)
557
+ );
558
+ --_card-surface-hover: var(
559
+ --slotted-card-background-selected-hover,
560
+ var(--slotted-tone-accent-subtle-hover, Canvas)
561
+ );
562
+ --_card-surface-active: var(
563
+ --slotted-card-background-selected-active,
564
+ var(--slotted-tone-accent-subtle-active, Canvas)
565
+ );
566
+ --_card-edge-rest: var(
567
+ --slotted-card-edge-color-selected,
568
+ var(--slotted-tone-accent-border, CanvasText)
569
+ );
570
+ --_card-edge: var(--_card-edge-rest);
571
+ --_card-edge-hover: var(--_card-edge-rest);
572
+ --_card-edge-active: var(--_card-edge-rest);
573
+ --_card-edge-width: var(
574
+ --slotted-card-edge-width-selected,
575
+ var(--slotted-border-width-thin, 1px)
576
+ );
577
+ }
578
+
579
+ /**
580
+ * Disabled is answered per fill like every other state: the two that rest on
581
+ * the raised surface recede onto the sunken one, and the fill already resting
582
+ * there takes the control's disabled ground instead. The edge and the text
583
+ * mute in every case, and the elevation drops to nothing — a card that cannot
584
+ * be used does not float above the page.
585
+ */
586
+ [data-ui='card'][data-disabled] {
587
+ --_card-surface: var(--_card-surface-disabled);
588
+ --_card-edge: var(
589
+ --slotted-card-border-color-disabled,
590
+ var(--slotted-disabled-border, GrayText)
591
+ );
592
+ --_card-shadow: none;
593
+
594
+ color: var(--slotted-card-color-disabled, var(--slotted-disabled-foreground, GrayText));
595
+ }
596
+
597
+ /* The card withdraws the hit area it added. Disabling the control itself is
598
+ the consumer's, because only the control can refuse an interaction. */
599
+ [data-ui='card'][data-disabled] [data-part='link']::after {
600
+ display: none;
601
+ }
602
+ }