@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,1011 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── One choice, and the box its mark is drawn in ───────────────────────
4
+ *
5
+ * The element carrying `data-ui` is a `div` with no role. The `input` inside
6
+ * it is the control: it holds the value, the name, the identifier and every
7
+ * native attribute, and it draws the box — the border, the ground and the
8
+ * focus ring — with `appearance: none`.
9
+ *
10
+ * The mark is a sibling of the control and not a pseudo-element of it,
11
+ * because Firefox renders neither `::before` nor `::after` on an `input`. It
12
+ * is the `Glyph` element the binding renders inside that sibling, which
13
+ * inherits its ink rather than baking in a colour that answers one scheme
14
+ * out of three.
15
+ *
16
+ * Painting reads `:checked` and `:indeterminate` rather than the mirrored
17
+ * attributes, so the picture cannot disagree with the element: an
18
+ * uncontrolled control that the browser has already toggled is drawn toggled
19
+ * whether or not anything re-rendered. `data-checked` and
20
+ * `data-indeterminate` are still written, because they are what a consumer
21
+ * styles against.
22
+ */
23
+ [data-ui='checkbox'][data-part='root'] {
24
+ /**
25
+ * The box is the selection scale's square: 16px at `md`, the same square
26
+ * Radix, Carbon and Primer all draw, and the theme states it once. It does
27
+ * not follow density — see the size rules below.
28
+ */
29
+ --_size: var(--slotted-checkbox-size, var(--slotted-selection-size-md, 16px));
30
+ --_font-size: var(--slotted-control-font-size-md, 13px);
31
+ --_gap: var(--slotted-control-label-gap-md, 12px);
32
+ --_line-height: var(--slotted-checkbox-line-height, var(--slotted-line-height-md, 1.5));
33
+ /**
34
+ * The mark inside its box: the clearance between the edge of the square
35
+ * and the edge of the glyph's own box, the same two pixels on all four
36
+ * sides at every step, so the mark keeps one distance from the edge rather
37
+ * than one ratio (CK6).
38
+ *
39
+ * It is a whole number of pixels and not a fraction of the box, because a
40
+ * fraction is what separated the two centres. Fifteen hundredths of a 16px
41
+ * box is an 11.19px mark offset by 2.39px, and a browser paints a box on
42
+ * the pixel grid: the mark was rasterised at 11px from 27px while the
43
+ * square stood at 25px, so the ink landed half a pixel to the start side,
44
+ * and moved when the row's line height moved the whole item onto another
45
+ * subpixel. The same `md` control drew a mark of 8.70 × 6.40px in one
46
+ * density and 10.20 × 7.50px in another. A whole clearance subtracted from
47
+ * a whole square is a whole mark whose offset is a whole number, so
48
+ * whichever way the pair is snapped, both are snapped together.
49
+ */
50
+ --_mark-inset: var(--slotted-checkbox-mark-inset, 2px);
51
+ --_mark-size: calc(var(--_size) - 2 * var(--_mark-inset));
52
+ /**
53
+ * The air the halo keeps around what it holds: half the gap between the
54
+ * box and the label, on every side of the box and past the end of the
55
+ * text. At `md` in `comfortable` it is 4px, and a one-line halo is 24px
56
+ * tall — the WCAG target it lights.
57
+ */
58
+ --_halo-spread: calc(var(--_gap) / 2);
59
+ /**
60
+ * The target: the WCAG 2.2 minimum, which the box is under and the row no
61
+ * longer pads itself out to. It is the halo's own box (below), so a
62
+ * one-line row is exactly its line and the twenty-four pixels are hit
63
+ * around the box rather than laid out beside it. The label used to carry
64
+ * them as a minimum plus slack, and with `box-sizing: content-box` the two
65
+ * added up: the row stood 28.5px tall at `md` and 30px at `dense` for one
66
+ * line of text.
67
+ */
68
+ --_target: var(--slotted-target-min, 24px);
69
+ /**
70
+ * The distance from the top of the label's box to the top of the square, so
71
+ * the box sits on the first line of a label that wraps to three rather than
72
+ * in the middle of the block. Stated rather than centred: `align-items:
73
+ * center` on a wrapped label is what puts a checkbox beside the second line,
74
+ * and `align-content: center` stops answering the moment the label wraps.
75
+ */
76
+ --_lift: calc((var(--_font-size) * var(--_line-height) - var(--_size)) / 2);
77
+ /**
78
+ * The distance from the label's text to the description under it: the step
79
+ * `field` puts between its own rows, in both variants.
80
+ */
81
+ --_description-gap: var(--slotted-space-3, 0.375rem);
82
+
83
+ /* The rows are as tall as they read. Stretched, the description drifted
84
+ away from the label it belongs to; the surplus is the container's. */
85
+ align-content: start;
86
+ color: var(--slotted-checkbox-color, var(--slotted-text, CanvasText));
87
+ display: grid;
88
+ font-family: var(--slotted-control-font-family, system-ui, sans-serif);
89
+ font-size: var(--_font-size);
90
+ grid-template-columns: auto minmax(0, 1fr);
91
+ /**
92
+ * The halo is drawn behind the box at a negative stack level, and the
93
+ * stacking context it is negative inside of is this one: without it the
94
+ * halo would sink behind whatever surface the checkbox stands on — a
95
+ * list row's wash, a card — and appear on none of them.
96
+ */
97
+ isolation: isolate;
98
+ line-height: var(--_line-height);
99
+ /* The containing block for the halo. */
100
+ position: relative;
101
+ }
102
+
103
+ /**
104
+ * The direction the halo and the target are placed in. A positioned box's
105
+ * flow-relative insets follow its own writing mode, and the label's can
106
+ * differ from the root's — a label in Arabic inside a form in Portuguese —
107
+ * so the two pseudo-elements take the root's direction rather than the
108
+ * label's, or they land on the far side of the row.
109
+ */
110
+ [data-ui='checkbox'][data-part='root'] {
111
+ --_direction: ltr;
112
+ }
113
+
114
+ [data-ui='checkbox'][data-part='root']:dir(rtl) {
115
+ --_direction: rtl;
116
+ }
117
+
118
+ /* ── Size ─────────────────────────────────────────────────────────────
119
+ *
120
+ * Three steps. The box reads the selection scale, which the theme states
121
+ * once and density does not move: the control glyph scale it read before
122
+ * fell to twelve pixels at `dense`, below the size a mark can be drawn in,
123
+ * and a box that shrinks with the text around it is a target that shrinks
124
+ * with it. The text, the gap and the tile's inset still follow density.
125
+ */
126
+
127
+ [data-ui='checkbox'][data-part='root'][data-size='sm'] {
128
+ --_size: var(--slotted-checkbox-size, var(--slotted-selection-size-sm, 14px));
129
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
130
+ --_gap: var(--slotted-control-label-gap-sm, 8px);
131
+ --_tile-inset: var(
132
+ --slotted-checkbox-tile-inset,
133
+ var(--slotted-control-padding-inline-sm, 10px)
134
+ );
135
+ }
136
+
137
+ [data-ui='checkbox'][data-part='root'][data-size='md'] {
138
+ --_size: var(--slotted-checkbox-size, var(--slotted-selection-size-md, 16px));
139
+ --_font-size: var(--slotted-control-font-size-md, 13px);
140
+ --_gap: var(--slotted-control-label-gap-md, 12px);
141
+ --_tile-inset: var(
142
+ --slotted-checkbox-tile-inset,
143
+ var(--slotted-control-padding-inline-md, 13px)
144
+ );
145
+ }
146
+
147
+ [data-ui='checkbox'][data-part='root'][data-size='lg'] {
148
+ --_size: var(--slotted-checkbox-size, var(--slotted-selection-size-lg, 18px));
149
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
150
+ --_gap: var(--slotted-control-label-gap-lg, 12px);
151
+ --_tile-inset: var(
152
+ --slotted-checkbox-tile-inset,
153
+ var(--slotted-control-padding-inline-lg, 16px)
154
+ );
155
+ }
156
+
157
+ /* ── The box ──────────────────────────────────────────────────────────── */
158
+
159
+ [data-ui='checkbox'] [data-part='control'] {
160
+ appearance: none;
161
+ background-color: var(--slotted-checkbox-background, var(--slotted-surface, Canvas));
162
+ /**
163
+ * The medium step of the border scale in the strong border colour. A
164
+ * sixteen-pixel box is edged too lightly at one pixel and too heavily at
165
+ * two, and the control border colour that separates a field from its
166
+ * ground is a weaker grey than a box this small can carry.
167
+ */
168
+ border: var(--slotted-checkbox-border-width, var(--slotted-border-width-medium, 1.5px)) solid
169
+ var(--slotted-checkbox-border-color, var(--slotted-border-strong, ButtonBorder));
170
+ /* The smallest radius: a rounder corner on a sixteen-pixel square reads
171
+ as a circle's cousin, and the circle is the radio's. */
172
+ border-radius: var(--slotted-checkbox-radius, var(--slotted-radius-xs, 2px));
173
+ block-size: var(--_size);
174
+ box-sizing: border-box;
175
+ cursor: pointer;
176
+ grid-area: 1 / 1;
177
+ inline-size: var(--_size);
178
+ margin: 0;
179
+ margin-block-start: var(--_lift);
180
+ /* The same air under the box, so a box with no text beside it is still a
181
+ line tall with the square in its middle. With the lift above and nothing
182
+ below it stood 17.75px tall against its floor, and beside a line of text
183
+ it sat 0.9px low. Beside a label the line is that tall already. */
184
+ margin-block-end: var(--_lift);
185
+ transition:
186
+ background-color var(--slotted-control-transition-duration)
187
+ var(--slotted-control-transition-easing),
188
+ border-color var(--slotted-control-transition-duration)
189
+ var(--slotted-control-transition-easing);
190
+ }
191
+
192
+ /* ── The mark ─────────────────────────────────────────────────────────
193
+ *
194
+ * One element for two glyphs. The binding draws the check, or the dash while
195
+ * the control is indeterminate; this sheet only reveals whichever it drew,
196
+ * on `:checked` or `:indeterminate`.
197
+ *
198
+ * The element is the glyph's own box: the square less its clearance on each
199
+ * side, set in the same grid cell as the box, so the two are concentric by
200
+ * construction and `getBoundingClientRect()` on the mark is the mark. The
201
+ * `icon` box fills it, which is what keeps the artwork one distance from the
202
+ * edge at every step.
203
+ */
204
+
205
+ [data-ui='checkbox'] [data-part='indicator'] {
206
+ --slotted-icon-size: var(--_mark-size);
207
+
208
+ block-size: var(--_mark-size);
209
+ /**
210
+ * The mark arrives along its own stroke, not out of its middle (CK6). Both
211
+ * glyphs are drawn from the start side to the end side — the check from
212
+ * the short arm through the corner to the long one, the dash from one cap
213
+ * to the other — so the reveal is a clip that opens on the inline axis and
214
+ * the ink appears where the pen would have put it.
215
+ *
216
+ * It was `scale: 0.6` → `1` on the whole element, which grows the artwork
217
+ * out of the box's geometric centre: the two ends arrive at once, the
218
+ * stroke thickens as it goes, and every subpixel error in the mark's
219
+ * placement travels across the box while it runs. A clip moves no edge and
220
+ * changes no size, so no frame of it can put ink outside the square.
221
+ *
222
+ * The inset is physical rather than flow-relative on purpose: a check is
223
+ * not mirrored in a right-to-left row, so neither is the hand that draws
224
+ * it.
225
+ */
226
+ clip-path: inset(0% 100% 0% 0%);
227
+ color: var(--slotted-checkbox-mark-color, var(--slotted-tone-accent-on-solid));
228
+ /* A flex box, so the icon sits in it as a box and not on a line of text. */
229
+ display: flex;
230
+ grid-area: 1 / 1;
231
+ inline-size: var(--_mark-size);
232
+ margin-block-start: calc(var(--_lift) + var(--_mark-inset));
233
+ margin-inline-start: var(--_mark-inset);
234
+ opacity: 0;
235
+ /** Nothing here answers the pointer; the control underneath does. */
236
+ pointer-events: none;
237
+ transition:
238
+ clip-path var(--slotted-duration-medium) var(--slotted-control-transition-easing),
239
+ opacity var(--slotted-duration-medium) var(--slotted-control-transition-easing);
240
+ }
241
+
242
+ [data-ui='checkbox'] [data-part='control']:checked + [data-part='indicator'],
243
+ [data-ui='checkbox'] [data-part='control']:indeterminate + [data-part='indicator'] {
244
+ clip-path: inset(0% 0% 0% 0%);
245
+ opacity: 1;
246
+ }
247
+
248
+ /**
249
+ * Reduced motion keeps the fade and drops the hand (R7). The clip is removed
250
+ * rather than shortened, so the mark is whole in every frame it is painted
251
+ * in and only its opacity moves.
252
+ */
253
+ @media (prefers-reduced-motion: reduce) {
254
+ [data-ui='checkbox'] [data-part='indicator'],
255
+ [data-ui='checkbox'] [data-part='control']:checked + [data-part='indicator'],
256
+ [data-ui='checkbox'] [data-part='control']:indeterminate + [data-part='indicator'] {
257
+ clip-path: none;
258
+ transition: opacity var(--slotted-duration-medium) var(--slotted-control-transition-easing);
259
+ }
260
+ }
261
+
262
+ /* ── The label, and the target ────────────────────────────────────────
263
+ *
264
+ * The label is the control's own element and it names it through `for`, so a
265
+ * description below it can be read without toggling anything. Its box is its
266
+ * line: the target the square is too small to be is the label's own
267
+ * pseudo-element, and the row is not padded out to it.
268
+ *
269
+ * It hugs its text rather than stretching across the column, so the empty
270
+ * row beside the words does not toggle the box, and it is the containing
271
+ * block of the target and the halo, which stand on the box outside it.
272
+ */
273
+
274
+ [data-ui='checkbox'] [data-part='label'] {
275
+ cursor: pointer;
276
+ grid-column: 2;
277
+ grid-row: 1;
278
+ /** A label that is one unbroken token — an id, a hash, a URL — breaks. */
279
+ overflow-wrap: anywhere;
280
+ }
281
+
282
+ /**
283
+ * The gap between the control and its text is the grid's, not the label's
284
+ * padding: a padding is laid out in the label's own direction, and a label
285
+ * in Arabic inside a row in Portuguese put it on the far side, against
286
+ * nothing, with the text against the control.
287
+ */
288
+ [data-ui='checkbox'][data-part='root']:has(> [data-part='label']) {
289
+ column-gap: var(--_gap);
290
+ }
291
+
292
+ [data-ui='checkbox'][data-part='root']:not([data-variant='tile']) [data-part='label'] {
293
+ justify-self: start;
294
+ position: relative;
295
+ }
296
+
297
+ /**
298
+ * The halo, and the target. Both are the label's pseudo-elements and not
299
+ * the indicator's, because the label is the element that toggles the
300
+ * control, and both sit at a negative stack level inside the root, so the
301
+ * box and the text paint over them and the box takes the pointer where it
302
+ * stands.
303
+ *
304
+ * The target is `::before`: a square of the WCAG minimum, hit but never
305
+ * seen. It is not centred on the box. A positioned box that hangs past the
306
+ * root's end or bottom edge is scrollable overflow in whatever the checkbox
307
+ * stands in, and a centred square hangs four pixels past the end in the
308
+ * label-first arrangement and two past the bottom in both; hanging past the
309
+ * start or the top is overflow nobody can scroll to. So the square ends
310
+ * where the box ends, and the eight pixels it adds are above the box and on
311
+ * its start side — which for a pointer is the same twenty-four.
312
+ *
313
+ * The halo is `::after`: one wash behind the box and the label, the target
314
+ * the pointer is over, lit as a whole. It stands `--_halo-spread` off the
315
+ * box on every side, and its corner is the box's radius plus
316
+ * that distance, so the corner is concentric with the square's. Past the
317
+ * text it stands half as far again: a line of text ends in a glyph's side
318
+ * bearing, lighter than the square's solid edge, and at `dense` the bare
319
+ * 2px read as the wash cutting into the last letter. It was a
320
+ * 32px circle centred on the box, and a circle needs room the row does not
321
+ * have: it reached past the gap to the label in seven of nine
322
+ * size-and-density pairs and drew under the label's first letter, and a
323
+ * table cell a little taller than the box cut it off. Material pays for the
324
+ * circle by laying out a 40px box around every checkbox; the wash takes
325
+ * the room the label already occupies. Its block edges clear the box by the
326
+ * same distance, so a one-line wash is the box plus that distance on every
327
+ * side, and a wrapped label is washed to its last line. The description is
328
+ * not washed: it does not toggle anything.
329
+ *
330
+ * Its ink is the accent's text tone at the halo opacity, and not the accent
331
+ * surface (CK7). A surface tone is a ground, tuned to sit under text on the
332
+ * page rather than to be told apart from it: at four tenths it measured
333
+ * 1.087:1 against the page in `light`, 1.026:1 in `dark` and 1.000:1 in
334
+ * `high-contrast`, where the token is the page's own white — the halo was a
335
+ * gesture one scheme out of three could see. The text tone is the accent the
336
+ * theme guarantees legible on the page in every scheme, so a twelfth of it
337
+ * lands in the same place in all three. `accent-border` carries the same
338
+ * guarantee and was not taken: `high-contrast` paints every border black, so
339
+ * the halo would have turned grey there and blue elsewhere.
340
+ *
341
+ * The tile has neither. Its surface is the target and answers the pointer
342
+ * itself, and its label's `::after` stretches over that surface instead.
343
+ */
344
+ [data-ui='checkbox'][data-part='root']:not([data-variant='tile']) [data-part='label']::before {
345
+ block-size: var(--_target);
346
+ content: '';
347
+ direction: var(--_direction);
348
+ inline-size: var(--_target);
349
+ inset-block-start: calc(var(--_lift) + var(--_size) - var(--_target));
350
+ inset-inline-start: calc((var(--_size) - var(--_target)) / 2 - var(--_size) - var(--_gap));
351
+ position: absolute;
352
+ z-index: -1;
353
+ }
354
+
355
+ [data-ui='checkbox'][data-part='root']:not([data-variant='tile']) [data-part='label']::after {
356
+ background-color: var(--slotted-checkbox-halo-color, var(--slotted-tone-accent-text));
357
+ border-radius: calc(
358
+ var(--slotted-checkbox-radius, var(--slotted-radius-xs, 2px)) + var(--_halo-spread)
359
+ );
360
+ content: '';
361
+ direction: var(--_direction);
362
+ inset-block: calc(var(--_lift) - var(--_halo-spread));
363
+ inset-inline-end: calc(-1.5 * var(--_halo-spread));
364
+ inset-inline-start: calc(-1 * (var(--_size) + var(--_gap) + var(--_halo-spread)));
365
+ opacity: 0;
366
+ position: absolute;
367
+ transition: opacity var(--slotted-control-transition-duration)
368
+ var(--slotted-control-transition-easing);
369
+ z-index: -1;
370
+ }
371
+
372
+ /**
373
+ * A label that shows nothing: empty — React renders one for a box named by
374
+ * `aria-label`, Angular whether or not content is projected — or holding
375
+ * only hidden text, which the bindings mark `data-label-hidden` because a
376
+ * stylesheet cannot tell a text node from a hidden element. Either is zero
377
+ * wide, and the column gap beside it pushed whatever followed the box eight
378
+ * pixels away and drew the wash to the gap and half again past the box: 14px
379
+ * on the end side against 4px on the start. So the gap goes, the target ends
380
+ * where the box ends, and the wash stands at the halo spread on all four sides.
381
+ */
382
+ [data-ui='checkbox'][data-part='root']:is(
383
+ [data-label-hidden],
384
+ :has(> [data-part='label']:empty)
385
+ ):not([data-label-placement='start']) {
386
+ column-gap: 0;
387
+ }
388
+
389
+ [data-ui='checkbox'][data-part='root']:is(
390
+ [data-label-hidden],
391
+ :has(> [data-part='label']:empty)
392
+ ):not([data-label-placement='start'], [data-variant='tile'])
393
+ [data-part='label']::before {
394
+ /* Ending where the box ends, as everywhere: the root ends there too. */
395
+ inset-inline-start: calc(-1 * var(--_target));
396
+ }
397
+
398
+ [data-ui='checkbox'][data-part='root']:is(
399
+ [data-label-hidden],
400
+ :has(> [data-part='label']:empty)
401
+ ):not([data-label-placement='start'], [data-variant='tile'])
402
+ [data-part='label']::after {
403
+ inset-inline-end: calc(-1 * var(--_halo-spread));
404
+ inset-inline-start: calc(-1 * (var(--_size) + var(--_halo-spread)));
405
+ }
406
+
407
+ /**
408
+ * Aligned under the label rather than under the box, and never inside the
409
+ * label: inside, it would join the accessible name, and a paragraph of help
410
+ * text that toggles a value when it is clicked is a paragraph nobody reads.
411
+ */
412
+ [data-ui='checkbox'] [data-part='description'] {
413
+ color: var(--slotted-checkbox-description-color, var(--slotted-text-muted, GrayText));
414
+ font-size: var(--slotted-checkbox-description-font-size, var(--slotted-font-size-xs, 0.75rem));
415
+ grid-column: 2;
416
+ grid-row: 2;
417
+ line-height: var(--slotted-line-height-xs, 1.4);
418
+ margin: 0;
419
+ overflow-wrap: anywhere;
420
+ padding-block-start: var(--_description-gap);
421
+ }
422
+
423
+ /* ── Placement ────────────────────────────────────────────────────────
424
+ *
425
+ * The box on the other side of its label. Two columns swap and the column
426
+ * gap stays between them, so the distance between box and text is the same
427
+ * number in both arrangements and in both writing directions.
428
+ */
429
+
430
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] {
431
+ /**
432
+ * `1fr` and not `max-content`, which was tried first and looked better in
433
+ * one row than in five: `max-content` sizes each item's first column from
434
+ * its own label, so a column of choices ends with the boxes at five
435
+ * different distances from the edge. A control that does not line up with
436
+ * the control above it is a worse picture than a control at the end of its
437
+ * row — and the end of the row is where a label-first arrangement puts a
438
+ * control everywhere it is used, from a settings list to a table cell.
439
+ */
440
+ grid-template-columns: minmax(0, 1fr) auto;
441
+ }
442
+
443
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='control'],
444
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='indicator'] {
445
+ grid-area: 1 / 2;
446
+ }
447
+
448
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='label'],
449
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='description'] {
450
+ grid-column: 1;
451
+ }
452
+
453
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='label'] {
454
+ justify-self: stretch;
455
+ }
456
+
457
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='label']::before {
458
+ inset-inline-end: calc(-1 * (var(--_size) + var(--_gap)));
459
+ inset-inline-start: auto;
460
+ }
461
+
462
+ [data-ui='checkbox'][data-part='root'][data-label-placement='start'] [data-part='label']::after {
463
+ inset-inline-end: calc(-1 * (var(--_size) + var(--_gap) + var(--_halo-spread)));
464
+ inset-inline-start: calc(-1.5 * var(--_halo-spread));
465
+ }
466
+
467
+ /* ── States ───────────────────────────────────────────────────────────
468
+ *
469
+ * In the order `STATE_PRECEDENCE` declares, each weaker one held back for
470
+ * every stronger one it can meet: a disabled control answers no hover, and a
471
+ * read-only one answers no press.
472
+ */
473
+
474
+ /**
475
+ * Hover is the edge and the halo, and the ground stays: the halo is the
476
+ * pointer's answer, and a ground that tinted with it was two answers.
477
+ * The halo lights from the box and from the label alike, because both are
478
+ * the same target — the label is what toggles the control.
479
+ */
480
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
481
+ [data-part='control']:hover:not(:disabled) {
482
+ border-color: var(--slotted-checkbox-hover-border-color, var(--slotted-tone-accent-border));
483
+ }
484
+
485
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
486
+ [data-part='control']:hover:not(:disabled)
487
+ ~ [data-part='label']::after,
488
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
489
+ [data-part='control']:not(:disabled)
490
+ ~ [data-part='label']:hover::after {
491
+ opacity: var(--slotted-selection-halo-opacity, 0.12);
492
+ }
493
+
494
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
495
+ [data-part='control']:active:not(:disabled) {
496
+ background-color: var(
497
+ --slotted-checkbox-active-background,
498
+ var(--slotted-tone-accent-subtle-active)
499
+ );
500
+ }
501
+
502
+ [data-ui='checkbox'] [data-part='control']:checked,
503
+ [data-ui='checkbox'] [data-part='control']:indeterminate {
504
+ background-color: var(--slotted-checkbox-checked-background, var(--slotted-tone-accent-solid));
505
+ border-color: var(--slotted-checkbox-checked-border-color, var(--slotted-tone-accent-solid));
506
+ }
507
+
508
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
509
+ [data-part='control']:checked:hover:not(:disabled),
510
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
511
+ [data-part='control']:indeterminate:hover:not(:disabled) {
512
+ background-color: var(
513
+ --slotted-checkbox-checked-hover-background,
514
+ var(--slotted-tone-accent-solid-hover)
515
+ );
516
+ border-color: var(
517
+ --slotted-checkbox-checked-hover-background,
518
+ var(--slotted-tone-accent-solid-hover)
519
+ );
520
+ }
521
+
522
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
523
+ [data-part='control']:checked:active:not(:disabled),
524
+ [data-ui='checkbox'][data-part='root']:not([data-readonly])
525
+ [data-part='control']:indeterminate:active:not(:disabled) {
526
+ background-color: var(
527
+ --slotted-checkbox-checked-active-background,
528
+ var(--slotted-tone-accent-solid-active)
529
+ );
530
+ border-color: var(
531
+ --slotted-checkbox-checked-active-background,
532
+ var(--slotted-tone-accent-solid-active)
533
+ );
534
+ }
535
+
536
+ [data-ui='checkbox'] [data-part='control']:focus-visible {
537
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
538
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
539
+ }
540
+
541
+ /**
542
+ * The ring is retargeted rather than repainted: every rule that draws focus
543
+ * reads one name, so pointing it at the invalid colour moves the outline
544
+ * without a second focus rule existing anywhere.
545
+ */
546
+ [data-ui='checkbox'][data-part='root'][data-invalid] {
547
+ --slotted-focus-ring-color: var(
548
+ --slotted-checkbox-invalid-focus-ring-color,
549
+ var(--slotted-focus-ring-color-invalid, Highlight)
550
+ );
551
+ }
552
+
553
+ /**
554
+ * The border thickens as well as changing colour. In the `high-contrast`
555
+ * scheme the theme paints `--slotted-tone-danger-border` the same black as
556
+ * every other border, so colour alone would make an invalid control identical
557
+ * to a valid one — and `docs/component-features.md` §2.5 refuses a state that
558
+ * only colour carries. It thickens by the edge every invalid control adds,
559
+ * `--slotted-control-invalid-edge-width`, and as a border rather than the
560
+ * inset shadow a field draws, because a width survives forced colours and a
561
+ * shadow does not. The box keeps its size, because the border box is what is
562
+ * measured.
563
+ */
564
+ [data-ui='checkbox'][data-part='root'][data-invalid] [data-part='control'] {
565
+ border-width: calc(
566
+ var(--slotted-checkbox-border-width, var(--slotted-border-width-medium, 1.5px)) +
567
+ var(--slotted-checkbox-invalid-edge-width, var(--slotted-control-invalid-edge-width, 1px))
568
+ );
569
+ border-color: var(--slotted-checkbox-invalid-border-color, var(--slotted-tone-danger-border));
570
+ }
571
+
572
+ [data-ui='checkbox'][data-part='root'][data-invalid] [data-part='control']:checked,
573
+ [data-ui='checkbox'][data-part='root'][data-invalid] [data-part='control']:indeterminate {
574
+ background-color: var(
575
+ --slotted-checkbox-invalid-checked-background,
576
+ var(--slotted-tone-danger-solid)
577
+ );
578
+ border-color: var(
579
+ --slotted-checkbox-invalid-checked-background,
580
+ var(--slotted-tone-danger-solid)
581
+ );
582
+ /*
583
+ * A filled box has its border in the fill's colour, so a wider border says
584
+ * nothing once the box is checked, and the fill's colour alone is what a
585
+ * colour cannot carry (WCAG 1.4.1). A line of the surface inside the
586
+ * border, the edge's width, parts the border from the fill: a checked
587
+ * invalid box is a ring around a smaller fill, a checked valid one is
588
+ * solid.
589
+ */
590
+ box-shadow: inset 0 0 0
591
+ var(--slotted-checkbox-invalid-edge-width, var(--slotted-control-invalid-edge-width, 1px))
592
+ var(--slotted-surface, Canvas);
593
+ }
594
+
595
+ /**
596
+ * Read-only keeps the ink, the ring and the tab stop: the value is real and
597
+ * is submitted, and `docs/component-features.md` §2.5 refuses to let it look
598
+ * disabled. Only the ground and the pointer say the value is not the
599
+ * reader's to move.
600
+ */
601
+ [data-ui='checkbox'][data-part='root'][data-readonly] [data-part='control'],
602
+ [data-ui='checkbox'][data-part='root'][data-readonly] [data-part='label'] {
603
+ cursor: default;
604
+ }
605
+
606
+ [data-ui='checkbox'][data-part='root'][data-readonly]
607
+ [data-part='control']:not(:checked):not(:indeterminate) {
608
+ background-color: var(
609
+ --slotted-checkbox-readonly-background,
610
+ /**
611
+ * The ground `textarea` already gives a read-only value, and not the
612
+ * sunken surface: at the `light` scheme the sunken step is #f8fafd
613
+ * against a white surface, a difference a reader cannot see, so the state
614
+ * existed in the accessibility tree and nowhere on screen.
615
+ */
616
+ var(--slotted-tone-secondary-subtle-hover)
617
+ );
618
+ }
619
+
620
+ /**
621
+ * `:disabled` beside `[data-disabled]`, because a disabled `fieldset`
622
+ * disables every control inside it and tells none of them.
623
+ */
624
+ [data-ui='checkbox'][data-part='root'][data-disabled] [data-part='control'],
625
+ [data-ui='checkbox'] [data-part='control']:disabled {
626
+ background-color: var(
627
+ --slotted-checkbox-disabled-background,
628
+ var(--slotted-disabled-background)
629
+ );
630
+ border-color: var(--slotted-checkbox-disabled-border-color, var(--slotted-disabled-border));
631
+ cursor: not-allowed;
632
+ }
633
+
634
+ [data-ui='checkbox'] [data-part='control']:disabled + [data-part='indicator'] {
635
+ color: var(--slotted-checkbox-disabled-mark-color, var(--slotted-disabled-foreground));
636
+ }
637
+
638
+ /**
639
+ * The element's own state reaches the text beside it through the general
640
+ * sibling combinator, and not only through the mirrored attribute: a disabled
641
+ * `fieldset` disables every control inside it and tells none of them, so a
642
+ * checkbox in a disabled group would otherwise keep a full-contrast label on
643
+ * a control nobody can reach.
644
+ */
645
+ [data-ui='checkbox'][data-part='root'][data-disabled] [data-part='label'],
646
+ [data-ui='checkbox'][data-part='root'][data-disabled] [data-part='description'],
647
+ [data-ui='checkbox'] [data-part='control']:disabled ~ [data-part='label'],
648
+ [data-ui='checkbox'] [data-part='control']:disabled ~ [data-part='description'] {
649
+ color: var(--slotted-checkbox-disabled-color, var(--slotted-disabled-foreground));
650
+ cursor: not-allowed;
651
+ }
652
+
653
+ /* ── The tile ─────────────────────────────────────────────────────────
654
+ *
655
+ * A choice drawn as a framed surface, and the whole of that surface as its
656
+ * target. Three of the implementations surveyed put this inside the checkbox
657
+ * family — Mantine's `Checkbox.Card`, Chakra's `CheckboxCard`, React Aria's
658
+ * `CheckboxButton` — and the reason it is not `card`'s `data-part="selection"`
659
+ * is that `card` is a content container: it holds media, three regions and an
660
+ * elevation calibrated for content, and it knows nothing of a `size` arriving
661
+ * from a field, of `invalid`, of `readonly` or of `required`. `selection`
662
+ * stays what it is, a choice living inside real card content; this is a
663
+ * choice that is the whole of what the surface holds.
664
+ *
665
+ * The name is `tile` rather than `card`, which is what Mantine and Chakra
666
+ * call it, because `card` already names a family here. Carbon and Cloudscape
667
+ * both call the framed choice a tile.
668
+ *
669
+ * The tile does not arrange itself. `grid` already ships `columns`,
670
+ * `minItemWidth` and a stretching alignment, so a row of equal-width tiles of
671
+ * equal height is a `grid` around the items and nothing this family adds.
672
+ */
673
+
674
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'] {
675
+ /* The description sits one step nearer its label inside a frame (CK2):
676
+ the tile's own inset already holds the pair apart from everything else,
677
+ and the plain variant's distance read as a third row inside the box. */
678
+ --_description-gap: var(
679
+ --slotted-checkbox-tile-description-gap,
680
+ var(--slotted-space-2, 0.25rem)
681
+ );
682
+ /* Current. The gated rules below move these three and nothing else, so a
683
+ state change is one declaration rather than a repainted rule. */
684
+ --_tile-surface: var(--slotted-checkbox-tile-background, var(--slotted-surface, Canvas));
685
+ --_tile-edge: var(
686
+ --slotted-checkbox-tile-border-color,
687
+ var(--slotted-control-border-color, ButtonBorder)
688
+ );
689
+ --_tile-edge-width: var(--slotted-control-border-width, 1px);
690
+
691
+ background-color: var(--_tile-surface);
692
+ border-radius: var(--slotted-checkbox-tile-radius, var(--slotted-radius-md, 6px));
693
+ /* The shadow a framed control carries at rest. A tile is a framed control,
694
+ so it reads the control step rather than a surface elevation: the two say
695
+ different things and the token foundation separates them for that. */
696
+ box-shadow: var(--slotted-control-shadow, none);
697
+ cursor: pointer;
698
+ padding: var(--_tile-inset);
699
+ /* The containing block for the edge and for the stretched target. */
700
+ position: relative;
701
+ transition:
702
+ background-color var(--slotted-control-transition-duration)
703
+ var(--slotted-control-transition-easing),
704
+ box-shadow var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
705
+ }
706
+
707
+ /**
708
+ * The edge is drawn over the border box and never in flow, so a hairline
709
+ * becoming a two-pixel chosen edge moves no line of text inside the tile. A
710
+ * real border would have to reserve its resting width, and reserving it is
711
+ * what makes the change shift every row by a pixel. It is a border rather
712
+ * than a shadow so that it survives forced colours, where a shadow is removed
713
+ * outright and a width is not. `card` draws its own edge the same way and for
714
+ * the same two reasons.
715
+ */
716
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']::before {
717
+ border: calc(var(--_tile-edge-width) + var(--_tile-invalid-edge, 0px)) solid var(--_tile-edge);
718
+ border-radius: inherit;
719
+ content: '';
720
+ inset: 0;
721
+ pointer-events: none;
722
+ position: absolute;
723
+ transition: border-color var(--slotted-control-transition-duration)
724
+ var(--slotted-control-transition-easing);
725
+ }
726
+
727
+ /**
728
+ * The target is the tile. The label already owns the target in the plain
729
+ * variant; here its hit area stretches over the whole surface, which is the
730
+ * mechanism `card` uses for a card that resolves to one destination. The
731
+ * element stays a `<label for>`, so the name, the toggle and `Space` are the
732
+ * platform's and nothing is reimplemented.
733
+ *
734
+ * Anything a consumer raises above the surface — a link inside the
735
+ * description, a button in the corner — sets `position: relative` on itself,
736
+ * for the same reason a card's footer does.
737
+ */
738
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'] [data-part='label']::after {
739
+ content: '';
740
+ inset: 0;
741
+ position: absolute;
742
+ }
743
+
744
+ /* ── The tile's states ────────────────────────────────────────────────
745
+ *
746
+ * Read on the root rather than on the control, because the stretched
747
+ * target means the pointer is over the label and not over the box for all but
748
+ * sixteen pixels of the surface. Each rule holds itself back for every
749
+ * stronger state it can meet, in the order `STATE_PRECEDENCE` declares —
750
+ * including `checked`, which outranks both: without that guard, hovering a
751
+ * chosen tile repainted it in the fainter tint an unchosen one shows, so the
752
+ * pointer made a tile look less chosen than it was.
753
+ */
754
+
755
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:not([data-readonly]):not(
756
+ [data-disabled]
757
+ ):has([data-part='control']:not(:disabled)):not(
758
+ :has([data-part='control']:is(:checked, :indeterminate))
759
+ ):hover {
760
+ --_tile-surface: var(
761
+ --slotted-checkbox-tile-hover-background,
762
+ var(--slotted-tone-accent-subtle-hover)
763
+ );
764
+ --_tile-edge: var(
765
+ --slotted-checkbox-tile-hover-border-color,
766
+ var(--slotted-tone-accent-border)
767
+ );
768
+ }
769
+
770
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:not([data-readonly]):not(
771
+ [data-disabled]
772
+ ):has([data-part='control']:not(:disabled)):not(
773
+ :has([data-part='control']:is(:checked, :indeterminate))
774
+ ):active {
775
+ --_tile-surface: var(
776
+ --slotted-checkbox-tile-active-background,
777
+ var(--slotted-tone-accent-subtle-active)
778
+ );
779
+ }
780
+
781
+ /**
782
+ * Chosen says it with the edge, at the solid step and at twice the width. The
783
+ * ground moves one tint with it, because at tile area an edge alone is a
784
+ * difference a reader has to look for across a row of five, and the tint
785
+ * alone is a difference the `high-contrast` scheme flattens to nothing.
786
+ */
787
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:not([data-readonly]):has(
788
+ [data-part='control']:is(:checked, :indeterminate)
789
+ ) {
790
+ --_tile-edge: var(
791
+ --slotted-checkbox-tile-checked-border-color,
792
+ var(--slotted-tone-accent-solid)
793
+ );
794
+ --_tile-edge-width: var(--slotted-border-width-thick, 2px);
795
+ }
796
+
797
+ /**
798
+ * The chosen tint is the weaker half of what "chosen" says, so it holds
799
+ * itself back for read-only, which outranks it. The edge above does not: the
800
+ * value is real and stays visible, and a read-only set then reads as a set of
801
+ * secondary grounds with one accent edge on the choice that was made.
802
+ */
803
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:not([data-readonly]):has(
804
+ [data-part='control']:is(:checked, :indeterminate)
805
+ ) {
806
+ --_tile-surface: var(
807
+ --slotted-checkbox-tile-checked-background,
808
+ var(--slotted-tone-accent-surface)
809
+ );
810
+ }
811
+
812
+ /**
813
+ * A chosen tile still answers the pointer, and it is the edge that moves
814
+ * rather than the ground: the chosen tint is already the deepest step the
815
+ * accent tone has below its solid, so a ground that moved would either not
816
+ * move at all in `light` — where `accent-subtle-active` and `accent-surface`
817
+ * are the same value — or land on the tint an unchosen tile shows when it is
818
+ * merely hovered. Carbon moves the ground because its selected fill is not at
819
+ * the end of its own scale; here it is.
820
+ */
821
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:not([data-readonly]):not(
822
+ [data-disabled]
823
+ ):has([data-part='control']:is(:checked, :indeterminate):not(:disabled)):hover {
824
+ --_tile-edge: var(
825
+ --slotted-checkbox-tile-checked-hover-border-color,
826
+ var(--slotted-tone-accent-solid-hover)
827
+ );
828
+ }
829
+
830
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'][data-invalid] {
831
+ --_tile-edge: var(
832
+ --slotted-checkbox-tile-invalid-border-color,
833
+ var(--slotted-tone-danger-border)
834
+ );
835
+ /* Added to whatever width the tile's state draws, chosen or not, so an
836
+ invalid tile is wider than the same tile valid (WCAG 1.4.1). */
837
+ --_tile-invalid-edge: var(
838
+ --slotted-checkbox-invalid-edge-width,
839
+ var(--slotted-control-invalid-edge-width, 1px)
840
+ );
841
+ }
842
+
843
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'][data-invalid]:has(
844
+ [data-part='control']:is(:checked, :indeterminate)
845
+ ) {
846
+ --_tile-surface: var(
847
+ --slotted-checkbox-tile-invalid-checked-background,
848
+ var(--slotted-tone-danger-surface)
849
+ );
850
+ --_tile-edge: var(
851
+ --slotted-checkbox-tile-invalid-checked-border-color,
852
+ var(--slotted-tone-danger-solid)
853
+ );
854
+ }
855
+
856
+ /* Read-only keeps the ink and the tab stop; only the ground and the pointer
857
+ say the value is not the reader's to move. */
858
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'][data-readonly] {
859
+ --_tile-surface: var(
860
+ --slotted-checkbox-tile-readonly-background,
861
+ var(--slotted-tone-secondary-subtle-hover)
862
+ );
863
+ /**
864
+ * The frame holds back for read-only, edge and all. It was the ground
865
+ * alone, and in the `dark` scheme that ground is `#18181b` against a chosen
866
+ * tile's `#181b22`: seven units of blue, which is a state that exists in
867
+ * the accessibility tree and nowhere on screen. The box inside is still
868
+ * painted chosen — it is the mark that says what was chosen — and the frame
869
+ * says the value is not the reader's to move, in every scheme, because the
870
+ * neutral edge and the accent edge are different colours in all three.
871
+ */
872
+ --_tile-edge: var(--slotted-checkbox-tile-readonly-border-color, var(--slotted-border-strong));
873
+
874
+ cursor: default;
875
+ }
876
+
877
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'][data-disabled],
878
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:has([data-part='control']:disabled) {
879
+ --_tile-surface: var(
880
+ --slotted-checkbox-tile-disabled-background,
881
+ var(--slotted-disabled-background)
882
+ );
883
+ --_tile-edge: var(
884
+ --slotted-checkbox-tile-disabled-border-color,
885
+ var(--slotted-disabled-border)
886
+ );
887
+
888
+ /* A tile that cannot be used does not sit above the page. */
889
+ box-shadow: none;
890
+ cursor: not-allowed;
891
+ }
892
+
893
+ /**
894
+ * The ring goes around the tile and the box loses its own. Drawing both left
895
+ * a ring around a sixteen-pixel box inside a ring around the whole surface,
896
+ * which reads as two controls. The tile is what the pointer and the keyboard
897
+ * are addressing, so the tile is what the ring is about.
898
+ */
899
+ [data-ui='checkbox'][data-part='root'][data-variant='tile']:has(
900
+ [data-part='control']:focus-visible
901
+ ) {
902
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
903
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
904
+ }
905
+
906
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'] [data-part='control']:focus-visible {
907
+ outline: none;
908
+ }
909
+
910
+ /* ── Forced colors ────────────────────────────────────────────────────
911
+ *
912
+ * The one place this family paints something the theme cannot reach. Forced
913
+ * colors overrides the box's `background-color` and the mark's `color`, so
914
+ * the mark and the ground it stands on would be forced to one colour and the
915
+ * check would disappear into the box it is drawn in. The pair is restated here in system
916
+ * colours, which is what the briefing allows a family to do exactly when the
917
+ * theme cannot.
918
+ */
919
+ @media (forced-colors: active) {
920
+ [data-ui='checkbox'] [data-part='control'] {
921
+ background-color: Field;
922
+ border-color: FieldText;
923
+ }
924
+
925
+ [data-ui='checkbox'] [data-part='control']:checked,
926
+ [data-ui='checkbox'] [data-part='control']:indeterminate {
927
+ background-color: Highlight;
928
+ border-color: Highlight;
929
+ }
930
+
931
+ /*
932
+ * A checked invalid box keeps the checked pair. The theme maps the danger
933
+ * fill to `ButtonFace`, so the invalid rule above painted fill, border and
934
+ * mark in the page's white and the box looked unchecked. The wider border
935
+ * takes `CanvasText`, and the parting line, a shadow forced colours remove,
936
+ * comes back as an outline in the page's colour inside it; a focused one
937
+ * draws its ring double.
938
+ */
939
+ [data-ui='checkbox'][data-part='root'][data-invalid] [data-part='control']:checked,
940
+ [data-ui='checkbox'][data-part='root'][data-invalid] [data-part='control']:indeterminate {
941
+ background-color: Highlight;
942
+ border-color: CanvasText;
943
+ outline: var(
944
+ --slotted-checkbox-invalid-edge-width,
945
+ var(--slotted-control-invalid-edge-width, 1px)
946
+ )
947
+ solid Canvas;
948
+ outline-offset: calc(
949
+ -1 *
950
+ (
951
+ var(--slotted-checkbox-border-width, var(--slotted-border-width-medium, 1.5px)) +
952
+ var(
953
+ --slotted-checkbox-invalid-edge-width,
954
+ var(--slotted-control-invalid-edge-width, 1px)
955
+ ) +
956
+ var(
957
+ --slotted-checkbox-invalid-edge-width,
958
+ var(--slotted-control-invalid-edge-width, 1px)
959
+ )
960
+ )
961
+ );
962
+ }
963
+
964
+ [data-ui='checkbox'][data-part='root'][data-invalid]
965
+ [data-part='control']:checked:focus-visible,
966
+ [data-ui='checkbox'][data-part='root'][data-invalid]
967
+ [data-part='control']:indeterminate:focus-visible {
968
+ outline: calc(
969
+ var(--slotted-focus-ring-width, 2px) +
970
+ var(
971
+ --slotted-checkbox-invalid-edge-width,
972
+ var(--slotted-control-invalid-edge-width, 1px)
973
+ )
974
+ )
975
+ double var(--slotted-focus-ring-color, Highlight);
976
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
977
+ }
978
+
979
+ [data-ui='checkbox'] [data-part='control']:checked + [data-part='indicator'],
980
+ [data-ui='checkbox'] [data-part='control']:indeterminate + [data-part='indicator'] {
981
+ color: HighlightText;
982
+ }
983
+
984
+ [data-ui='checkbox'][data-part='root'][data-invalid]
985
+ [data-part='control']:checked
986
+ + [data-part='indicator'],
987
+ [data-ui='checkbox'][data-part='root'][data-invalid]
988
+ [data-part='control']:indeterminate
989
+ + [data-part='indicator'] {
990
+ color: HighlightText;
991
+ }
992
+
993
+ /* A tile's invalid edge is the danger pair, which the theme maps to the
994
+ page's white; it takes the system's text colour instead. */
995
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'][data-invalid],
996
+ [data-ui='checkbox'][data-part='root'][data-variant='tile'][data-invalid]:has(
997
+ [data-part='control']:is(:checked, :indeterminate)
998
+ ) {
999
+ --_tile-edge: CanvasText;
1000
+ }
1001
+
1002
+ [data-ui='checkbox'] [data-part='control']:disabled {
1003
+ background-color: Field;
1004
+ border-color: GrayText;
1005
+ }
1006
+
1007
+ [data-ui='checkbox'] [data-part='control']:disabled + [data-part='indicator'] {
1008
+ color: GrayText;
1009
+ }
1010
+ }
1011
+ }