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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,39 @@
1
+ [
2
+ "--slotted-border-subtle",
3
+ "--slotted-border-width-thin",
4
+ "--slotted-control-gap-md",
5
+ "--slotted-font-family-heading",
6
+ "--slotted-font-family-sans",
7
+ "--slotted-font-size-lg",
8
+ "--slotted-font-size-md",
9
+ "--slotted-font-size-sm",
10
+ "--slotted-font-size-xl",
11
+ "--slotted-font-size-xs",
12
+ "--slotted-font-weight-semibold",
13
+ "--slotted-line-height-lg",
14
+ "--slotted-line-height-md",
15
+ "--slotted-line-height-sm",
16
+ "--slotted-line-height-xl",
17
+ "--slotted-line-height-xs",
18
+ "--slotted-page-ground",
19
+ "--slotted-radius-lg",
20
+ "--slotted-section-border",
21
+ "--slotted-section-description-measure",
22
+ "--slotted-section-gap",
23
+ "--slotted-section-heading-size-lg",
24
+ "--slotted-section-heading-size-md",
25
+ "--slotted-section-heading-size-sm",
26
+ "--slotted-section-inset",
27
+ "--slotted-section-radius",
28
+ "--slotted-section-surface",
29
+ "--slotted-space-1",
30
+ "--slotted-space-3",
31
+ "--slotted-space-4",
32
+ "--slotted-space-5",
33
+ "--slotted-space-6",
34
+ "--slotted-surface",
35
+ "--slotted-surface-raised",
36
+ "--slotted-text",
37
+ "--slotted-text-muted",
38
+ "--slotted-z-index-sticky"
39
+ ]
@@ -0,0 +1,564 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── One choice out of a few, applied at once, on a track ───────────────
4
+ *
5
+ * The root is a `div` carrying `role="radiogroup"`; each segment is a
6
+ * `label` wrapping a real `input[type=radio]`. The whole keyboard model is
7
+ * therefore the platform's, because the radios share a `name`: Tab lands on
8
+ * the checked segment, the arrows move focus and the selection together in
9
+ * both axes and in both writing directions, the set wraps, and a disabled
10
+ * segment is skipped. `radio-group` records the same decision, and this is
11
+ * the family that wears it as a track.
12
+ *
13
+ * It is a radio group and not a row of buttons. Primer builds the row of
14
+ * buttons and pays one tab stop per segment where the Radio Group pattern
15
+ * gives one for the set; Angular Material writes `radiogroup` around
16
+ * children carrying `aria-pressed`, which is a group whose role and children
17
+ * disagree. The family whose items are buttons is `toggle-group`, and the
18
+ * difference a reader feels is the arrows: here they move the value.
19
+ *
20
+ * ── The control is exactly as tall as a control ───────────────────────
21
+ *
22
+ * The root is `--slotted-control-height-*` to the pixel, and everything
23
+ * inside it is measured back from that: the track's own edge, the ring of
24
+ * ground around the thumb, and the thumb. A segmented control is put in
25
+ * forms beside a text field and a select, so a family that added its padding
26
+ * to the control height would stand two or six pixels prouder than every
27
+ * other control on the row. `--_inner` is what is left for the thumb, and
28
+ * the segments take it.
29
+ *
30
+ * ── The thumb's place is arithmetic ───────────────────────────────────
31
+ *
32
+ * The track is a grid whose columns are equal, so the widest label decides
33
+ * the width of every segment — which is Carbon's stated rule, taken from the
34
+ * layout rather than measured. The thumb is one column wide and moves by its
35
+ * own width times the checked index, so nothing is read from layout and the
36
+ * first server-rendered paint is already right. Mantine measures the checked
37
+ * node with a ref and Ark's machine measures too; both paint the thumb in
38
+ * the wrong place until hydration.
39
+ *
40
+ * `--slotted-segmented-control-count` and `-index` are runtime values under
41
+ * the name that says so, which is the convention `docs/build-briefing.md`
42
+ * sets. They are not design tokens and no theme writes them.
43
+ */
44
+ [data-ui='segmented-control'][data-part='root'] {
45
+ --_count: var(--slotted-segmented-control-count, 1);
46
+ --_index: var(--slotted-segmented-control-index, 0);
47
+ --_height: var(--slotted-control-height-md, 34px);
48
+ --_padding-inline: var(--slotted-control-padding-inline-md, 13px);
49
+ --_font-size: var(--slotted-control-font-size-md, 13px);
50
+ --_icon-size: var(--slotted-control-icon-size-md, 16px);
51
+ --_gap: var(--slotted-control-gap-md, 8px);
52
+ --_radius: var(--slotted-segmented-control-radius, var(--slotted-control-radius, 4px));
53
+ --_border-width: var(--slotted-control-border-width, 1px);
54
+ /**
55
+ * The ring of track left visible around the thumb. It is what says the
56
+ * thumb sits *in* the control rather than being the control, and it is one
57
+ * step on the space scale rather than a number chosen here.
58
+ */
59
+ --_inset: var(--slotted-segmented-control-inset, var(--slotted-space-1, 0.125rem));
60
+ /** What is left for a segment once the track has taken its edge and ring. */
61
+ --_inner: calc(var(--_height) - (var(--_inset) + var(--_border-width)) * 2);
62
+ /** The thumb's corner, kept concentric with the track's own. */
63
+ --_thumb-radius: max(calc(var(--_radius) - var(--_inset)), var(--slotted-radius-xs, 2px));
64
+ /**
65
+ * How long the thumb takes to cross. One step above the control's own
66
+ * interaction feedback, because a thing that travels needs longer than a
67
+ * thing that changes colour, and every value on the scale collapses to a
68
+ * single frame under `prefers-reduced-motion`.
69
+ */
70
+ --_travel: var(--slotted-duration-medium);
71
+
72
+ /* A control in a row of controls: the fill, edge and lift a field has. */
73
+ background: var(--slotted-segmented-control-background, var(--slotted-control-surface, Field));
74
+ border: var(--_border-width) solid
75
+ var(
76
+ --slotted-segmented-control-border-color,
77
+ var(--slotted-control-border-color, ButtonBorder)
78
+ );
79
+ border-radius: var(--_radius);
80
+ box-shadow: var(--slotted-segmented-control-shadow, var(--slotted-control-shadow, none));
81
+ box-sizing: border-box;
82
+ display: inline-grid;
83
+ font-family: var(--slotted-control-font-family, system-ui, sans-serif);
84
+ font-size: var(--_font-size);
85
+ /**
86
+ * Equal columns, sized by the widest segment. `1fr` in an intrinsically
87
+ * sized grid resolves every track to the largest content contribution, so
88
+ * the rule Carbon states — base the width on the longest label, and give
89
+ * every segment that width — falls out of the layout.
90
+ */
91
+ grid-auto-columns: minmax(0, 1fr);
92
+ grid-auto-flow: column;
93
+ isolation: isolate;
94
+ min-inline-size: 0;
95
+ padding: var(--_inset);
96
+ position: relative;
97
+ }
98
+
99
+ [data-ui='segmented-control'][data-part='root']:empty {
100
+ display: none;
101
+ }
102
+
103
+ /* ── Size ─────────────────────────────────────────────────────────────── */
104
+
105
+ [data-ui='segmented-control'][data-part='root'][data-size='sm'] {
106
+ --_height: var(--slotted-control-height-sm, 30px);
107
+ --_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
108
+ --_font-size: var(--slotted-control-font-size-sm, 12px);
109
+ --_icon-size: var(--slotted-control-icon-size-sm, 14px);
110
+ --_gap: var(--slotted-control-gap-sm, 6px);
111
+ }
112
+
113
+ [data-ui='segmented-control'][data-part='root'][data-size='lg'] {
114
+ --_height: var(--slotted-control-height-lg, 38px);
115
+ --_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
116
+ --_font-size: var(--slotted-control-font-size-lg, 14px);
117
+ --_icon-size: var(--slotted-control-icon-size-lg, 18px);
118
+ --_gap: var(--slotted-control-gap-lg, 8px);
119
+ }
120
+
121
+ /* ── Orientation and width ────────────────────────────────────────────── */
122
+
123
+ [data-ui='segmented-control'][data-part='root'][data-orientation='vertical'] {
124
+ grid-auto-columns: auto;
125
+ grid-auto-flow: row;
126
+ grid-auto-rows: minmax(0, 1fr);
127
+ }
128
+
129
+ [data-ui='segmented-control'][data-part='root'][data-full-width] {
130
+ display: grid;
131
+ inline-size: 100%;
132
+ }
133
+
134
+ /**
135
+ * ── Who is the query container, and why it is not always the root ─────
136
+ *
137
+ * `container-type: inline-size` contains the inline axis, so the element's
138
+ * own width is computed as though it had no contents — and a control that
139
+ * shrinks to its labels then shrinks to nothing. Measured: every
140
+ * content-width scene on this page stood about one character wide and broke
141
+ * its labels one letter per line, which is the same defect
142
+ * `docs/build-briefing.md` records for `collapsible`, `pagination` and
143
+ * `stepper`, arriving from the other direction.
144
+ *
145
+ * So the container is declared on the one shape that has a width of its own
146
+ * to be asked about: a control told to condense is a control whose width its
147
+ * parent decides, and it takes that width. Asking a control to give up its
148
+ * words at a width it chose for itself is a question with no answer — it
149
+ * chose a width that fits them.
150
+ */
151
+ [data-ui='segmented-control'][data-part='root'][data-condense='icons'] {
152
+ container-name: slotted-segmented-control;
153
+ container-type: inline-size;
154
+ display: grid;
155
+ inline-size: 100%;
156
+ }
157
+
158
+ /* ── The segment ──────────────────────────────────────────────────────── */
159
+
160
+ [data-ui='segmented-control'] [data-part='item'] {
161
+ align-items: center;
162
+ block-size: var(--_inner);
163
+ border-radius: var(--_thumb-radius);
164
+ box-sizing: border-box;
165
+ /**
166
+ * A segment that is not checked is still a live control, so it takes the
167
+ * page's own ink rather than the muted step. The muted step was tried
168
+ * first and it put an available segment and an unavailable one within a
169
+ * hair of each other — `#5b6b7f` against `#64748b` — so the one state a
170
+ * reader has to be able to tell apart was the one they could not.
171
+ */
172
+ color: var(--slotted-segmented-control-color, var(--slotted-text, ButtonText));
173
+ cursor: pointer;
174
+ display: flex;
175
+ font-weight: var(--slotted-control-font-weight, 600);
176
+ gap: var(--_gap);
177
+ justify-content: center;
178
+ letter-spacing: var(--slotted-control-letter-spacing, 0);
179
+ line-height: var(--slotted-control-line-height, 1);
180
+ /**
181
+ * The thumb is shorter than the control it lives in, so at the smallest
182
+ * step and the tightest density it is eighteen pixels tall. The WCAG
183
+ * minimum is the floor across the segment, and the radio below reaches the
184
+ * track's own edge to meet it in the other axis.
185
+ */
186
+ min-inline-size: max(var(--_inner), var(--slotted-target-min, 24px));
187
+ padding-inline: var(--_padding-inline);
188
+ position: relative;
189
+ transition:
190
+ background-color var(--slotted-control-transition-duration)
191
+ var(--slotted-control-transition-easing),
192
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
193
+ /* Above the thumb, which is painted behind every segment. */
194
+ z-index: 1;
195
+ }
196
+
197
+ /* An empty label is no label: a binding that always renders the span must
198
+ not leave a flex gap after the icon of a segment without words. */
199
+ [data-ui='segmented-control'] [data-part='item']:not(:has([data-part='label']:not(:empty))) {
200
+ padding-inline: 0;
201
+ }
202
+
203
+ [data-ui='segmented-control'] [data-part='label']:empty {
204
+ display: none;
205
+ }
206
+
207
+ /**
208
+ * ── The rule between two segments ─────────────────────────────────────
209
+ *
210
+ * A track of words with nothing between them reads as a sentence. The rule
211
+ * is what says the control is divided, and it is what Mantine ships on by
212
+ * default, Apple draws, and Carbon draws. It is shorter than the segment, so
213
+ * it reads as a divider rather than as a second frame, and it gives way
214
+ * around the thumb — a grey line touching the accent edge is the "border
215
+ * inside a border" this family had to correct elsewhere.
216
+ */
217
+ [data-ui='segmented-control'] [data-part='item'] + [data-part='item']::before {
218
+ /* No system colour behind it: an unthemed decoration disappears rather
219
+ than shouting, which is what `fallbacks.test.mjs` holds a pseudo-element
220
+ to. The rule is a decoration — what divides the segments in forced
221
+ colours is the thumb's own border. */
222
+ background: var(--slotted-segmented-control-divider-color, var(--slotted-border-subtle));
223
+ content: '';
224
+ inline-size: var(--_border-width);
225
+ inset-block: 22%;
226
+ inset-inline-start: 0;
227
+ position: absolute;
228
+ transition: opacity var(--_travel) var(--slotted-easing-standard);
229
+ }
230
+
231
+ [data-ui='segmented-control'][data-part='root'][data-orientation='vertical']
232
+ [data-part='item']
233
+ + [data-part='item']::before {
234
+ block-size: var(--_border-width);
235
+ inline-size: auto;
236
+ inset-block: auto;
237
+ inset-block-start: 0;
238
+ inset-inline: 22%;
239
+ }
240
+
241
+ [data-ui='segmented-control'] [data-part='item'][data-checked]::before,
242
+ [data-ui='segmented-control'] [data-part='item'][data-checked] + [data-part='item']::before {
243
+ opacity: 0;
244
+ }
245
+
246
+ /**
247
+ * The radio itself, carrying the focus the platform gives it and the whole
248
+ * hit area of the segment. It reaches past the thumb and over the ring of
249
+ * track around it, so the target is the control's own height rather than the
250
+ * thumb's — which is what keeps the smallest step at the tightest density
251
+ * above the WCAG minimum.
252
+ */
253
+ [data-ui='segmented-control'] [data-part='control'] {
254
+ appearance: none;
255
+ background: none;
256
+ border: 0;
257
+ border-radius: inherit;
258
+ cursor: inherit;
259
+ inset-block: calc(-1 * (var(--_inset) + var(--_border-width)));
260
+ inset-inline: 0;
261
+ margin: 0;
262
+ opacity: 0;
263
+ padding: 0;
264
+ position: absolute;
265
+ }
266
+
267
+ [data-ui='segmented-control'] [data-part='icon'] {
268
+ block-size: var(--_icon-size);
269
+ display: inline-flex;
270
+ flex: none;
271
+ inline-size: var(--_icon-size);
272
+ }
273
+
274
+ /**
275
+ * The words get a box of their own, so a label an equal column made too
276
+ * narrow clips rather than wrapping the segment onto a second line. The
277
+ * string stays whole in the DOM: truncation never removes content.
278
+ */
279
+ [data-ui='segmented-control'] [data-part='label'] {
280
+ overflow: hidden;
281
+ text-overflow: ellipsis;
282
+ white-space: nowrap;
283
+ }
284
+
285
+ /**
286
+ * ── The thumb ─────────────────────────────────────────────────────────
287
+ *
288
+ * One column wide, moved by its own width times the checked index. The
289
+ * percentage is of the thumb itself, so the arithmetic needs no width in it
290
+ * and no measurement behind it; `:dir(rtl)` negates the step, because the
291
+ * reading direction is which way "the next segment" is.
292
+ *
293
+ * The edge is a border rather than an inset ring, so the same declaration
294
+ * leaves room for the elevation beside it: a framed control carries
295
+ * `--slotted-control-shadow`, which is `none` in `high-contrast` — and a
296
+ * shadow list may not contain `none`, so the two could not have shared one
297
+ * `box-shadow`.
298
+ *
299
+ * The movement is the one thing in this family a reader's eye follows: it
300
+ * says which segment was left, which a fill appearing in a new place does
301
+ * not. It takes a step above the control's own transition, because a thing
302
+ * that travels needs longer than a thing that changes colour, and the theme
303
+ * collapses every step to a single frame under `prefers-reduced-motion`.
304
+ *
305
+ * The thumb is absent while nothing is checked, so the first selection fades
306
+ * it in where it belongs rather than sliding it from an edge the reader has
307
+ * never seen — which is what Mantine does.
308
+ */
309
+ [data-ui='segmented-control'] [data-part='indicator'] {
310
+ background: var(
311
+ --slotted-segmented-control-indicator-background,
312
+ var(--slotted-tone-accent-subtle-active, Highlight)
313
+ );
314
+ block-size: calc(100% - var(--_inset) * 2);
315
+ border: var(--_border-width) solid
316
+ var(
317
+ --slotted-segmented-control-indicator-border-color,
318
+ var(--slotted-tone-accent-border, Highlight)
319
+ );
320
+ border-radius: var(--_thumb-radius);
321
+ box-shadow: var(--slotted-control-shadow, none);
322
+ box-sizing: border-box;
323
+ inline-size: calc((100% - var(--_inset) * 2) / var(--_count));
324
+ inset-block-start: var(--_inset);
325
+ inset-inline-start: var(--_inset);
326
+ pointer-events: none;
327
+ position: absolute;
328
+ transition:
329
+ opacity var(--slotted-control-transition-duration) var(--slotted-easing-standard),
330
+ translate var(--_travel) var(--slotted-easing-standard);
331
+ translate: calc(var(--_index) * 100%);
332
+ z-index: 0;
333
+
334
+ @starting-style {
335
+ opacity: 0;
336
+ }
337
+ }
338
+
339
+ [data-ui='segmented-control'] [data-part='indicator']:dir(rtl) {
340
+ translate: calc(var(--_index) * -100%);
341
+ }
342
+
343
+ [data-ui='segmented-control'][data-part='root'][data-orientation='vertical']
344
+ [data-part='indicator'] {
345
+ block-size: calc((100% - var(--_inset) * 2) / var(--_count));
346
+ inline-size: calc(100% - var(--_inset) * 2);
347
+ translate: 0 calc(var(--_index) * 100%);
348
+ }
349
+
350
+ /**
351
+ * ── Checked ──────────────────────────────────────────────────────────
352
+ *
353
+ * The thumb carries the ground and the edge; the segment carries the text.
354
+ * Nothing about the segment's geometry changes — the weight stays where it
355
+ * was, because a label that thickens when it is chosen moves every label
356
+ * beside it.
357
+ */
358
+ [data-ui='segmented-control'] [data-part='item'][data-checked] {
359
+ color: var(
360
+ --slotted-segmented-control-checked-color,
361
+ var(--slotted-tone-accent-text, HighlightText)
362
+ );
363
+ }
364
+
365
+ /* ── Hover and press, held back for every stronger state ──────────────── */
366
+
367
+ /**
368
+ * A segment that is not checked answers the pointer with a ground rather
369
+ * than with a shade of its own ink. Ink alone was the first attempt: with
370
+ * the unchecked label already at the page's own colour there was nowhere
371
+ * darker for it to go, and a hover that changes nothing is a hover a reader
372
+ * cannot see. The ground is the neutral one, for the reason `button`
373
+ * records — a tinted hover on a row of choices reads as a row of status
374
+ * chips before it reads as one of them being pointed at.
375
+ */
376
+ [data-ui='segmented-control'][data-part='root']:not([data-readonly])
377
+ [data-part='item']:hover:not([data-disabled]):not([data-checked]):has(
378
+ [data-part='control']:enabled
379
+ ) {
380
+ background: var(
381
+ --slotted-segmented-control-hover-background,
382
+ var(--slotted-tone-secondary-subtle-hover, ButtonFace)
383
+ );
384
+ }
385
+
386
+ /**
387
+ * A press answers on the frame it went down on and releases at the control's
388
+ * own pace, which is the rule every activating family here writes: the
389
+ * duration belongs to the state being entered.
390
+ */
391
+ [data-ui='segmented-control'][data-part='root']:not([data-readonly])
392
+ [data-part='item']:active:not([data-disabled]):not([data-checked]):has(
393
+ [data-part='control']:enabled
394
+ ) {
395
+ background: var(
396
+ --slotted-segmented-control-active-background,
397
+ var(--slotted-tone-secondary-subtle-active, ButtonFace)
398
+ );
399
+ transition-duration: var(--slotted-duration-instant);
400
+ }
401
+
402
+ /* ── Focus ────────────────────────────────────────────────────────────── */
403
+
404
+ /**
405
+ * The element holding focus is the radio, and the ring belongs around the
406
+ * segment. A family that moves its ring has to put out the one the browser
407
+ * draws where focus actually is, or it paints two rings in two colours —
408
+ * the rule `checkbox`, `switch` and `radio-group` each record.
409
+ *
410
+ * The ring is drawn *inside* the segment, with its outer edge on the
411
+ * segment's own border box — which is what a negative offset of exactly the
412
+ * ring's width gives. An outline is painted outward, and the segments are
413
+ * flush inside a track that keeps only two pixels of ground around them, so
414
+ * an outward ring has nowhere of its own to go: at offset zero it crossed
415
+ * into the neighbour and stacked with that neighbour's thumb edge, which
416
+ * read as three pixels of ink at one seam and one pixel at every other.
417
+ * Measured at 300% zoom, between a focused segment and the checked one
418
+ * beside it.
419
+ *
420
+ * Inside, the ring lands exactly where the thumb's edge lands and replaces
421
+ * it, so a segment that is both checked and focused shows one ring rather
422
+ * than a ring and a border a pixel apart.
423
+ */
424
+ [data-ui='segmented-control'] [data-part='item']:has([data-part='control']:focus-visible) {
425
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
426
+ outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
427
+ z-index: 2;
428
+ }
429
+
430
+ [data-ui='segmented-control'] [data-part='control']:focus-visible {
431
+ outline: none;
432
+ }
433
+
434
+ /* ── Invalid ──────────────────────────────────────────────────────────── */
435
+
436
+ /**
437
+ * The ring is retargeted on the element rather than repainted in every rule
438
+ * that draws one, which is what `--slotted-focus-ring-color-invalid` exists
439
+ * for and what keeps the outline and its shadow the same colour.
440
+ */
441
+ [data-ui='segmented-control'][data-part='root'][data-invalid] {
442
+ --slotted-focus-ring-color: var(
443
+ --slotted-segmented-control-invalid-focus-ring-color,
444
+ var(--slotted-focus-ring-color-invalid, Highlight)
445
+ );
446
+
447
+ border-color: var(
448
+ --slotted-segmented-control-invalid-border-color,
449
+ var(--slotted-tone-danger-border, ButtonBorder)
450
+ );
451
+ /*
452
+ * Width as well as colour (WCAG 1.4.1), the edge `text-field` draws: in
453
+ * `high-contrast` the danger border is the resting edge's black. The line
454
+ * is a shadow inside the border, so neither the track nor a segment moves,
455
+ * and it takes the resting lift's place. Focus is the segment's ring, so
456
+ * no focus rule repaints it.
457
+ */
458
+ --_invalid-edge-width: var(
459
+ --slotted-segmented-control-invalid-edge-width,
460
+ var(--slotted-control-invalid-edge-width, 1px)
461
+ );
462
+
463
+ box-shadow: inset 0 0 0 var(--_invalid-edge-width)
464
+ var(
465
+ --slotted-segmented-control-invalid-border-color,
466
+ var(--slotted-tone-danger-border, ButtonBorder)
467
+ );
468
+ }
469
+
470
+ /* ── Read-only ────────────────────────────────────────────────────────── */
471
+
472
+ /**
473
+ * A read-only control keeps its colours and loses its cursor: it is still
474
+ * legible, still focusable and still copyable, and it must not be mistaken
475
+ * for a disabled one — which is what `docs/component-features.md` §2.5 asks.
476
+ */
477
+ [data-ui='segmented-control'][data-part='root'][data-readonly] [data-part='item'],
478
+ [data-ui='segmented-control'][data-part='root'][data-readonly] [data-part='control'] {
479
+ cursor: default;
480
+ }
481
+
482
+ /* ── Disabled ─────────────────────────────────────────────────────────── */
483
+
484
+ [data-ui='segmented-control'] [data-part='item'][data-disabled] {
485
+ color: var(
486
+ --slotted-segmented-control-disabled-color,
487
+ var(--slotted-disabled-foreground, GrayText)
488
+ );
489
+ cursor: default;
490
+ }
491
+
492
+ [data-ui='segmented-control'] [data-part='control']:disabled {
493
+ cursor: default;
494
+ }
495
+
496
+ /**
497
+ * A whole control that is unavailable greys its edge and its words and keeps
498
+ * its track. Painting the track in the disabled ground as well was the first
499
+ * attempt, and it made the thumb the same colour as what it sits on: the
500
+ * reader could no longer tell which of the three had been chosen. The track
501
+ * is the ground the thumb is read against, so it is the one thing that has
502
+ * to stay.
503
+ */
504
+ [data-ui='segmented-control'][data-part='root'][data-disabled] {
505
+ border-color: var(
506
+ --slotted-segmented-control-disabled-border-color,
507
+ var(--slotted-disabled-border, GrayText)
508
+ );
509
+ }
510
+
511
+ [data-ui='segmented-control'][data-part='root'][data-disabled] [data-part='indicator'] {
512
+ background: var(
513
+ --slotted-segmented-control-disabled-indicator-background,
514
+ var(--slotted-disabled-background, ButtonFace)
515
+ );
516
+ border-color: var(
517
+ --slotted-segmented-control-disabled-border-color,
518
+ var(--slotted-disabled-border, GrayText)
519
+ );
520
+ box-shadow: none;
521
+ }
522
+
523
+ /* ── Condensed ────────────────────────────────────────────────────────── */
524
+
525
+ /**
526
+ * Below the width its labels need, a control asked to condense keeps its
527
+ * icons and hides its words — Primer's `hideLabels`, asked of the control's
528
+ * own inline size rather than of the viewport, because the viewport is not
529
+ * what the control is narrow inside.
530
+ *
531
+ * The words are hidden and not removed, so the accessible name of every
532
+ * segment is the same at every width. A container breakpoint is a literal
533
+ * because `@container (max-inline-size: var(--x))` is invalid CSS.
534
+ */
535
+ @container slotted-segmented-control (max-inline-size: 20rem) {
536
+ [data-ui='segmented-control'][data-part='root'][data-condense='icons']
537
+ [data-part='item']:has([data-part='icon'])
538
+ [data-part='label'] {
539
+ block-size: 1px;
540
+ clip-path: inset(50%);
541
+ inline-size: 1px;
542
+ overflow: hidden;
543
+ position: absolute;
544
+ white-space: nowrap;
545
+ }
546
+
547
+ [data-ui='segmented-control'][data-part='root'][data-condense='icons']
548
+ [data-part='item']:has([data-part='icon']) {
549
+ padding-inline: 0;
550
+ }
551
+ }
552
+
553
+ /*
554
+ * Forced colours remove every box shadow, and the edge with them. The width
555
+ * comes back as an outline inside the border; the
556
+ * ring belongs to a segment and leaves it alone.
557
+ */
558
+ @media (forced-colors: active) {
559
+ [data-ui='segmented-control'][data-part='root'][data-invalid] {
560
+ outline: var(--_invalid-edge-width) solid CanvasText;
561
+ outline-offset: calc(-1 * (var(--_border-width) + var(--_invalid-edge-width)));
562
+ }
563
+ }
564
+ }
@@ -0,0 +1,67 @@
1
+ [
2
+ "--slotted-border-subtle",
3
+ "--slotted-control-border-color",
4
+ "--slotted-control-border-width",
5
+ "--slotted-control-font-family",
6
+ "--slotted-control-font-size-lg",
7
+ "--slotted-control-font-size-md",
8
+ "--slotted-control-font-size-sm",
9
+ "--slotted-control-font-weight",
10
+ "--slotted-control-gap-lg",
11
+ "--slotted-control-gap-md",
12
+ "--slotted-control-gap-sm",
13
+ "--slotted-control-height-lg",
14
+ "--slotted-control-height-md",
15
+ "--slotted-control-height-sm",
16
+ "--slotted-control-icon-size-lg",
17
+ "--slotted-control-icon-size-md",
18
+ "--slotted-control-icon-size-sm",
19
+ "--slotted-control-invalid-edge-width",
20
+ "--slotted-control-letter-spacing",
21
+ "--slotted-control-line-height",
22
+ "--slotted-control-padding-inline-lg",
23
+ "--slotted-control-padding-inline-md",
24
+ "--slotted-control-padding-inline-sm",
25
+ "--slotted-control-radius",
26
+ "--slotted-control-shadow",
27
+ "--slotted-control-surface",
28
+ "--slotted-control-transition-duration",
29
+ "--slotted-control-transition-easing",
30
+ "--slotted-disabled-background",
31
+ "--slotted-disabled-border",
32
+ "--slotted-disabled-foreground",
33
+ "--slotted-duration-instant",
34
+ "--slotted-duration-medium",
35
+ "--slotted-easing-standard",
36
+ "--slotted-focus-ring-color",
37
+ "--slotted-focus-ring-color-invalid",
38
+ "--slotted-focus-ring-width",
39
+ "--slotted-radius-xs",
40
+ "--slotted-segmented-control-active-background",
41
+ "--slotted-segmented-control-background",
42
+ "--slotted-segmented-control-border-color",
43
+ "--slotted-segmented-control-checked-color",
44
+ "--slotted-segmented-control-color",
45
+ "--slotted-segmented-control-disabled-border-color",
46
+ "--slotted-segmented-control-disabled-color",
47
+ "--slotted-segmented-control-disabled-indicator-background",
48
+ "--slotted-segmented-control-divider-color",
49
+ "--slotted-segmented-control-hover-background",
50
+ "--slotted-segmented-control-indicator-background",
51
+ "--slotted-segmented-control-indicator-border-color",
52
+ "--slotted-segmented-control-inset",
53
+ "--slotted-segmented-control-invalid-border-color",
54
+ "--slotted-segmented-control-invalid-edge-width",
55
+ "--slotted-segmented-control-invalid-focus-ring-color",
56
+ "--slotted-segmented-control-radius",
57
+ "--slotted-segmented-control-shadow",
58
+ "--slotted-space-1",
59
+ "--slotted-target-min",
60
+ "--slotted-text",
61
+ "--slotted-tone-accent-border",
62
+ "--slotted-tone-accent-subtle-active",
63
+ "--slotted-tone-accent-text",
64
+ "--slotted-tone-danger-border",
65
+ "--slotted-tone-secondary-subtle-active",
66
+ "--slotted-tone-secondary-subtle-hover"
67
+ ]