@slotted/styles 0.1.0-beta.1 → 0.1.0-beta.10

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 (176) hide show
  1. package/package.json +1 -1
  2. package/src/accordion/accordion.css +10 -10
  3. package/src/action-bar/action-bar.css +31 -32
  4. package/src/action-sheet/action-sheet.css +36 -36
  5. package/src/alert/alert.css +43 -35
  6. package/src/alert/alert.tokens.json +5 -0
  7. package/src/app-shell/app-shell.css +356 -301
  8. package/src/aspect-ratio/aspect-ratio.css +9 -9
  9. package/src/attachment/attachment.css +91 -99
  10. package/src/avatar/avatar.css +71 -41
  11. package/src/avatar/avatar.tokens.json +7 -0
  12. package/src/avatar-group/avatar-group.css +21 -22
  13. package/src/badge/badge.css +45 -37
  14. package/src/badge/badge.tokens.json +5 -0
  15. package/src/breadcrumb/breadcrumb.css +37 -37
  16. package/src/button/button.css +139 -64
  17. package/src/button/button.tokens.json +10 -0
  18. package/src/button-group/button-group.css +76 -72
  19. package/src/button-group/button-group.tokens.json +2 -0
  20. package/src/calendar/calendar.css +93 -103
  21. package/src/card/card.css +54 -61
  22. package/src/carousel/carousel.css +150 -161
  23. package/src/cascader/cascader.css +152 -153
  24. package/src/cascader/cascader.tokens.json +1 -0
  25. package/src/chart/chart.css +242 -239
  26. package/src/chart/chart.tokens.json +1 -0
  27. package/src/checkbox/checkbox.css +119 -124
  28. package/src/checkbox-group/checkbox-group.css +7 -7
  29. package/src/code-block/code-block.css +81 -85
  30. package/src/collapsible/collapsible.css +69 -76
  31. package/src/color-picker/color-picker.css +83 -91
  32. package/src/column-manager/column-manager.css +106 -114
  33. package/src/combobox/combobox.css +132 -107
  34. package/src/combobox/combobox.tokens.json +1 -0
  35. package/src/command-palette/command-palette.css +84 -90
  36. package/src/container/container.css +24 -24
  37. package/src/context-menu/context-menu.css +8 -8
  38. package/src/currency-field/currency-field.css +210 -88
  39. package/src/currency-field/currency-field.tokens.json +13 -0
  40. package/src/dashboard-grid/dashboard-grid.css +148 -116
  41. package/src/data-grid/data-grid.css +504 -469
  42. package/src/data-grid/data-grid.tokens.json +2 -0
  43. package/src/data-list/data-list.css +81 -70
  44. package/src/data-list/data-list.tokens.json +2 -1
  45. package/src/date-field/date-field.css +55 -52
  46. package/src/date-field/date-field.tokens.json +1 -0
  47. package/src/date-picker/date-picker.css +40 -42
  48. package/src/date-range-picker/date-range-picker.css +114 -118
  49. package/src/datetime-field/datetime-field.css +78 -80
  50. package/src/datetime-field/datetime-field.tokens.json +1 -0
  51. package/src/datetime-picker/datetime-picker.css +61 -63
  52. package/src/datetime-range-picker/datetime-range-picker.css +149 -156
  53. package/src/description-list/description-list.css +61 -64
  54. package/src/detail-panel/detail-panel.css +70 -70
  55. package/src/dialog/dialog.css +334 -153
  56. package/src/dialog/dialog.tokens.json +5 -0
  57. package/src/divider/divider.css +18 -18
  58. package/src/drawer/drawer.css +186 -209
  59. package/src/entity-picker/entity-picker.css +238 -241
  60. package/src/entity-picker/entity-picker.tokens.json +1 -0
  61. package/src/event-calendar/event-calendar.css +343 -385
  62. package/src/field/field.css +74 -78
  63. package/src/field-group/field-group.css +22 -25
  64. package/src/filter-bar/filter-bar.css +193 -209
  65. package/src/form/form.css +34 -24
  66. package/src/form/form.tokens.json +1 -0
  67. package/src/fullscreen/fullscreen.css +24 -25
  68. package/src/grid/grid.css +28 -28
  69. package/src/highlight/highlight.css +6 -6
  70. package/src/hover-card/hover-card.css +6 -6
  71. package/src/icon/icon.css +19 -15
  72. package/src/icon/icon.tokens.json +1 -0
  73. package/src/image/image.css +39 -39
  74. package/src/import-export-center/import-export-center.css +135 -156
  75. package/src/inline/inline.css +27 -27
  76. package/src/inline-message/inline-message.css +52 -28
  77. package/src/inline-message/inline-message.tokens.json +4 -0
  78. package/src/kbd/kbd.css +30 -26
  79. package/src/link/link.css +15 -15
  80. package/src/list/list.css +44 -17
  81. package/src/list/list.tokens.json +4 -0
  82. package/src/list-item/list-item.css +80 -66
  83. package/src/list-item/list-item.tokens.json +7 -0
  84. package/src/listbox/listbox.css +56 -59
  85. package/src/loading-overlay/loading-overlay.css +75 -92
  86. package/src/markdown/markdown.css +17 -17
  87. package/src/menu/menu.css +83 -63
  88. package/src/menubar/menubar.css +13 -13
  89. package/src/message/message.css +182 -202
  90. package/src/message/message.tokens.json +5 -0
  91. package/src/message-scroller/message-scroller.css +41 -42
  92. package/src/multi-select/multi-select.css +152 -145
  93. package/src/multi-select/multi-select.tokens.json +1 -0
  94. package/src/navigation-menu/navigation-menu.css +167 -188
  95. package/src/notification-center/notification-center.css +140 -148
  96. package/src/notification-center/notification-center.tokens.json +3 -0
  97. package/src/number-field/number-field.css +84 -83
  98. package/src/number-field/number-field.tokens.json +1 -0
  99. package/src/otp-input/otp-input.css +67 -57
  100. package/src/otp-input/otp-input.tokens.json +1 -0
  101. package/src/page/page.css +23 -23
  102. package/src/page-header/page-header.css +268 -281
  103. package/src/page-header/page-header.tokens.json +1 -0
  104. package/src/pagination/pagination.css +68 -72
  105. package/src/popover/popover.css +40 -49
  106. package/src/progress-indicator/progress-indicator.css +177 -120
  107. package/src/progress-indicator/progress-indicator.tokens.json +1 -1
  108. package/src/prompt-composer/prompt-composer.css +221 -102
  109. package/src/prompt-composer/prompt-composer.tokens.json +15 -0
  110. package/src/prose/prose.css +71 -75
  111. package/src/radio-group/radio-group.css +98 -103
  112. package/src/rating/rating.css +136 -132
  113. package/src/rating/rating.tokens.json +1 -0
  114. package/src/result-state/result-state.css +74 -77
  115. package/src/runtime-properties.mjs +2 -0
  116. package/src/saved-views/saved-views.css +41 -47
  117. package/src/scroll-area/scroll-area.css +39 -32
  118. package/src/section/section.css +161 -41
  119. package/src/section/section.tokens.json +7 -2
  120. package/src/segmented-control/segmented-control.css +254 -97
  121. package/src/segmented-control/segmented-control.tokens.json +15 -11
  122. package/src/select/select.css +118 -117
  123. package/src/select/select.tokens.json +1 -0
  124. package/src/side-navigation/side-navigation.css +179 -182
  125. package/src/side-navigation/side-navigation.tokens.json +1 -0
  126. package/src/skeleton/skeleton.css +47 -48
  127. package/src/slider/slider.css +86 -88
  128. package/src/sparkline/sparkline.css +62 -40
  129. package/src/sparkline/sparkline.tokens.json +2 -0
  130. package/src/spinner/spinner.css +39 -182
  131. package/src/spinner/spinner.tokens.json +0 -2
  132. package/src/split-button/split-button.css +30 -31
  133. package/src/split-button/split-button.tokens.json +1 -0
  134. package/src/splitter/splitter.css +35 -35
  135. package/src/stack/stack.css +24 -24
  136. package/src/stat-card/stat-card.css +341 -224
  137. package/src/stat-card/stat-card.tokens.json +3 -0
  138. package/src/status-indicator/status-indicator.css +28 -28
  139. package/src/stepper/stepper.css +58 -62
  140. package/src/switch/switch.css +86 -92
  141. package/src/table/table.css +310 -165
  142. package/src/table/table.tokens.json +6 -0
  143. package/src/table-of-contents/table-of-contents.css +51 -54
  144. package/src/tabs/tabs.css +330 -302
  145. package/src/tabs/tabs.tokens.json +8 -5
  146. package/src/tag/tag.css +79 -71
  147. package/src/tag/tag.tokens.json +9 -0
  148. package/src/tags-input/tags-input.css +145 -82
  149. package/src/tags-input/tags-input.tokens.json +7 -1
  150. package/src/text-field/text-field.css +132 -136
  151. package/src/text-field/text-field.tokens.json +1 -0
  152. package/src/textarea/textarea.css +31 -28
  153. package/src/textarea/textarea.tokens.json +1 -0
  154. package/src/time-field/time-field.css +72 -69
  155. package/src/time-field/time-field.tokens.json +1 -0
  156. package/src/time-picker/time-picker.css +56 -58
  157. package/src/time-range-picker/time-range-picker.css +126 -133
  158. package/src/timeline/timeline.css +254 -266
  159. package/src/timeline/timeline.tokens.json +5 -0
  160. package/src/toast/toast.css +89 -90
  161. package/src/toast/toast.tokens.json +5 -0
  162. package/src/toggle/toggle.css +62 -25
  163. package/src/toggle/toggle.tokens.json +8 -0
  164. package/src/toggle-group/toggle-group.css +106 -60
  165. package/src/toggle-group/toggle-group.tokens.json +10 -0
  166. package/src/toolbar/toolbar.css +30 -35
  167. package/src/tooltip/tooltip.css +26 -26
  168. package/src/top-navigation/top-navigation.css +243 -259
  169. package/src/tour/tour.css +51 -60
  170. package/src/transfer/transfer.css +160 -164
  171. package/src/tree/tree.css +103 -111
  172. package/src/typography/typography.css +40 -36
  173. package/src/typography/typography.tokens.json +1 -0
  174. package/src/upload/upload.css +224 -224
  175. package/src/visually-hidden/visually-hidden.css +1 -1
  176. package/src/wizard/wizard.css +185 -197
@@ -41,7 +41,7 @@
41
41
  * the name that says so, which is the convention `docs/build-briefing.md`
42
42
  * sets. They are not design tokens and no theme writes them.
43
43
  */
44
- [data-ui='segmented-control'][data-part='root'] {
44
+ [data-ui-segmented-control][data-part-root] {
45
45
  --_count: var(--slotted-segmented-control-count, 1);
46
46
  --_index: var(--slotted-segmented-control-index, 0);
47
47
  --_height: var(--slotted-control-height-md, 34px);
@@ -59,6 +59,8 @@
59
59
  --_inset: var(--slotted-segmented-control-inset, var(--slotted-space-1, 0.125rem));
60
60
  /** What is left for a segment once the track has taken its edge and ring. */
61
61
  --_inner: calc(var(--_height) - (var(--_inset) + var(--_border-width)) * 2);
62
+ /** The space between two segments: the ring's own step. */
63
+ --_spacing: var(--slotted-segmented-control-gap, var(--_inset));
62
64
  /** The thumb's corner, kept concentric with the track's own. */
63
65
  --_thumb-radius: max(calc(var(--_radius) - var(--_inset)), var(--slotted-radius-xs, 2px));
64
66
  /**
@@ -69,15 +71,22 @@
69
71
  */
70
72
  --_travel: var(--slotted-duration-medium);
71
73
 
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
- );
74
+ /**
75
+ * A neutral track: the page's own ink at a low alpha rather than an opaque
76
+ * fill, so it darkens whatever it stands on — a surface, a sunken panel, a
77
+ * card header — by the same step, and reads as a recess in each. The edge
78
+ * keeps its width and is transparent by default, so the arithmetic below is
79
+ * the same with a theme's edge and without one. An accent track with an
80
+ * accent thumb edge was the first appearance: a coloured outline on a row
81
+ * of neutral controls read as an alert before it read as a choice.
82
+ */
83
+ background: var(
84
+ --slotted-segmented-control-background,
85
+ color-mix(in oklab, var(--slotted-text, CanvasText) 6%, transparent)
86
+ );
87
+ border: var(--_border-width) solid var(--slotted-segmented-control-border-color, transparent);
79
88
  border-radius: var(--_radius);
80
- box-shadow: var(--slotted-segmented-control-shadow, var(--slotted-control-shadow, none));
89
+ box-shadow: var(--slotted-segmented-control-shadow, none);
81
90
  box-sizing: border-box;
82
91
  display: inline-grid;
83
92
  font-family: var(--slotted-control-font-family, system-ui, sans-serif);
@@ -87,8 +96,15 @@
87
96
  * sized grid resolves every track to the largest content contribution, so
88
97
  * the rule Carbon states — base the width on the longest label, and give
89
98
  * every segment that width — falls out of the layout.
99
+ *
100
+ * The column carries the segment's floor, and the segment none of its own:
101
+ * a segment with a minimum inside a column that could shrink below it ran
102
+ * past its column in a narrow container, while the thumb, a column wide,
103
+ * did not — the thumb and its segment no longer matched, and the label
104
+ * overflowed the track.
90
105
  */
91
- grid-auto-columns: minmax(0, 1fr);
106
+ gap: var(--_spacing);
107
+ grid-auto-columns: minmax(max(var(--_inner), var(--slotted-target-min, 24px)), 1fr);
92
108
  grid-auto-flow: column;
93
109
  isolation: isolate;
94
110
  min-inline-size: 0;
@@ -96,13 +112,13 @@
96
112
  position: relative;
97
113
  }
98
114
 
99
- [data-ui='segmented-control'][data-part='root']:empty {
115
+ [data-ui-segmented-control][data-part-root]:empty {
100
116
  display: none;
101
117
  }
102
118
 
103
119
  /* ── Size ─────────────────────────────────────────────────────────────── */
104
120
 
105
- [data-ui='segmented-control'][data-part='root'][data-size='sm'] {
121
+ [data-ui-segmented-control][data-part-root][data-size='sm'] {
106
122
  --_height: var(--slotted-control-height-sm, 30px);
107
123
  --_padding-inline: var(--slotted-control-padding-inline-sm, 10px);
108
124
  --_font-size: var(--slotted-control-font-size-sm, 12px);
@@ -110,7 +126,7 @@
110
126
  --_gap: var(--slotted-control-gap-sm, 6px);
111
127
  }
112
128
 
113
- [data-ui='segmented-control'][data-part='root'][data-size='lg'] {
129
+ [data-ui-segmented-control][data-part-root][data-size='lg'] {
114
130
  --_height: var(--slotted-control-height-lg, 38px);
115
131
  --_padding-inline: var(--slotted-control-padding-inline-lg, 16px);
116
132
  --_font-size: var(--slotted-control-font-size-lg, 14px);
@@ -120,13 +136,13 @@
120
136
 
121
137
  /* ── Orientation and width ────────────────────────────────────────────── */
122
138
 
123
- [data-ui='segmented-control'][data-part='root'][data-orientation='vertical'] {
139
+ [data-ui-segmented-control][data-part-root][data-orientation='vertical'] {
124
140
  grid-auto-columns: auto;
125
141
  grid-auto-flow: row;
126
142
  grid-auto-rows: minmax(0, 1fr);
127
143
  }
128
144
 
129
- [data-ui='segmented-control'][data-part='root'][data-full-width] {
145
+ [data-ui-segmented-control][data-part-root][data-full-width] {
130
146
  display: grid;
131
147
  inline-size: 100%;
132
148
  }
@@ -148,7 +164,7 @@
148
164
  * words at a width it chose for itself is a question with no answer — it
149
165
  * chose a width that fits them.
150
166
  */
151
- [data-ui='segmented-control'][data-part='root'][data-condense='icons'] {
167
+ [data-ui-segmented-control][data-part-root][data-condense='icons'] {
152
168
  container-name: slotted-segmented-control;
153
169
  container-type: inline-size;
154
170
  display: grid;
@@ -157,7 +173,7 @@
157
173
 
158
174
  /* ── The segment ──────────────────────────────────────────────────────── */
159
175
 
160
- [data-ui='segmented-control'] [data-part='item'] {
176
+ [data-ui-segmented-control] [data-part-item] {
161
177
  align-items: center;
162
178
  block-size: var(--_inner);
163
179
  border-radius: var(--_thumb-radius);
@@ -172,7 +188,10 @@
172
188
  color: var(--slotted-segmented-control-color, var(--slotted-text, ButtonText));
173
189
  cursor: pointer;
174
190
  display: flex;
175
- font-weight: var(--slotted-control-font-weight, 600);
191
+ font-weight: var(
192
+ --slotted-segmented-control-font-weight,
193
+ var(--slotted-font-weight-medium, 500)
194
+ );
176
195
  gap: var(--_gap);
177
196
  justify-content: center;
178
197
  letter-spacing: var(--slotted-control-letter-spacing, 0);
@@ -180,66 +199,95 @@
180
199
  /**
181
200
  * The thumb is shorter than the control it lives in, so at the smallest
182
201
  * 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.
202
+ * minimum is the column's floor across the segment, and the radio below
203
+ * reaches the track's own edge to meet it in the other axis.
185
204
  */
186
- min-inline-size: max(var(--_inner), var(--slotted-target-min, 24px));
205
+ min-inline-size: 0;
187
206
  padding-inline: var(--_padding-inline);
207
+ /**
208
+ * Positioned without a `z-index`: the segment paints after the thumb, which
209
+ * comes first in the DOM, so its words lie over the thumb, and its divider
210
+ * and its hover ground — pseudo-elements at `z-index: -1` — lie under it.
211
+ * A segment at `z-index: 1` lifted both over the thumb as well, and a thumb
212
+ * crossing a hovered segment or a divider lost part of its edge for the
213
+ * length of the slide.
214
+ */
188
215
  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;
216
+ transition: color var(--slotted-control-transition-duration)
217
+ var(--slotted-control-transition-easing);
218
+ }
219
+
220
+ /** The hover and press ground, under the thumb; see the segment above. */
221
+ [data-ui-segmented-control] [data-part-item]::after {
222
+ background: transparent;
223
+ border-radius: inherit;
224
+ content: '';
225
+ inset: 0;
226
+ position: absolute;
227
+ transition: background-color var(--slotted-control-transition-duration)
228
+ var(--slotted-control-transition-easing);
229
+ z-index: -1;
195
230
  }
196
231
 
197
232
  /* An empty label is no label: a binding that always renders the span must
198
233
  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))) {
234
+ [data-ui-segmented-control] [data-part-item]:not(:has([data-part-label]:not(:empty))) {
200
235
  padding-inline: 0;
201
236
  }
202
237
 
203
- [data-ui='segmented-control'] [data-part='label']:empty {
238
+ [data-ui-segmented-control] [data-part-label]:empty {
204
239
  display: none;
205
240
  }
206
241
 
207
242
  /**
208
- * ── The rule between two segments ─────────────────────────────────────
243
+ * ── Between two segments ──────────────────────────────────────────────
244
+ *
245
+ * Segments stand apart by the same step as the ring of track around them,
246
+ * so each one — its hover ground, the thumb on it — is a shape of its own
247
+ * on the track, and a rule in the middle of that room says the control is
248
+ * divided: a track of words with nothing between them reads as a sentence.
209
249
  *
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.
250
+ * The rule is centred in the gap rather than on a segment's edge, half the
251
+ * segment's height, rounded at its ends, in a light tint of the ink rather
252
+ * than an opaque border colour. It gives way wherever it would touch a
253
+ * shape: beside the thumb, and beside the segment under the pointer, whose
254
+ * ground would otherwise meet it. It lies under the thumb, so a thumb that
255
+ * crosses it while sliding passes over it.
216
256
  */
217
- [data-ui='segmented-control'] [data-part='item'] + [data-part='item']::before {
257
+ [data-ui-segmented-control] [data-part-item] + [data-part-item]::before {
218
258
  /* No system colour behind it: an unthemed decoration disappears rather
219
259
  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));
260
+ to. In forced colours the thumb's own edge is what divides. */
261
+ background: var(
262
+ --slotted-segmented-control-divider-color,
263
+ color-mix(in oklab, var(--slotted-text) 14%, transparent)
264
+ );
265
+ border-radius: var(--slotted-radius-full, 9999px);
223
266
  content: '';
224
267
  inline-size: var(--_border-width);
225
- inset-block: 22%;
226
- inset-inline-start: 0;
268
+ inset-block: 25%;
269
+ inset-inline-start: calc((var(--_spacing) + var(--_border-width)) / -2);
227
270
  position: absolute;
228
271
  transition: opacity var(--_travel) var(--slotted-easing-standard);
272
+ z-index: -1;
229
273
  }
230
274
 
231
- [data-ui='segmented-control'][data-part='root'][data-orientation='vertical']
232
- [data-part='item']
233
- + [data-part='item']::before {
275
+ [data-ui-segmented-control][data-part-root][data-orientation='vertical']
276
+ [data-part-item]
277
+ + [data-part-item]::before {
234
278
  block-size: var(--_border-width);
235
279
  inline-size: auto;
236
- inset-block: auto;
237
- inset-block-start: 0;
238
- inset-inline: 22%;
280
+ inset-block: calc((var(--_spacing) + var(--_border-width)) / -2) auto;
281
+ inset-inline: 25%;
239
282
  }
240
283
 
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 {
284
+ [data-ui-segmented-control] [data-part-item][data-checked]::before,
285
+ [data-ui-segmented-control] [data-part-item][data-checked] + [data-part-item]::before,
286
+ [data-ui-segmented-control][data-part-root]:not([data-readonly])
287
+ [data-part-item]:hover:has([data-part-control]:enabled)::before,
288
+ [data-ui-segmented-control][data-part-root]:not([data-readonly])
289
+ [data-part-item]:hover:has([data-part-control]:enabled)
290
+ + [data-part-item]::before {
243
291
  opacity: 0;
244
292
  }
245
293
 
@@ -250,21 +298,23 @@
250
298
  * thumb's — which is what keeps the smallest step at the tightest density
251
299
  * above the WCAG minimum.
252
300
  */
253
- [data-ui='segmented-control'] [data-part='control'] {
301
+ [data-ui-segmented-control] [data-part-control] {
254
302
  appearance: none;
255
303
  background: none;
256
304
  border: 0;
257
305
  border-radius: inherit;
258
306
  cursor: inherit;
259
307
  inset-block: calc(-1 * (var(--_inset) + var(--_border-width)));
260
- inset-inline: 0;
308
+ /* Half the space on each side, so the room between two segments is not a
309
+ place a press lands on nothing. */
310
+ inset-inline: calc(var(--_spacing) / -2);
261
311
  margin: 0;
262
312
  opacity: 0;
263
313
  padding: 0;
264
314
  position: absolute;
265
315
  }
266
316
 
267
- [data-ui='segmented-control'] [data-part='icon'] {
317
+ [data-ui-segmented-control] [data-part-icon] {
268
318
  block-size: var(--_icon-size);
269
319
  display: inline-flex;
270
320
  flex: none;
@@ -276,8 +326,13 @@
276
326
  * narrow clips rather than wrapping the segment onto a second line. The
277
327
  * string stays whole in the DOM: truncation never removes content.
278
328
  */
279
- [data-ui='segmented-control'] [data-part='label'] {
280
- overflow: hidden;
329
+ [data-ui-segmented-control] [data-part-label] {
330
+ /* Clipped along the line only: `hidden` on a one-line box cut the
331
+ descenders and the accents above the cap height. A clipped box is not a
332
+ scroll container, so the flex minimum is given back by hand, or the words
333
+ would hold their full width and run into the next segment. */
334
+ min-inline-size: 0;
335
+ overflow-x: clip;
281
336
  text-overflow: ellipsis;
282
337
  white-space: nowrap;
283
338
  }
@@ -306,21 +361,34 @@
306
361
  * it in where it belongs rather than sliding it from an edge the reader has
307
362
  * never seen — which is what Mantine does.
308
363
  */
309
- [data-ui='segmented-control'] [data-part='indicator'] {
364
+ [data-ui-segmented-control] [data-part-indicator] {
365
+ /* A raised surface with a hairline and the lightest elevation: the chosen
366
+ segment sits on the track rather than being painted into it. In a dark
367
+ scheme the raised surface is darker than the tinted track, and a thumb
368
+ darker than its track reads as a hole, so it takes a step of the ink. */
310
369
  background: var(
311
370
  --slotted-segmented-control-indicator-background,
312
- var(--slotted-tone-accent-subtle-active, Highlight)
371
+ light-dark(
372
+ var(--slotted-surface-raised, Canvas),
373
+ color-mix(
374
+ in oklab,
375
+ var(--slotted-text, CanvasText) 14%,
376
+ var(--slotted-surface-raised, Canvas)
377
+ )
378
+ )
313
379
  );
314
380
  block-size: calc(100% - var(--_inset) * 2);
315
381
  border: var(--_border-width) solid
316
382
  var(
317
383
  --slotted-segmented-control-indicator-border-color,
318
- var(--slotted-tone-accent-border, Highlight)
384
+ color-mix(in oklab, var(--slotted-text, CanvasText) 10%, transparent)
319
385
  );
320
386
  border-radius: var(--_thumb-radius);
321
- box-shadow: var(--slotted-control-shadow, none);
387
+ box-shadow: var(--slotted-segmented-control-indicator-shadow, var(--slotted-shadow-xs, none));
322
388
  box-sizing: border-box;
323
- inline-size: calc((100% - var(--_inset) * 2) / var(--_count));
389
+ inline-size: calc(
390
+ (100% - var(--_inset) * 2 - var(--_spacing) * (var(--_count) - 1)) / var(--_count)
391
+ );
324
392
  inset-block-start: var(--_inset);
325
393
  inset-inline-start: var(--_inset);
326
394
  pointer-events: none;
@@ -328,7 +396,7 @@
328
396
  transition:
329
397
  opacity var(--slotted-control-transition-duration) var(--slotted-easing-standard),
330
398
  translate var(--_travel) var(--slotted-easing-standard);
331
- translate: calc(var(--_index) * 100%);
399
+ translate: calc(var(--_index) * (100% + var(--_spacing)));
332
400
  z-index: 0;
333
401
 
334
402
  @starting-style {
@@ -336,15 +404,16 @@
336
404
  }
337
405
  }
338
406
 
339
- [data-ui='segmented-control'] [data-part='indicator']:dir(rtl) {
340
- translate: calc(var(--_index) * -100%);
407
+ [data-ui-segmented-control] [data-part-indicator]:dir(rtl) {
408
+ translate: calc(var(--_index) * (-100% - var(--_spacing)));
341
409
  }
342
410
 
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));
411
+ [data-ui-segmented-control][data-part-root][data-orientation='vertical'] [data-part-indicator] {
412
+ block-size: calc(
413
+ (100% - var(--_inset) * 2 - var(--_spacing) * (var(--_count) - 1)) / var(--_count)
414
+ );
346
415
  inline-size: calc(100% - var(--_inset) * 2);
347
- translate: 0 calc(var(--_index) * 100%);
416
+ translate: 0 calc(var(--_index) * (100% + var(--_spacing)));
348
417
  }
349
418
 
350
419
  /**
@@ -355,11 +424,8 @@
355
424
  * was, because a label that thickens when it is chosen moves every label
356
425
  * beside it.
357
426
  */
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
- );
427
+ [data-ui-segmented-control] [data-part-item][data-checked] {
428
+ color: var(--slotted-segmented-control-checked-color, var(--slotted-text, CanvasText));
363
429
  }
364
430
 
365
431
  /* ── Hover and press, held back for every stronger state ──────────────── */
@@ -373,13 +439,13 @@
373
439
  * records — a tinted hover on a row of choices reads as a row of status
374
440
  * chips before it reads as one of them being pointed at.
375
441
  */
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
- ) {
442
+ [data-ui-segmented-control][data-part-root]:not([data-readonly])
443
+ [data-part-item]:hover:not([data-disabled]):not([data-checked]):has(
444
+ [data-part-control]:enabled
445
+ )::after {
380
446
  background: var(
381
447
  --slotted-segmented-control-hover-background,
382
- var(--slotted-tone-secondary-subtle-hover, ButtonFace)
448
+ color-mix(in oklab, var(--slotted-text) 5%, transparent)
383
449
  );
384
450
  }
385
451
 
@@ -388,13 +454,13 @@
388
454
  * own pace, which is the rule every activating family here writes: the
389
455
  * duration belongs to the state being entered.
390
456
  */
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
- ) {
457
+ [data-ui-segmented-control][data-part-root]:not([data-readonly])
458
+ [data-part-item]:active:not([data-disabled]):not([data-checked]):has(
459
+ [data-part-control]:enabled
460
+ )::after {
395
461
  background: var(
396
462
  --slotted-segmented-control-active-background,
397
- var(--slotted-tone-secondary-subtle-active, ButtonFace)
463
+ color-mix(in oklab, var(--slotted-text) 9%, transparent)
398
464
  );
399
465
  transition-duration: var(--slotted-duration-instant);
400
466
  }
@@ -421,13 +487,13 @@
421
487
  * it, so a segment that is both checked and focused shows one ring rather
422
488
  * than a ring and a border a pixel apart.
423
489
  */
424
- [data-ui='segmented-control'] [data-part='item']:has([data-part='control']:focus-visible) {
490
+ [data-ui-segmented-control] [data-part-item]:has([data-part-control]:focus-visible) {
425
491
  outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
426
492
  outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
427
493
  z-index: 2;
428
494
  }
429
495
 
430
- [data-ui='segmented-control'] [data-part='control']:focus-visible {
496
+ [data-ui-segmented-control] [data-part-control]:focus-visible {
431
497
  outline: none;
432
498
  }
433
499
 
@@ -438,7 +504,7 @@
438
504
  * that draws one, which is what `--slotted-focus-ring-color-invalid` exists
439
505
  * for and what keeps the outline and its shadow the same colour.
440
506
  */
441
- [data-ui='segmented-control'][data-part='root'][data-invalid] {
507
+ [data-ui-segmented-control][data-part-root][data-invalid] {
442
508
  --slotted-focus-ring-color: var(
443
509
  --slotted-segmented-control-invalid-focus-ring-color,
444
510
  var(--slotted-focus-ring-color-invalid, Highlight)
@@ -474,14 +540,14 @@
474
540
  * legible, still focusable and still copyable, and it must not be mistaken
475
541
  * for a disabled one — which is what `docs/component-features.md` §2.5 asks.
476
542
  */
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'] {
543
+ [data-ui-segmented-control][data-part-root][data-readonly] [data-part-item],
544
+ [data-ui-segmented-control][data-part-root][data-readonly] [data-part-control] {
479
545
  cursor: default;
480
546
  }
481
547
 
482
548
  /* ── Disabled ─────────────────────────────────────────────────────────── */
483
549
 
484
- [data-ui='segmented-control'] [data-part='item'][data-disabled] {
550
+ [data-ui-segmented-control] [data-part-item][data-disabled] {
485
551
  color: var(
486
552
  --slotted-segmented-control-disabled-color,
487
553
  var(--slotted-disabled-foreground, GrayText)
@@ -489,7 +555,7 @@
489
555
  cursor: default;
490
556
  }
491
557
 
492
- [data-ui='segmented-control'] [data-part='control']:disabled {
558
+ [data-ui-segmented-control] [data-part-control]:disabled {
493
559
  cursor: default;
494
560
  }
495
561
 
@@ -501,14 +567,14 @@
501
567
  * is the ground the thumb is read against, so it is the one thing that has
502
568
  * to stay.
503
569
  */
504
- [data-ui='segmented-control'][data-part='root'][data-disabled] {
570
+ [data-ui-segmented-control][data-part-root][data-disabled] {
505
571
  border-color: var(
506
572
  --slotted-segmented-control-disabled-border-color,
507
573
  var(--slotted-disabled-border, GrayText)
508
574
  );
509
575
  }
510
576
 
511
- [data-ui='segmented-control'][data-part='root'][data-disabled] [data-part='indicator'] {
577
+ [data-ui-segmented-control][data-part-root][data-disabled] [data-part-indicator] {
512
578
  background: var(
513
579
  --slotted-segmented-control-disabled-indicator-background,
514
580
  var(--slotted-disabled-background, ButtonFace)
@@ -520,6 +586,87 @@
520
586
  box-shadow: none;
521
587
  }
522
588
 
589
+ /**
590
+ * ── Underline ──────────────────────────────────────────────────────────
591
+ *
592
+ * The same radio group with no track: the words alone, sized to their own
593
+ * width, the chosen one in the text colour and underlined. Every other word
594
+ * is muted and takes the text colour under the pointer. No thumb, no
595
+ * dividers, no ground — the underline is the mark, and it sits under the
596
+ * word rather than on an edge of a box (R1).
597
+ */
598
+ [data-ui-segmented-control][data-part-root][data-variant='underline'] {
599
+ --_inner: var(--_height);
600
+
601
+ background: none;
602
+ border: 0;
603
+ border-radius: 0;
604
+ box-shadow: none;
605
+ column-gap: var(--slotted-segmented-control-underline-gap, var(--slotted-space-6, 1rem));
606
+ grid-auto-columns: auto;
607
+ padding: 0;
608
+ }
609
+
610
+ [data-ui-segmented-control][data-part-root][data-variant='underline'] [data-part-indicator],
611
+ [data-ui-segmented-control][data-part-root][data-variant='underline']
612
+ [data-part-item]
613
+ + [data-part-item]::before,
614
+ [data-ui-segmented-control][data-part-root][data-variant='underline'] [data-part-item]::after {
615
+ display: none;
616
+ }
617
+
618
+ [data-ui-segmented-control][data-part-root][data-variant='underline'] [data-part-item] {
619
+ color: var(--slotted-segmented-control-underline-color, var(--slotted-text-muted, GrayText));
620
+ font-weight: var(--slotted-font-weight-medium, 500);
621
+ min-inline-size: var(--slotted-target-min, 24px);
622
+ padding-inline: 0;
623
+ }
624
+
625
+ [data-ui-segmented-control][data-part-root][data-variant='underline']
626
+ [data-part-item][data-checked] {
627
+ color: var(
628
+ --slotted-segmented-control-underline-checked-color,
629
+ var(--slotted-text, CanvasText)
630
+ );
631
+ }
632
+
633
+ [data-ui-segmented-control][data-part-root][data-variant='underline']
634
+ [data-part-item][data-checked]
635
+ [data-part-label] {
636
+ text-decoration-color: currentColor;
637
+ text-decoration-line: underline;
638
+ text-decoration-thickness: var(--slotted-border-width-thick, 2px);
639
+ text-underline-offset: 0.35em;
640
+ }
641
+
642
+ [data-ui-segmented-control][data-part-root][data-variant='underline'][data-invalid]
643
+ [data-part-item][data-checked]
644
+ [data-part-label] {
645
+ text-decoration-color: var(--slotted-tone-danger-border, CanvasText);
646
+ }
647
+
648
+ [data-ui-segmented-control][data-part-root][data-variant='underline']:not([data-readonly])
649
+ [data-part-item]:hover:not([data-disabled]):not([data-checked]):has(
650
+ [data-part-control]:enabled
651
+ ),
652
+ [data-ui-segmented-control][data-part-root][data-variant='underline']:not([data-readonly])
653
+ [data-part-item]:active:not([data-disabled]):not([data-checked]):has(
654
+ [data-part-control]:enabled
655
+ ) {
656
+ background: none;
657
+ color: var(--slotted-text, CanvasText);
658
+ }
659
+
660
+ [data-ui-segmented-control][data-part-root][data-variant='underline']
661
+ [data-part-item]:has([data-part-control]:focus-visible) {
662
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
663
+ }
664
+
665
+ [data-ui-segmented-control][data-part-root][data-variant='underline']
666
+ [data-part-item][data-disabled] {
667
+ color: var(--slotted-disabled-foreground, GrayText);
668
+ }
669
+
523
670
  /* ── Condensed ────────────────────────────────────────────────────────── */
524
671
 
525
672
  /**
@@ -533,9 +680,9 @@
533
680
  * because `@container (max-inline-size: var(--x))` is invalid CSS.
534
681
  */
535
682
  @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'] {
683
+ [data-ui-segmented-control][data-part-root][data-condense='icons']
684
+ [data-part-item]:has([data-part-icon])
685
+ [data-part-label] {
539
686
  block-size: 1px;
540
687
  clip-path: inset(50%);
541
688
  inline-size: 1px;
@@ -544,8 +691,8 @@
544
691
  white-space: nowrap;
545
692
  }
546
693
 
547
- [data-ui='segmented-control'][data-part='root'][data-condense='icons']
548
- [data-part='item']:has([data-part='icon']) {
694
+ [data-ui-segmented-control][data-part-root][data-condense='icons']
695
+ [data-part-item]:has([data-part-icon]) {
549
696
  padding-inline: 0;
550
697
  }
551
698
  }
@@ -556,7 +703,17 @@
556
703
  * ring belongs to a segment and leaves it alone.
557
704
  */
558
705
  @media (forced-colors: active) {
559
- [data-ui='segmented-control'][data-part='root'][data-invalid] {
706
+ /* The neutral track is a tint, and a tint flattens to Canvas: the edge
707
+ says where the control is, and the thumb's edge says which is chosen. */
708
+ [data-ui-segmented-control][data-part-root]:not([data-variant='underline']) {
709
+ border-color: ButtonBorder;
710
+ }
711
+
712
+ [data-ui-segmented-control] [data-part-indicator] {
713
+ border-color: Highlight;
714
+ }
715
+
716
+ [data-ui-segmented-control][data-part-root][data-invalid] {
560
717
  outline: var(--_invalid-edge-width) solid CanvasText;
561
718
  outline-offset: calc(-1 * (var(--_border-width) + var(--_invalid-edge-width)));
562
719
  }