@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,778 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── One choice out of a set ────────────────────────────────────────────
4
+ *
5
+ * The root is a `div` that arranges its items and, where nothing else has
6
+ * grouped them, carries `role="radiogroup"`. Each item is a `div` holding a
7
+ * native `input[type=radio]`, its mark, its label and its description.
8
+ *
9
+ * The whole keyboard model is the platform's, because the inputs share a
10
+ * `name`: Tab lands on the checked item, the arrows move focus and selection
11
+ * in both axes, the set wraps, and disabled items are skipped — including
12
+ * the arrow directions of a right-to-left document, which a roving `tabindex`
13
+ * written once per binding would have to relearn twice.
14
+ *
15
+ * Its rules reach the same foundation tokens `checkbox`'s do rather than
16
+ * `checkbox` itself, so a theme can move one without the other and neither
17
+ * stylesheet depends on the other's selectors.
18
+ */
19
+ [data-ui='radio-group'][data-part='root'] {
20
+ /**
21
+ * The circle is the selection scale's, the same square `checkbox` draws
22
+ * in: 16px at `md`, stated once by the theme and not moved by density.
23
+ */
24
+ --_size: var(--slotted-radio-group-size, var(--slotted-selection-size-md, 16px));
25
+ --_font-size: var(--slotted-control-font-size-md, 13px);
26
+ /** `--_gap` spaces one choice from the next; `--_label-gap` spaces a
27
+ circle from its own label, and is the wider of the two. */
28
+ --_gap: var(--slotted-control-gap-md, 8px);
29
+ --_label-gap: var(--slotted-control-label-gap-md, 12px);
30
+ --_line-height: var(--slotted-radio-group-line-height, var(--slotted-line-height-md, 1.5));
31
+ /**
32
+ * The air the halo keeps around the circle and past the end of the text:
33
+ * half the gap between them, for the reason `checkbox` records.
34
+ */
35
+ --_halo-spread: calc(var(--_label-gap) / 2);
36
+ /**
37
+ * The target: the WCAG 2.2 minimum, hit around the circle by the halo's
38
+ * own box rather than laid out beside it, for the reason `checkbox`
39
+ * records — a row is its line, not its line padded out to twenty-four.
40
+ */
41
+ --_target: var(--slotted-target-min, 24px);
42
+ --_lift: calc((var(--_font-size) * var(--_line-height) - var(--_size)) / 2);
43
+ /** The distance from the label's text to the description under it: the
44
+ step `field` puts between its own rows, in both variants. */
45
+ --_description-gap: var(--slotted-space-3, 0.375rem);
46
+
47
+ color: var(--slotted-radio-group-color, var(--slotted-text, CanvasText));
48
+ display: flex;
49
+ flex-direction: column;
50
+ font-family: var(--slotted-control-font-family, system-ui, sans-serif);
51
+ font-size: var(--_font-size);
52
+ gap: var(--slotted-radio-group-gap, var(--_gap));
53
+ line-height: var(--_line-height);
54
+ }
55
+
56
+ /**
57
+ * A choice that carries a description is two rows tall, and one control gap
58
+ * between two of them puts a description as near the next label as its own.
59
+ * Proximity then names the wrong owner. The distance doubles exactly where
60
+ * the second row exists, so a column of bare labels keeps the tighter rhythm
61
+ * it reads well at, and it is the same doubling the horizontal rule below
62
+ * already applies.
63
+ */
64
+ [data-ui='radio-group'][data-part='root']:has([data-part='description']) {
65
+ row-gap: var(--slotted-radio-group-gap, calc(var(--_gap) * 2));
66
+ }
67
+
68
+ /**
69
+ * Horizontal wraps rather than scrolls or clips. A row of choices that runs
70
+ * past the container is a row whose last option nobody can see, and a set of
71
+ * options is not a scroll region.
72
+ */
73
+ [data-ui='radio-group'][data-part='root'][data-orientation='horizontal'] {
74
+ flex-direction: row;
75
+ flex-wrap: wrap;
76
+ /** Twice the control gap: the distance between two choices has to read as
77
+ larger than the distance between a box and its own label. */
78
+ column-gap: calc(var(--_gap) * 2);
79
+ }
80
+
81
+ /* ── Size ─────────────────────────────────────────────────────────────── */
82
+
83
+ [data-ui='radio-group'][data-part='root'][data-size='sm'] {
84
+ --_size: var(--slotted-radio-group-size, var(--slotted-selection-size-sm, 14px));
85
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
86
+ --_gap: var(--slotted-control-gap-sm, 6px);
87
+ --_label-gap: var(--slotted-control-label-gap-sm, 8px);
88
+ --_tile-inset: var(
89
+ --slotted-radio-group-tile-inset,
90
+ var(--slotted-control-padding-inline-sm, 10px)
91
+ );
92
+ }
93
+
94
+ [data-ui='radio-group'][data-part='root'][data-size='md'] {
95
+ --_size: var(--slotted-radio-group-size, var(--slotted-selection-size-md, 16px));
96
+ --_font-size: var(--slotted-control-font-size-md, 13px);
97
+ --_gap: var(--slotted-control-gap-md, 8px);
98
+ --_label-gap: var(--slotted-control-label-gap-md, 12px);
99
+ --_tile-inset: var(
100
+ --slotted-radio-group-tile-inset,
101
+ var(--slotted-control-padding-inline-md, 13px)
102
+ );
103
+ }
104
+
105
+ [data-ui='radio-group'][data-part='root'][data-size='lg'] {
106
+ --_size: var(--slotted-radio-group-size, var(--slotted-selection-size-lg, 18px));
107
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
108
+ --_gap: var(--slotted-control-gap-lg, 8px);
109
+ --_label-gap: var(--slotted-control-label-gap-lg, 12px);
110
+ --_tile-inset: var(
111
+ --slotted-radio-group-tile-inset,
112
+ var(--slotted-control-padding-inline-lg, 16px)
113
+ );
114
+ }
115
+
116
+ /* ── One item ─────────────────────────────────────────────────────────── */
117
+
118
+ [data-ui='radio-group'] [data-part='item'] {
119
+ /* The rows are as tall as they read. Stretched, the description drifted
120
+ away from the label it belongs to; the surplus is the container's. */
121
+ align-content: start;
122
+ display: grid;
123
+ grid-template-columns: auto minmax(0, 1fr);
124
+ /* The halo sits at a negative stack level, and this is the stacking
125
+ context it is negative inside of — see `checkbox`. */
126
+ isolation: isolate;
127
+ /* The containing block for the halo. */
128
+ position: relative;
129
+ }
130
+
131
+ /* The direction the halo and the target are placed in — the root's, not the
132
+ label's, for the reason `checkbox` records. */
133
+ [data-ui='radio-group'][data-part='root'] {
134
+ --_direction: ltr;
135
+ }
136
+
137
+ [data-ui='radio-group'][data-part='root']:dir(rtl) {
138
+ --_direction: rtl;
139
+ }
140
+
141
+ [data-ui='radio-group'] [data-part='control'] {
142
+ appearance: none;
143
+ background-color: var(--slotted-radio-group-background, var(--slotted-surface, Canvas));
144
+ /* The medium step in the strong border colour, as `checkbox` edges its
145
+ square: a sixteen-pixel circle is edged too lightly at one pixel and too
146
+ heavily at two. */
147
+ border: var(--slotted-radio-group-border-width, var(--slotted-border-width-medium, 1.5px)) solid
148
+ var(--slotted-radio-group-border-color, var(--slotted-border-strong, ButtonBorder));
149
+ /** A circle, and the one place this family and `checkbox` differ visually. */
150
+ border-radius: var(--slotted-radius-full, 9999px);
151
+ block-size: var(--_size);
152
+ box-sizing: border-box;
153
+ cursor: pointer;
154
+ grid-area: 1 / 1;
155
+ inline-size: var(--_size);
156
+ margin: 0;
157
+ margin-block-start: var(--_lift);
158
+ transition:
159
+ background-color var(--slotted-control-transition-duration)
160
+ var(--slotted-control-transition-easing),
161
+ border-color var(--slotted-control-transition-duration)
162
+ var(--slotted-control-transition-easing);
163
+ }
164
+
165
+ /**
166
+ * The dot: the accent's solid step on the surface, inside a circle whose
167
+ * edge has turned the same colour. The glyph scale's dot is authored at half
168
+ * its grid, so the `Glyph` element the binding renders, filling the box,
169
+ * draws a dot of eight pixels in a circle of sixteen — the proportion every surveyed implementation lands
170
+ * on — and the box the dot is centred in is the control's own, of whole
171
+ * pixels, so the two are concentric at every step.
172
+ *
173
+ * A dot rather than a hole punched in a thick accent edge, which was the
174
+ * other reading of the item: the hole is four pixels at `sm`, the contract
175
+ * names the dot glyph as this part's mark, and the mark then enters with the
176
+ * same growth `checkbox`'s does.
177
+ */
178
+ [data-ui='radio-group'] [data-part='indicator'] {
179
+ --slotted-icon-size: var(--_size);
180
+
181
+ block-size: var(--_size);
182
+ color: var(--slotted-radio-group-mark-color, var(--slotted-tone-accent-solid));
183
+ /* A flex box, so the icon sits in it as a box and not on a line of text. */
184
+ display: flex;
185
+ grid-area: 1 / 1;
186
+ inline-size: var(--_size);
187
+ margin-block-start: var(--_lift);
188
+ opacity: 0;
189
+ pointer-events: none;
190
+ /* The mark grows in at the medium step, one above the edge — the same
191
+ motion `checkbox` gives its check. */
192
+ scale: 0.6;
193
+ transition:
194
+ opacity var(--slotted-duration-medium) var(--slotted-control-transition-easing),
195
+ scale var(--slotted-duration-medium) var(--slotted-control-transition-easing);
196
+ }
197
+
198
+ [data-ui='radio-group'] [data-part='control']:checked + [data-part='indicator'] {
199
+ opacity: 1;
200
+ scale: 1;
201
+ }
202
+
203
+ [data-ui='radio-group'] [data-part='label'] {
204
+ cursor: pointer;
205
+ grid-column: 2;
206
+ grid-row: 1;
207
+ overflow-wrap: anywhere;
208
+ }
209
+
210
+ /**
211
+ * The gap between the control and its text is the grid's, not the label's
212
+ * padding: a padding is laid out in the label's own direction, and a label
213
+ * in Arabic inside a row in Portuguese put it on the far side, against
214
+ * nothing, with the text against the control.
215
+ */
216
+ [data-ui='radio-group'] [data-part='item']:has(> [data-part='label']) {
217
+ column-gap: var(--_label-gap);
218
+ }
219
+
220
+ [data-ui='radio-group'][data-part='root']:not([data-variant='tile']) [data-part='label'] {
221
+ justify-self: start;
222
+ position: relative;
223
+ }
224
+
225
+ /**
226
+ * The target and the halo, as `checkbox` draws them and for the reasons it
227
+ * records: the label's `::before` is the WCAG-minimum square, ending where
228
+ * the circle ends so it never hangs past the item's end or bottom; its
229
+ * `::after` is the wash behind the circle and the label, concentric with
230
+ * the circle, in the accent's text tone rather than its surface tone, which
231
+ * is a ground and disappeared into the page in `dark` and in
232
+ * `high-contrast` (CK7). The tile has neither; its surface answers the
233
+ * pointer.
234
+ */
235
+ [data-ui='radio-group'][data-part='root']:not([data-variant='tile']) [data-part='label']::before {
236
+ block-size: var(--_target);
237
+ content: '';
238
+ direction: var(--_direction);
239
+ inline-size: var(--_target);
240
+ inset-block-start: calc(var(--_lift) + var(--_size) - var(--_target));
241
+ inset-inline-start: calc(
242
+ (var(--_size) - var(--_target)) / 2 - var(--_size) - var(--_label-gap)
243
+ );
244
+ position: absolute;
245
+ z-index: -1;
246
+ }
247
+
248
+ [data-ui='radio-group'][data-part='root']:not([data-variant='tile']) [data-part='label']::after {
249
+ background-color: var(--slotted-radio-group-halo-color, var(--slotted-tone-accent-text));
250
+ border-radius: calc(var(--_size) / 2 + var(--_halo-spread));
251
+ content: '';
252
+ direction: var(--_direction);
253
+ inset-block: calc(var(--_lift) - var(--_halo-spread));
254
+ inset-inline-end: calc(-1.5 * var(--_halo-spread));
255
+ inset-inline-start: calc(-1 * (var(--_size) + var(--_label-gap) + var(--_halo-spread)));
256
+ opacity: 0;
257
+ position: absolute;
258
+ transition: opacity var(--slotted-control-transition-duration)
259
+ var(--slotted-control-transition-easing);
260
+ z-index: -1;
261
+ }
262
+
263
+ /* A label with nothing in it — the Angular binding renders one whether or
264
+ not content is projected — has no text to wash, so the wash closes around
265
+ the circle at the same distance on all four sides. */
266
+ [data-ui='radio-group'][data-part='root']:not([data-variant='tile'])
267
+ [data-part='label']:empty::after {
268
+ inset-inline-end: calc(var(--_label-gap) - var(--_halo-spread));
269
+ }
270
+
271
+ [data-ui='radio-group'] [data-part='description'] {
272
+ color: var(--slotted-radio-group-description-color, var(--slotted-text-muted, GrayText));
273
+ font-size: var(
274
+ --slotted-radio-group-description-font-size,
275
+ var(--slotted-font-size-xs, 0.75rem)
276
+ );
277
+ grid-column: 2;
278
+ grid-row: 2;
279
+ line-height: var(--slotted-line-height-xs, 1.4);
280
+ margin: 0;
281
+ overflow-wrap: anywhere;
282
+ padding-block-start: var(--_description-gap);
283
+ }
284
+
285
+ /* ── Placement ────────────────────────────────────────────────────────── */
286
+
287
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start'] [data-part='item'] {
288
+ /**
289
+ * `1fr` and not `max-content`, which was tried first and looked better in
290
+ * one row than in five: `max-content` sizes each item's first column from
291
+ * its own label, so a column of choices ends with the boxes at five
292
+ * different distances from the edge. A control that does not line up with
293
+ * the control above it is a worse picture than a control at the end of its
294
+ * row — and the end of the row is where a label-first arrangement puts a
295
+ * control everywhere it is used, from a settings list to a table cell.
296
+ */
297
+ grid-template-columns: minmax(0, 1fr) auto;
298
+ }
299
+
300
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start'] [data-part='control'],
301
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start'] [data-part='indicator'] {
302
+ grid-area: 1 / 2;
303
+ }
304
+
305
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start'] [data-part='label'],
306
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start']
307
+ [data-part='description'] {
308
+ grid-column: 1;
309
+ }
310
+
311
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start'] [data-part='label'] {
312
+ justify-self: stretch;
313
+ }
314
+
315
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start']
316
+ [data-part='label']::before {
317
+ inset-inline-end: calc(-1 * (var(--_size) + var(--_label-gap)));
318
+ inset-inline-start: auto;
319
+ }
320
+
321
+ [data-ui='radio-group'][data-part='root'][data-label-placement='start']
322
+ [data-part='label']::after {
323
+ inset-inline-end: calc(-1 * (var(--_size) + var(--_label-gap) + var(--_halo-spread)));
324
+ inset-inline-start: calc(-1.5 * var(--_halo-spread));
325
+ }
326
+
327
+ /* ── States ───────────────────────────────────────────────────────────── */
328
+
329
+ /**
330
+ * Hover is the edge and the halo, and the ground stays — the same answer
331
+ * `checkbox` gives, lit from the circle and from the label alike because
332
+ * both are the one target.
333
+ */
334
+ [data-ui='radio-group'][data-part='root']:not([data-readonly])
335
+ [data-part='control']:hover:not(:disabled) {
336
+ border-color: var(--slotted-radio-group-hover-border-color, var(--slotted-tone-accent-border));
337
+ }
338
+
339
+ [data-ui='radio-group'][data-part='root']:not([data-readonly])
340
+ [data-part='control']:hover:not(:disabled)
341
+ ~ [data-part='label']::after,
342
+ [data-ui='radio-group'][data-part='root']:not([data-readonly])
343
+ [data-part='control']:not(:disabled)
344
+ ~ [data-part='label']:hover::after {
345
+ opacity: var(--slotted-selection-halo-opacity, 0.12);
346
+ }
347
+
348
+ [data-ui='radio-group'][data-part='root']:not([data-readonly])
349
+ [data-part='control']:active:not(:disabled) {
350
+ background-color: var(
351
+ --slotted-radio-group-active-background,
352
+ var(--slotted-tone-accent-subtle-active)
353
+ );
354
+ }
355
+
356
+ /**
357
+ * Chosen is the edge and the dot in the accent's solid step, on the surface
358
+ * the circle already stood on: the ground does not fill, because the dot is
359
+ * what says chosen and a filled circle with a white dot is a checkbox with a
360
+ * different corner.
361
+ */
362
+ [data-ui='radio-group'] [data-part='control']:checked {
363
+ border-color: var(--slotted-radio-group-checked-border-color, var(--slotted-tone-accent-solid));
364
+ }
365
+
366
+ [data-ui='radio-group'][data-part='root']:not([data-readonly])
367
+ [data-part='control']:checked:hover:not(:disabled) {
368
+ border-color: var(
369
+ --slotted-radio-group-checked-hover-border-color,
370
+ var(--slotted-tone-accent-solid-hover)
371
+ );
372
+ }
373
+
374
+ [data-ui='radio-group'][data-part='root']:not([data-readonly])
375
+ [data-part='control']:checked:active:not(:disabled) {
376
+ border-color: var(
377
+ --slotted-radio-group-checked-active-border-color,
378
+ var(--slotted-tone-accent-solid-active)
379
+ );
380
+ }
381
+
382
+ [data-ui='radio-group'] [data-part='control']:focus-visible {
383
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
384
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
385
+ }
386
+
387
+ [data-ui='radio-group'][data-part='root'][data-invalid] {
388
+ --slotted-focus-ring-color: var(
389
+ --slotted-radio-group-invalid-focus-ring-color,
390
+ var(--slotted-focus-ring-color-invalid, Highlight)
391
+ );
392
+ }
393
+
394
+ /**
395
+ * The border thickens as well as changing colour. In the `high-contrast`
396
+ * scheme the theme paints `--slotted-tone-danger-border` the same black as
397
+ * every other border, so colour alone would make an invalid control identical
398
+ * to a valid one — and `docs/component-features.md` §2.5 refuses a state that
399
+ * only colour carries. It thickens by the edge every invalid control adds,
400
+ * `--slotted-control-invalid-edge-width`, and as a border rather than the
401
+ * inset shadow a field draws, because a width survives forced colours and a
402
+ * shadow does not. The box keeps its size, because the border box is what is
403
+ * measured.
404
+ */
405
+ [data-ui='radio-group'][data-part='root'][data-invalid] [data-part='control'] {
406
+ border-width: calc(
407
+ var(--slotted-radio-group-border-width, var(--slotted-border-width-medium, 1.5px)) +
408
+ var(
409
+ --slotted-radio-group-invalid-edge-width,
410
+ var(--slotted-control-invalid-edge-width, 1px)
411
+ )
412
+ );
413
+ border-color: var(
414
+ --slotted-radio-group-invalid-border-color,
415
+ var(--slotted-tone-danger-border)
416
+ );
417
+ }
418
+
419
+ [data-ui='radio-group'][data-part='root'][data-invalid] [data-part='control']:checked {
420
+ border-color: var(
421
+ --slotted-radio-group-invalid-checked-border-color,
422
+ var(--slotted-tone-danger-solid)
423
+ );
424
+ }
425
+
426
+ [data-ui='radio-group'][data-part='root'][data-invalid]
427
+ [data-part='control']:checked
428
+ + [data-part='indicator'] {
429
+ color: var(--slotted-radio-group-invalid-mark-color, var(--slotted-tone-danger-solid));
430
+ }
431
+
432
+ /**
433
+ * Read-only keeps the ink, the ring and the tab stop. Only the ground of the
434
+ * unchosen options and the pointer say the value is not the reader's to move,
435
+ * and the chosen one keeps its accent because the value is real.
436
+ */
437
+ [data-ui='radio-group'][data-part='root'][data-readonly] [data-part='control'],
438
+ [data-ui='radio-group'][data-part='root'][data-readonly] [data-part='label'] {
439
+ cursor: default;
440
+ }
441
+
442
+ [data-ui='radio-group'][data-part='root'][data-readonly] [data-part='control']:not(:checked) {
443
+ background-color: var(
444
+ --slotted-radio-group-readonly-background,
445
+ /**
446
+ * The ground `textarea` already gives a read-only value, and not the
447
+ * sunken surface: at the `light` scheme the sunken step is #f8fafd
448
+ * against a white surface, a difference a reader cannot see, so the state
449
+ * existed in the accessibility tree and nowhere on screen.
450
+ */
451
+ var(--slotted-tone-secondary-subtle-hover)
452
+ );
453
+ }
454
+
455
+ [data-ui='radio-group'][data-part='root'][data-disabled] [data-part='control'],
456
+ [data-ui='radio-group'] [data-part='control']:disabled {
457
+ background-color: var(
458
+ --slotted-radio-group-disabled-background,
459
+ var(--slotted-disabled-background)
460
+ );
461
+ border-color: var(--slotted-radio-group-disabled-border-color, var(--slotted-disabled-border));
462
+ cursor: not-allowed;
463
+ }
464
+
465
+ [data-ui='radio-group'] [data-part='control']:disabled + [data-part='indicator'] {
466
+ color: var(--slotted-radio-group-disabled-mark-color, var(--slotted-disabled-foreground));
467
+ }
468
+
469
+ [data-ui='radio-group'][data-part='root'][data-disabled] [data-part='label'],
470
+ [data-ui='radio-group'][data-part='root'][data-disabled] [data-part='description'],
471
+ [data-ui='radio-group'] [data-part='control']:disabled ~ [data-part='label'],
472
+ [data-ui='radio-group'] [data-part='control']:disabled ~ [data-part='description'] {
473
+ color: var(--slotted-radio-group-disabled-color, var(--slotted-disabled-foreground));
474
+ cursor: not-allowed;
475
+ }
476
+
477
+ /* ── The tile ─────────────────────────────────────────────────────────
478
+ *
479
+ * Each item drawn as a framed surface, and the whole of that surface as its
480
+ * target. The variant is the group's, because a set with one plain item among
481
+ * four tiles is a set nobody meant to write; the painting is the item's,
482
+ * because the item is what a reader clicks.
483
+ *
484
+ * A plan picker is the case this exists for, and it is an exclusive set:
485
+ * Cloudscape built a whole `Tiles` family for it and Carbon a selectable
486
+ * `Tile`. Shipping the variant on `checkbox` and declining it here would have
487
+ * been half a capability.
488
+ *
489
+ * The tile does not arrange itself. `grid` already ships `columns`,
490
+ * `minItemWidth` and a stretching alignment, so a row of equal-width tiles of
491
+ * equal height is a `grid` around the items and nothing this family adds.
492
+ */
493
+
494
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'] [data-part='item'] {
495
+ /* The description sits one step nearer its label inside a frame, as
496
+ `checkbox` keeps it (CK2). */
497
+ --_description-gap: var(
498
+ --slotted-radio-group-tile-description-gap,
499
+ var(--slotted-space-2, 0.25rem)
500
+ );
501
+ /* Current. The gated rules below move these three and nothing else. */
502
+ --_tile-surface: var(--slotted-radio-group-tile-background, var(--slotted-surface, Canvas));
503
+ --_tile-edge: var(
504
+ --slotted-radio-group-tile-border-color,
505
+ var(--slotted-control-border-color, ButtonBorder)
506
+ );
507
+ --_tile-edge-width: var(--slotted-control-border-width, 1px);
508
+
509
+ background-color: var(--_tile-surface);
510
+ border-radius: var(--slotted-radio-group-tile-radius, var(--slotted-radius-md, 6px));
511
+ /* The shadow a framed control carries at rest. A tile is a framed control,
512
+ so it reads the control step rather than a surface elevation. */
513
+ box-shadow: var(--slotted-control-shadow, none);
514
+ cursor: pointer;
515
+ padding: var(--_tile-inset);
516
+ /* The containing block for the edge and for the stretched target. */
517
+ position: relative;
518
+ transition:
519
+ background-color var(--slotted-control-transition-duration)
520
+ var(--slotted-control-transition-easing),
521
+ box-shadow var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
522
+ }
523
+
524
+ /**
525
+ * The edge is drawn over the border box and never in flow, so a hairline
526
+ * becoming a two-pixel chosen edge moves no line of text inside the tile. It
527
+ * is a border rather than a shadow so that it survives forced colours, where
528
+ * a shadow is removed outright and a width is not. `card` draws its own edge
529
+ * the same way and for the same two reasons.
530
+ */
531
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'] [data-part='item']::before {
532
+ border: calc(var(--_tile-edge-width) + var(--_tile-invalid-edge, 0px)) solid var(--_tile-edge);
533
+ border-radius: inherit;
534
+ content: '';
535
+ inset: 0;
536
+ pointer-events: none;
537
+ position: absolute;
538
+ transition: border-color var(--slotted-control-transition-duration)
539
+ var(--slotted-control-transition-easing);
540
+ }
541
+
542
+ /**
543
+ * The target is the tile. The label already owns the target in the plain
544
+ * variant; here its hit area stretches over the whole surface, which is the
545
+ * mechanism `card` uses for a card that resolves to one destination. The
546
+ * element stays a `<label for>`, so the name, the selection and the arrow
547
+ * keys are the platform's and nothing is reimplemented.
548
+ *
549
+ * Anything a consumer raises above the surface sets `position: relative` on
550
+ * itself, for the same reason a card's footer does.
551
+ */
552
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']
553
+ [data-part='item']
554
+ [data-part='label']::after {
555
+ content: '';
556
+ inset: 0;
557
+ position: absolute;
558
+ }
559
+
560
+ /* ── The tile's states ────────────────────────────────────────────────
561
+ *
562
+ * Read on the item rather than on the control, because the stretched
563
+ * target means the pointer is over the label and not over the box for all but
564
+ * sixteen pixels of the surface. Each rule holds itself back for every
565
+ * stronger state it can meet, in the order `STATE_PRECEDENCE` declares —
566
+ * including `checked`, which outranks both: without that guard, hovering a
567
+ * chosen tile repainted it in the fainter tint an unchosen one shows, so the
568
+ * pointer made a tile look less chosen than it was.
569
+ */
570
+
571
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']:not([data-readonly]):not(
572
+ [data-disabled]
573
+ )
574
+ [data-part='item']:has([data-part='control']:not(:disabled)):not(
575
+ :has([data-part='control']:checked)
576
+ ):hover {
577
+ --_tile-surface: var(
578
+ --slotted-radio-group-tile-hover-background,
579
+ var(--slotted-tone-accent-subtle-hover)
580
+ );
581
+ --_tile-edge: var(
582
+ --slotted-radio-group-tile-hover-border-color,
583
+ var(--slotted-tone-accent-border)
584
+ );
585
+ }
586
+
587
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']:not([data-readonly]):not(
588
+ [data-disabled]
589
+ )
590
+ [data-part='item']:has([data-part='control']:not(:disabled)):not(
591
+ :has([data-part='control']:checked)
592
+ ):active {
593
+ --_tile-surface: var(
594
+ --slotted-radio-group-tile-active-background,
595
+ var(--slotted-tone-accent-subtle-active)
596
+ );
597
+ }
598
+
599
+ /**
600
+ * Chosen says it with the edge, at the solid step and at twice the width. The
601
+ * ground moves one tint with it, because at tile area an edge alone is a
602
+ * difference a reader has to look for across a row of five, and the tint
603
+ * alone is a difference the `high-contrast` scheme flattens to nothing.
604
+ */
605
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']:not([data-readonly])
606
+ [data-part='item']:has([data-part='control']:checked) {
607
+ --_tile-edge: var(
608
+ --slotted-radio-group-tile-checked-border-color,
609
+ var(--slotted-tone-accent-solid)
610
+ );
611
+ --_tile-edge-width: var(--slotted-border-width-thick, 2px);
612
+ }
613
+
614
+ /**
615
+ * The chosen tint is the weaker half of what "chosen" says, so it holds
616
+ * itself back for read-only, which outranks it. The edge above does not: the
617
+ * value is real and stays visible, and a read-only set then reads as a set of
618
+ * secondary grounds with one accent edge on the choice that was made.
619
+ */
620
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']:not([data-readonly])
621
+ [data-part='item']:has([data-part='control']:checked) {
622
+ --_tile-surface: var(
623
+ --slotted-radio-group-tile-checked-background,
624
+ var(--slotted-tone-accent-surface)
625
+ );
626
+ }
627
+
628
+ /**
629
+ * A chosen tile still answers the pointer, and it is the edge that moves
630
+ * rather than the ground: the chosen tint is already the deepest step the
631
+ * accent tone has below its solid, so a ground that moved would either not
632
+ * move at all in `light` or land on the tint an unchosen hovered tile shows.
633
+ */
634
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']:not([data-readonly]):not(
635
+ [data-disabled]
636
+ )
637
+ [data-part='item']:has([data-part='control']:checked:not(:disabled)):hover {
638
+ --_tile-edge: var(
639
+ --slotted-radio-group-tile-checked-hover-border-color,
640
+ var(--slotted-tone-accent-solid-hover)
641
+ );
642
+ }
643
+
644
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'][data-invalid] [data-part='item'] {
645
+ --_tile-edge: var(
646
+ --slotted-radio-group-tile-invalid-border-color,
647
+ var(--slotted-tone-danger-border)
648
+ );
649
+ /* Added to whatever width the tile's state draws, chosen or not, so an
650
+ invalid tile is wider than the same tile valid (WCAG 1.4.1). */
651
+ --_tile-invalid-edge: var(
652
+ --slotted-radio-group-invalid-edge-width,
653
+ var(--slotted-control-invalid-edge-width, 1px)
654
+ );
655
+ }
656
+
657
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'][data-invalid]
658
+ [data-part='item']:has([data-part='control']:checked) {
659
+ --_tile-surface: var(
660
+ --slotted-radio-group-tile-invalid-checked-background,
661
+ var(--slotted-tone-danger-surface)
662
+ );
663
+ --_tile-edge: var(
664
+ --slotted-radio-group-tile-invalid-checked-border-color,
665
+ var(--slotted-tone-danger-solid)
666
+ );
667
+ }
668
+
669
+ /* Read-only keeps the ink and the tab stop; only the ground and the pointer
670
+ say the value is not the reader's to move. */
671
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'][data-readonly] [data-part='item'] {
672
+ --_tile-surface: var(
673
+ --slotted-radio-group-tile-readonly-background,
674
+ var(--slotted-tone-secondary-subtle-hover)
675
+ );
676
+ /**
677
+ * The frame holds back for read-only, edge and all, for the reason
678
+ * `checkbox` records: in the `dark` scheme the read-only ground and the
679
+ * chosen ground are seven units of blue apart, so the ground alone made a
680
+ * read-only chosen tile and a chosen tile the same picture. The circle
681
+ * inside is still painted chosen — it is the mark that says what was
682
+ * chosen — and the frame says the value is not the reader's to move.
683
+ */
684
+ --_tile-edge: var(
685
+ --slotted-radio-group-tile-readonly-border-color,
686
+ var(--slotted-border-strong)
687
+ );
688
+
689
+ cursor: default;
690
+ }
691
+
692
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'][data-disabled] [data-part='item'],
693
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']
694
+ [data-part='item']:has([data-part='control']:disabled) {
695
+ --_tile-surface: var(
696
+ --slotted-radio-group-tile-disabled-background,
697
+ var(--slotted-disabled-background)
698
+ );
699
+ --_tile-edge: var(
700
+ --slotted-radio-group-tile-disabled-border-color,
701
+ var(--slotted-disabled-border)
702
+ );
703
+
704
+ /* A tile that cannot be used does not sit above the page. */
705
+ box-shadow: none;
706
+ cursor: not-allowed;
707
+ }
708
+
709
+ /**
710
+ * The ring goes around the tile and the circle loses its own. Drawing both
711
+ * left a ring around a sixteen-pixel circle inside a ring around the whole
712
+ * surface, which reads as two controls.
713
+ */
714
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']
715
+ [data-part='item']:has([data-part='control']:focus-visible) {
716
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
717
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
718
+ }
719
+
720
+ [data-ui='radio-group'][data-part='root'][data-variant='tile']
721
+ [data-part='control']:focus-visible {
722
+ outline: none;
723
+ }
724
+
725
+ /* ── Forced colors ────────────────────────────────────────────────────
726
+ *
727
+ * Forced colors overrides the circle's `background-color` and the dot's
728
+ * `color`, so the dot would vanish into the circle it is drawn in. Restated in system colours, as `checkbox` restates the same pair.
729
+ */
730
+ @media (forced-colors: active) {
731
+ [data-ui='radio-group'] [data-part='control'] {
732
+ background-color: Field;
733
+ border-color: FieldText;
734
+ }
735
+
736
+ [data-ui='radio-group'] [data-part='control']:checked {
737
+ background-color: Field;
738
+ border-color: Highlight;
739
+ }
740
+
741
+ [data-ui='radio-group'] [data-part='control']:checked + [data-part='indicator'] {
742
+ color: Highlight;
743
+ }
744
+
745
+ /*
746
+ * A checked invalid option keeps the checked pair. The theme maps the
747
+ * danger fill to `ButtonFace`, so the invalid rules above painted the edge
748
+ * and the dot in the page's white and the dot vanished. The dot is
749
+ * `Highlight` as for a valid option, and the wider invalid edge takes
750
+ * `CanvasText`, as does a tile's invalid edge.
751
+ */
752
+ [data-ui='radio-group'][data-part='root'][data-invalid] [data-part='control']:checked {
753
+ background-color: Field;
754
+ border-color: CanvasText;
755
+ }
756
+
757
+ [data-ui='radio-group'][data-part='root'][data-invalid]
758
+ [data-part='control']:checked
759
+ + [data-part='indicator'] {
760
+ color: Highlight;
761
+ }
762
+
763
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'][data-invalid] [data-part='item'],
764
+ [data-ui='radio-group'][data-part='root'][data-variant='tile'][data-invalid]
765
+ [data-part='item']:has([data-part='control']:checked) {
766
+ --_tile-edge: CanvasText;
767
+ }
768
+
769
+ [data-ui='radio-group'] [data-part='control']:disabled {
770
+ background-color: Field;
771
+ border-color: GrayText;
772
+ }
773
+
774
+ [data-ui='radio-group'] [data-part='control']:disabled + [data-part='indicator'] {
775
+ color: GrayText;
776
+ }
777
+ }
778
+ }