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

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 (180) hide show
  1. package/package.json +1 -1
  2. package/src/accordion/accordion.css +10 -10
  3. package/src/action-bar/action-bar.css +35 -42
  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 +149 -68
  17. package/src/button/button.tokens.json +10 -0
  18. package/src/button-group/button-group.css +79 -74
  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 +103 -90
  36. package/src/command-palette/command-palette.tokens.json +1 -0
  37. package/src/container/container.css +24 -24
  38. package/src/context-menu/context-menu.css +8 -8
  39. package/src/currency-field/currency-field.css +210 -88
  40. package/src/currency-field/currency-field.tokens.json +13 -0
  41. package/src/dashboard-grid/dashboard-grid.css +148 -116
  42. package/src/data-grid/data-grid.css +535 -470
  43. package/src/data-grid/data-grid.tokens.json +4 -0
  44. package/src/data-list/data-list.css +81 -70
  45. package/src/data-list/data-list.tokens.json +2 -1
  46. package/src/date-field/date-field.css +55 -52
  47. package/src/date-field/date-field.tokens.json +1 -0
  48. package/src/date-picker/date-picker.css +40 -42
  49. package/src/date-range-picker/date-range-picker.css +114 -118
  50. package/src/datetime-field/datetime-field.css +78 -80
  51. package/src/datetime-field/datetime-field.tokens.json +1 -0
  52. package/src/datetime-picker/datetime-picker.css +61 -63
  53. package/src/datetime-range-picker/datetime-range-picker.css +149 -156
  54. package/src/description-list/description-list.css +61 -64
  55. package/src/detail-panel/detail-panel.css +86 -71
  56. package/src/detail-panel/detail-panel.tokens.json +2 -0
  57. package/src/dialog/dialog.css +372 -153
  58. package/src/dialog/dialog.tokens.json +6 -0
  59. package/src/divider/divider.css +18 -18
  60. package/src/drawer/drawer.css +205 -210
  61. package/src/drawer/drawer.tokens.json +1 -0
  62. package/src/entity-picker/entity-picker.css +238 -241
  63. package/src/entity-picker/entity-picker.tokens.json +1 -0
  64. package/src/event-calendar/event-calendar.css +343 -385
  65. package/src/field/field.css +74 -78
  66. package/src/field-group/field-group.css +22 -25
  67. package/src/filter-bar/filter-bar.css +193 -209
  68. package/src/form/form.css +34 -24
  69. package/src/form/form.tokens.json +1 -0
  70. package/src/fullscreen/fullscreen.css +24 -25
  71. package/src/grid/grid.css +28 -28
  72. package/src/highlight/highlight.css +6 -6
  73. package/src/hover-card/hover-card.css +6 -6
  74. package/src/icon/icon.css +19 -15
  75. package/src/icon/icon.tokens.json +1 -0
  76. package/src/image/image.css +39 -39
  77. package/src/import-export-center/import-export-center.css +135 -156
  78. package/src/inline/inline.css +27 -27
  79. package/src/inline-message/inline-message.css +52 -28
  80. package/src/inline-message/inline-message.tokens.json +4 -0
  81. package/src/kbd/kbd.css +30 -26
  82. package/src/link/link.css +15 -15
  83. package/src/list/list.css +44 -17
  84. package/src/list/list.tokens.json +4 -0
  85. package/src/list-item/list-item.css +80 -66
  86. package/src/list-item/list-item.tokens.json +7 -0
  87. package/src/listbox/listbox.css +56 -59
  88. package/src/loading-overlay/loading-overlay.css +75 -92
  89. package/src/markdown/markdown.css +17 -17
  90. package/src/menu/menu.css +83 -63
  91. package/src/menubar/menubar.css +13 -13
  92. package/src/message/message.css +182 -202
  93. package/src/message/message.tokens.json +5 -0
  94. package/src/message-scroller/message-scroller.css +41 -42
  95. package/src/multi-select/multi-select.css +152 -145
  96. package/src/multi-select/multi-select.tokens.json +1 -0
  97. package/src/navigation-menu/navigation-menu.css +167 -188
  98. package/src/notification-center/notification-center.css +140 -148
  99. package/src/notification-center/notification-center.tokens.json +3 -0
  100. package/src/number-field/number-field.css +84 -83
  101. package/src/number-field/number-field.tokens.json +1 -0
  102. package/src/otp-input/otp-input.css +67 -57
  103. package/src/otp-input/otp-input.tokens.json +1 -0
  104. package/src/page/page.css +39 -23
  105. package/src/page/page.tokens.json +1 -0
  106. package/src/page-header/page-header.css +323 -283
  107. package/src/page-header/page-header.tokens.json +5 -0
  108. package/src/pagination/pagination.css +68 -72
  109. package/src/popover/popover.css +40 -49
  110. package/src/progress-indicator/progress-indicator.css +177 -120
  111. package/src/progress-indicator/progress-indicator.tokens.json +1 -1
  112. package/src/prompt-composer/prompt-composer.css +221 -102
  113. package/src/prompt-composer/prompt-composer.tokens.json +15 -0
  114. package/src/prose/prose.css +71 -75
  115. package/src/radio-group/radio-group.css +98 -103
  116. package/src/rating/rating.css +136 -132
  117. package/src/rating/rating.tokens.json +1 -0
  118. package/src/result-state/result-state.css +74 -77
  119. package/src/runtime-properties.mjs +2 -0
  120. package/src/saved-views/saved-views.css +41 -47
  121. package/src/scroll-area/scroll-area.css +39 -32
  122. package/src/section/section.css +161 -41
  123. package/src/section/section.tokens.json +7 -2
  124. package/src/segmented-control/segmented-control.css +254 -97
  125. package/src/segmented-control/segmented-control.tokens.json +15 -11
  126. package/src/select/select.css +118 -117
  127. package/src/select/select.tokens.json +1 -0
  128. package/src/side-navigation/side-navigation.css +179 -182
  129. package/src/side-navigation/side-navigation.tokens.json +1 -0
  130. package/src/skeleton/skeleton.css +47 -48
  131. package/src/slider/slider.css +86 -88
  132. package/src/sparkline/sparkline.css +62 -40
  133. package/src/sparkline/sparkline.tokens.json +2 -0
  134. package/src/spinner/spinner.css +83 -180
  135. package/src/spinner/spinner.tokens.json +0 -1
  136. package/src/split-button/split-button.css +30 -31
  137. package/src/split-button/split-button.tokens.json +1 -0
  138. package/src/splitter/splitter.css +35 -35
  139. package/src/stack/stack.css +24 -24
  140. package/src/stat-card/stat-card.css +341 -224
  141. package/src/stat-card/stat-card.tokens.json +3 -0
  142. package/src/status-indicator/status-indicator.css +28 -28
  143. package/src/stepper/stepper.css +58 -62
  144. package/src/switch/switch.css +86 -92
  145. package/src/table/table.css +310 -165
  146. package/src/table/table.tokens.json +6 -0
  147. package/src/table-of-contents/table-of-contents.css +51 -54
  148. package/src/tabs/tabs.css +337 -302
  149. package/src/tabs/tabs.tokens.json +8 -5
  150. package/src/tag/tag.css +79 -71
  151. package/src/tag/tag.tokens.json +9 -0
  152. package/src/tags-input/tags-input.css +145 -82
  153. package/src/tags-input/tags-input.tokens.json +7 -1
  154. package/src/text-field/text-field.css +132 -136
  155. package/src/text-field/text-field.tokens.json +1 -0
  156. package/src/textarea/textarea.css +31 -28
  157. package/src/textarea/textarea.tokens.json +1 -0
  158. package/src/time-field/time-field.css +72 -69
  159. package/src/time-field/time-field.tokens.json +1 -0
  160. package/src/time-picker/time-picker.css +56 -58
  161. package/src/time-range-picker/time-range-picker.css +126 -133
  162. package/src/timeline/timeline.css +254 -266
  163. package/src/timeline/timeline.tokens.json +5 -0
  164. package/src/toast/toast.css +108 -90
  165. package/src/toast/toast.tokens.json +6 -0
  166. package/src/toggle/toggle.css +62 -25
  167. package/src/toggle/toggle.tokens.json +8 -0
  168. package/src/toggle-group/toggle-group.css +106 -60
  169. package/src/toggle-group/toggle-group.tokens.json +10 -0
  170. package/src/toolbar/toolbar.css +30 -35
  171. package/src/tooltip/tooltip.css +26 -26
  172. package/src/top-navigation/top-navigation.css +243 -259
  173. package/src/tour/tour.css +51 -60
  174. package/src/transfer/transfer.css +160 -164
  175. package/src/tree/tree.css +103 -111
  176. package/src/typography/typography.css +40 -36
  177. package/src/typography/typography.tokens.json +1 -0
  178. package/src/upload/upload.css +224 -224
  179. package/src/visually-hidden/visually-hidden.css +1 -1
  180. package/src/wizard/wizard.css +185 -197
package/src/tabs/tabs.css CHANGED
@@ -16,7 +16,7 @@
16
16
  * │ panel │
17
17
  * └──────────────────────────────────────────────────┘
18
18
  */
19
- [data-ui='tabs'] {
19
+ [data-ui-tabs] {
20
20
  /**
21
21
  * The control scales, read once at the root, so `size` and the three
22
22
  * densities are one decision each rather than one per rule.
@@ -47,23 +47,35 @@
47
47
  min(calc(var(--_icon-size) + 2 * var(--slotted-space-2, 0.25rem)), var(--_inner-size))
48
48
  );
49
49
  --_dismiss-overhang: calc((var(--_dismiss-size) - var(--_icon-size)) / 2);
50
- --_marker-size: var(--slotted-border-width-thick, 2px);
50
+ /**
51
+ * The marker: one bar the list draws under the selected tab, and the hover
52
+ * bar under the tab the pointer is on, in the same geometry — the tab's
53
+ * content box, a pill just above the list's edge. It is never thinner than the
54
+ * thick border width, the floor the high-contrast scheme keeps for a mark
55
+ * that carries state, and its thickness, its rounding and its inset are
56
+ * tokens of their own: tied to the thick border width, a theme could not
57
+ * thicken the marker without thickening sixty other rules.
58
+ */
59
+ --_marker-size: max(
60
+ var(--slotted-tabs-marker-size, 3px),
61
+ var(--slotted-border-width-thick, 2px)
62
+ );
63
+ --_marker-radius: var(--slotted-tabs-marker-radius, var(--slotted-radius-full, 9999px));
64
+ --_marker-inset: var(--slotted-tabs-marker-inset, var(--_padding-inline));
65
+ /**
66
+ * The gap between the bars and the list's edge: the selected marker and the
67
+ * hover bar stand this far above the track, so the line reads as a line and
68
+ * the bar as a mark on it. An inset rather than a transform, because the
69
+ * marker's `translate` is its slide between tabs.
70
+ */
71
+ --_marker-offset: var(--slotted-tabs-marker-offset, 1px);
72
+ --_track-color: var(--slotted-tabs-track-color, var(--slotted-border-subtle, ButtonBorder));
51
73
  /**
52
74
  * The length of the fade a scrolling list takes at an edge that has more
53
75
  * behind it, read once here so the five mask gradients below state a
54
76
  * length and no public property of their own.
55
77
  */
56
78
  --_fade: var(--slotted-tabs-fade-size, var(--slotted-space-6, 1rem));
57
- /**
58
- * Which way the selection travelled, resolved into a transform origin. The
59
- * marker of the tab being left collapses toward the direction of travel
60
- * and the marker of the tab being entered grows from the side the reader
61
- * came from, so the pair reads as one bar moving. Nothing is measured:
62
- * every surveyed indicator reads the active tab's box, which is why Base UI
63
- * ships `renderBeforeHydration` and Ark UI a `setIndicatorRect`.
64
- */
65
- --_marker-origin: 50%;
66
- --_marker-origin-selected: 50%;
67
79
 
68
80
  /**
69
81
  * No column gap on the grid itself: three gaps stood around two empty
@@ -83,7 +95,7 @@
83
95
  row-gap: var(--slotted-tabs-gap, var(--slotted-space-6, 1rem));
84
96
  }
85
97
 
86
- [data-ui='tabs'][data-size='sm'] {
98
+ [data-ui-tabs][data-size='sm'] {
87
99
  --_height: var(--slotted-control-height-sm, 30px);
88
100
  --_font-size: var(--slotted-control-font-size-sm, 0.8125rem);
89
101
  --_padding-inline: var(--slotted-control-padding-inline-sm, 0.625rem);
@@ -92,7 +104,7 @@
92
104
  --_inner-size: var(--slotted-control-inner-size-sm, 22px);
93
105
  }
94
106
 
95
- [data-ui='tabs'][data-size='lg'] {
107
+ [data-ui-tabs][data-size='lg'] {
96
108
  --_height: var(--slotted-control-height-lg, 40px);
97
109
  --_font-size: var(--slotted-control-font-size-lg, 1rem);
98
110
  --_padding-inline: var(--slotted-control-padding-inline-lg, 1rem);
@@ -111,7 +123,10 @@
111
123
  * the panel: the distance the row gap keeps in a horizontal family. Without
112
124
  * it the panel's first letter touched the track on the list's end edge.
113
125
  */
114
- [data-ui='tabs'][data-orientation='vertical'] {
126
+ [data-ui-tabs][data-orientation='vertical'] {
127
+ /* The inset runs along the block axis, where a tab has no padding. */
128
+ --_marker-inset: var(--slotted-tabs-marker-inset, 0px);
129
+
115
130
  column-gap: var(--slotted-tabs-gap, var(--slotted-space-6, 1rem));
116
131
  grid-template-columns: auto minmax(0, 1fr);
117
132
  grid-template-rows: auto auto;
@@ -124,7 +139,7 @@
124
139
  * which is on the border box and therefore does not scroll with the tabs —
125
140
  * a track drawn on the content would slide away with the first tab.
126
141
  */
127
- [data-ui='tabs'] > [data-part='list'] {
142
+ [data-ui-tabs] > [data-part-list] {
128
143
  align-items: stretch;
129
144
  display: flex;
130
145
  gap: var(--slotted-tabs-list-gap, var(--_list-gap));
@@ -140,6 +155,8 @@
140
155
  overflow-x: auto;
141
156
  overflow-y: hidden;
142
157
  overscroll-behavior-inline: contain;
158
+ /* The containing block of the marker, which scrolls with the tabs. */
159
+ position: relative;
143
160
  /**
144
161
  * The platform's scrollbar is the affordance until the family draws its
145
162
  * own. It is hidden only once `data-overflowing` says the paging controls
@@ -149,13 +166,23 @@
149
166
  scrollbar-width: thin;
150
167
  }
151
168
 
152
- [data-ui='tabs'][data-overflowing] > [data-part='list'] {
169
+ [data-ui-tabs][data-overflowing] > [data-part-list] {
153
170
  scrollbar-width: none;
154
171
  }
155
172
 
156
- [data-ui='tabs'][data-variant='line'][data-track='line'] > [data-part='list'] {
157
- border-block-end: var(--_track-size) solid
158
- var(--slotted-tabs-track-color, var(--slotted-border-subtle, ButtonBorder));
173
+ /**
174
+ * The track is an inset shadow on the list's own padding rather than its
175
+ * border. The list is a scroll container, which clips what it draws to its
176
+ * padding box, so a marker standing over a border was cut along it; inside
177
+ * the padding the marker lies over the line, flush with the list's edge. The
178
+ * shadow is the box's, so the line does not scroll with the tabs.
179
+ */
180
+ [data-ui-tabs][data-variant='line'][data-track='line'] > [data-part-list] {
181
+ /* How far the hover bar reaches past an item to stand on the track. */
182
+ --_track-reach: var(--_track-size);
183
+
184
+ box-shadow: inset 0 calc(-1 * var(--_track-size)) 0 var(--_track-color);
185
+ padding-block-end: var(--_track-size);
159
186
  }
160
187
 
161
188
  /**
@@ -169,39 +196,25 @@
169
196
  * variant for the same case, which is a second family's worth of paint to
170
197
  * remove one line.
171
198
  *
172
- * The marker then sits on the item's own edge rather than a track's width
173
- * below it, so it lands on the line the page already drew. Where that line is
174
- * is the page's business: the family stops at its own box.
199
+ * The marker then sits on the list's own edge, which is the items' edge, so
200
+ * it lands on the line the page already drew. Where that line is is the
201
+ * page's business: the family stops at its own box.
175
202
  */
176
- [data-ui='tabs'][data-track='none'] > :where([data-part='list']) > [data-part='item']::before,
177
- [data-ui='tabs'][data-track='none'] > :where([data-part='list']) > [data-part='item']::after {
178
- inset-block-end: 0;
179
- }
180
-
181
- [data-ui='tabs'][data-track='none'][data-orientation='vertical']
182
- > :where([data-part='list'])
183
- > [data-part='item']::before,
184
- [data-ui='tabs'][data-track='none'][data-orientation='vertical']
185
- > :where([data-part='list'])
186
- > [data-part='item']::after {
187
- inset-block-end: auto;
188
- inset-inline-end: 0;
189
- }
190
203
 
191
204
  /**
192
205
  * A column of labels reads down its own start edge. Centring them is what the
193
206
  * horizontal rule wants — a row of equal shares has a middle — and in a
194
207
  * column it produces a ragged left edge nobody can scan.
195
208
  */
196
- [data-ui='tabs'][data-orientation='vertical']
197
- > :where([data-part='list'])
198
- > :where([data-part='item'])
199
- > [data-part='tab'] {
209
+ [data-ui-tabs][data-orientation='vertical']
210
+ > :where([data-part-list])
211
+ > :where([data-part-item])
212
+ > [data-part-tab] {
200
213
  justify-content: start;
201
214
  text-align: start;
202
215
  }
203
216
 
204
- [data-ui='tabs'][data-orientation='vertical'] > [data-part='list'] {
217
+ [data-ui-tabs][data-orientation='vertical'] > [data-part-list] {
205
218
  flex-direction: column;
206
219
  grid-column: 1;
207
220
  grid-row: 1;
@@ -209,11 +222,17 @@
209
222
  overflow-y: auto;
210
223
  }
211
224
 
212
- [data-ui='tabs'][data-orientation='vertical'][data-variant='line'][data-track='line']
213
- > [data-part='list'] {
214
- border-block-end: 0;
215
- border-inline-end: var(--_track-size) solid
216
- var(--slotted-tabs-track-color, var(--slotted-border-subtle, ButtonBorder));
225
+ /* A shadow takes no logical offset, so the line's side is mirrored by hand. */
226
+ [data-ui-tabs][data-orientation='vertical'][data-variant='line'][data-track='line']
227
+ > [data-part-list] {
228
+ box-shadow: inset calc(-1 * var(--_track-size)) 0 0 var(--_track-color);
229
+ padding-block-end: 0;
230
+ padding-inline-end: var(--_track-size);
231
+ }
232
+
233
+ [data-ui-tabs][data-orientation='vertical'][data-variant='line'][data-track='line']:dir(rtl)
234
+ > [data-part-list] {
235
+ box-shadow: inset var(--_track-size) 0 0 var(--_track-color);
217
236
  }
218
237
 
219
238
  /**
@@ -225,7 +244,7 @@
225
244
  * label is what gives way, and it is the only place a horizontal label ever
226
245
  * truncates.
227
246
  */
228
- [data-ui='tabs'][data-fit='equal'] > [data-part='list'] {
247
+ [data-ui-tabs][data-fit='equal'] > [data-part-list] {
229
248
  display: grid;
230
249
  grid-auto-columns: minmax(0, 1fr);
231
250
  grid-auto-flow: column;
@@ -234,15 +253,15 @@
234
253
  }
235
254
 
236
255
  /** Equal shares are shares of the line, so each item takes the one it is given. */
237
- [data-ui='tabs'][data-fit='equal'] > :where([data-part='list']) > [data-part='item'] {
256
+ [data-ui-tabs][data-fit='equal'] > :where([data-part-list]) > [data-part-item] {
238
257
  min-inline-size: 0;
239
258
  }
240
259
 
241
- [data-ui='tabs'][data-fit='equal']
242
- > :where([data-part='list'])
243
- > :where([data-part='item'])
244
- > :where([data-part='tab'])
245
- > [data-part='label'] {
260
+ [data-ui-tabs][data-fit='equal']
261
+ > :where([data-part-list])
262
+ > :where([data-part-item])
263
+ > :where([data-part-tab])
264
+ > [data-part-label] {
246
265
  overflow: hidden;
247
266
  text-overflow: ellipsis;
248
267
  white-space: nowrap;
@@ -251,12 +270,11 @@
251
270
  /* ── One tab, and the box that holds it ───────────────────────────────── */
252
271
 
253
272
  /**
254
- * The item is presentational, and it exists for two reasons a `button`
255
- * cannot answer: a `button` may not contain a `button`, so a dismissible tab
256
- * needs a box for its second control, and the marker has to span the pair
257
- * rather than only the label.
273
+ * The item is presentational, and it exists because a `button` may not
274
+ * contain a `button`: a dismissible tab needs a box for its second control.
275
+ * It also carries the hover bar, under the tab and not the control.
258
276
  */
259
- [data-ui='tabs'] > :where([data-part='list']) > [data-part='item'] {
277
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-item] {
260
278
  align-items: stretch;
261
279
  display: grid;
262
280
  grid-auto-flow: column;
@@ -270,7 +288,7 @@
270
288
  position: relative;
271
289
  }
272
290
 
273
- [data-ui='tabs'] > :where([data-part='list']) > :where([data-part='item']) > [data-part='tab'] {
291
+ [data-ui-tabs] > :where([data-part-list]) > :where([data-part-item]) > [data-part-tab] {
274
292
  align-items: center;
275
293
  appearance: none;
276
294
  background-color: transparent;
@@ -302,10 +320,10 @@
302
320
  * inset there: the control carries it instead, measured from the mark rather
303
321
  * than from the box.
304
322
  */
305
- [data-ui='tabs']
306
- > :where([data-part='list'])
307
- > [data-part='item']:has(> [data-part='dismiss'])
308
- > [data-part='tab'] {
323
+ [data-ui-tabs]
324
+ > :where([data-part-list])
325
+ > [data-part-item]:has(> [data-part-dismiss])
326
+ > [data-part-tab] {
309
327
  padding-inline-end: 0;
310
328
  }
311
329
 
@@ -315,11 +333,11 @@
315
333
  * needs something to be aligned against, and a label in another script needs
316
334
  * to read its own way inside a page that reads the other.
317
335
  */
318
- [data-ui='tabs']
319
- > :where([data-part='list'])
320
- > :where([data-part='item'])
321
- > :where([data-part='tab'])
322
- > [data-part='label'] {
336
+ [data-ui-tabs]
337
+ > :where([data-part-list])
338
+ > :where([data-part-item])
339
+ > :where([data-part-tab])
340
+ > [data-part-label] {
323
341
  min-inline-size: 0;
324
342
  unicode-bidi: plaintext;
325
343
  }
@@ -331,11 +349,11 @@
331
349
  * gives way. An icon box reads `--slotted-icon-size` from the slot, so it
332
350
  * takes the tab's icon step.
333
351
  */
334
- [data-ui='tabs']
335
- > :where([data-part='list'])
336
- > :where([data-part='item'])
337
- > :where([data-part='tab'])
338
- > :is([data-part='start'], [data-part='end']) {
352
+ [data-ui-tabs]
353
+ > :where([data-part-list])
354
+ > :where([data-part-item])
355
+ > :where([data-part-tab])
356
+ > :is([data-part-start], [data-part-end]) {
339
357
  --slotted-icon-size: var(--_icon-size);
340
358
 
341
359
  align-items: center;
@@ -354,10 +372,10 @@
354
372
  * meeting a line that runs under all of them. What that variant says on
355
373
  * hover is where the mark would go, and it says it on the item, below.
356
374
  */
357
- [data-ui='tabs']
358
- > :where([data-part='list'])
359
- > :where([data-part='item'])
360
- > [data-part='tab']:not(:disabled):not([aria-selected='true']):hover {
375
+ [data-ui-tabs]
376
+ > :where([data-part-list])
377
+ > :where([data-part-item])
378
+ > [data-part-tab]:not(:disabled):not([aria-selected='true']):hover {
361
379
  color: var(--slotted-tabs-hover-color, var(--slotted-text, CanvasText));
362
380
  }
363
381
 
@@ -367,20 +385,20 @@
367
385
  * and goes one step further on press is what a segmented control does —
368
386
  * the fill has a box to belong to and no line to meet.
369
387
  */
370
- [data-ui='tabs'][data-variant='contained']
371
- > :where([data-part='list'])
372
- > :where([data-part='item'])
373
- > [data-part='tab']:not(:disabled):not([aria-selected='true']):hover {
388
+ [data-ui-tabs][data-variant='contained']
389
+ > :where([data-part-list])
390
+ > :where([data-part-item])
391
+ > [data-part-tab]:not(:disabled):not([aria-selected='true']):hover {
374
392
  background-color: var(
375
393
  --slotted-tabs-hover-background,
376
394
  var(--slotted-tone-secondary-subtle-hover)
377
395
  );
378
396
  }
379
397
 
380
- [data-ui='tabs'][data-variant='contained']
381
- > :where([data-part='list'])
382
- > :where([data-part='item'])
383
- > [data-part='tab']:not(:disabled):active {
398
+ [data-ui-tabs][data-variant='contained']
399
+ > :where([data-part-list])
400
+ > :where([data-part-item])
401
+ > [data-part-tab]:not(:disabled):active {
384
402
  background-color: var(
385
403
  --slotted-tabs-active-background,
386
404
  var(--slotted-tone-secondary-subtle-active)
@@ -398,10 +416,10 @@
398
416
  * focus became one blue shape that said neither of the two things — the
399
417
  * capture is what showed it.
400
418
  */
401
- [data-ui='tabs']
402
- > :where([data-part='list'])
403
- > :where([data-part='item'])
404
- > [data-part='tab']:focus-visible {
419
+ [data-ui-tabs]
420
+ > :where([data-part-list])
421
+ > :where([data-part-item])
422
+ > [data-part-tab]:focus-visible {
405
423
  outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
406
424
  outline-offset: calc(-1 * (var(--slotted-focus-ring-offset, 2px) + var(--_marker-size)));
407
425
  }
@@ -412,25 +430,19 @@
412
430
  * and nothing about it reaches the edge the scroller clips at — and an inset
413
431
  * ring on a twenty-four pixel box would be drawn through the mark.
414
432
  */
415
- [data-ui='tabs']
416
- > :where([data-part='list'])
417
- > :where([data-part='item'])
418
- > [data-part='dismiss']:focus-visible {
433
+ [data-ui-tabs]
434
+ > :where([data-part-list])
435
+ > :where([data-part-item])
436
+ > [data-part-dismiss]:focus-visible {
419
437
  outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
420
438
  outline-offset: var(--slotted-focus-ring-offset, 2px);
421
439
  }
422
440
 
423
- [data-ui='tabs']
424
- > :where([data-part='list'])
425
- > [data-part='item'][data-selected]
426
- > [data-part='tab'] {
441
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-item][data-selected] > [data-part-tab] {
427
442
  color: var(--slotted-tabs-selected-color, var(--slotted-tone-accent-text));
428
443
  }
429
444
 
430
- [data-ui='tabs']
431
- > :where([data-part='list'])
432
- > :where([data-part='item'])
433
- > [data-part='tab']:disabled {
445
+ [data-ui-tabs] > :where([data-part-list]) > :where([data-part-item]) > [data-part-tab]:disabled {
434
446
  color: var(--slotted-disabled-foreground, GrayText);
435
447
  cursor: not-allowed;
436
448
  }
@@ -438,132 +450,160 @@
438
450
  /* ── The marker ───────────────────────────────────────────────────────── */
439
451
 
440
452
  /**
441
- * Drawn on the item, on the edge the list runs along, and present on every
442
- * item rather than only on the selected one: a mark that appears has nothing
443
- * to transition from. It is scaled to nothing across the axis it marks, and
444
- * the transform origin is what the direction of travel decides.
445
- *
446
- * It is pulled over the track by exactly the track's own width, so the two
447
- * lines are one line rather than a hairline with a thicker line beside it.
453
+ * One bar, drawn by the list rather than by each item, that slides from the
454
+ * tab being left to the tab being entered: a mark per item can only appear
455
+ * or scale from an edge, and one mark moving is what says the selection
456
+ * moved. The binding creates it with `createIndicator` from
457
+ * `@slotted/core/measure`, which writes the selected tab's start and length
458
+ * in the list's scroll coordinates; everything else is decided here.
448
459
  *
449
- * There are two of them in the same place. `::after` is the mark, in the
450
- * accent, and the one that travels. `::before` is the hover, in a neutral
451
- * a step darker than the track: the same bar, under the tab the pointer is
452
- * on, saying where the mark would go. It fades rather than grows — a bar
453
- * that slid in from one side on every pass of the pointer would be the
454
- * travelling animation with nothing travelling — and it is never on the
455
- * selected item, where the mark already is.
456
- */
457
- [data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::before,
458
- [data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::after {
459
- border-start-end-radius: var(--slotted-radius-xs, 2px);
460
- border-start-start-radius: var(--slotted-radius-xs, 2px);
460
+ * It spans the tab's content box — the icon and the label — through
461
+ * `--slotted-tabs-marker-inset`, which defaults to the tab's own inline
462
+ * padding, and never the closing control: a tab followed by one gives up its
463
+ * end padding, so the end inset is nought there. Below the width of a short
464
+ * label it keeps a minimum length, centred on the content. It stands
465
+ * `--slotted-tabs-marker-offset` above the list's edge, over the track, and the axes are physical in this one rule
466
+ * because the measurement is: the position is from the list's left edge.
467
+ * At rest the bar is laid out by `left` and `inline-size`, so its ends keep
468
+ * their radius; it slides by `translate` and `scale` together, both on the
469
+ * compositor, so both edges advance in the same frame. A composited
470
+ * `translate` beside a main-thread `inline-size` drifted apart while the
471
+ * panel rendered, and a slide of `left` and `inline-size` lost every frame
472
+ * to a panel rendering on the same thread.
473
+ */
474
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-marker] {
475
+ --_start-inset: var(--_marker-inset);
476
+ --_end-inset: var(--_marker-inset);
477
+ --_left-inset: var(--_start-inset);
478
+ --_span: calc(var(--_indicator-length, 0px) - var(--_start-inset) - var(--_end-inset));
479
+ --_length: max(var(--slotted-space-6, 1rem), var(--_span));
480
+
481
+ background-color: var(--slotted-tabs-marker-color, var(--slotted-tone-accent-solid));
461
482
  block-size: var(--_marker-size);
462
- content: '';
463
- inset-block-end: calc(-1 * var(--_track-size));
464
- inset-inline: 0;
465
- opacity: 0;
483
+ border-radius: var(--_marker-radius);
484
+ inline-size: var(--_length);
485
+ inset-block-end: calc(var(--_marker-lift, 0px) + var(--_marker-offset));
486
+ left: calc(
487
+ var(--_indicator-start, 0px) + var(--_left-inset) + (var(--_span) - var(--_length)) / 2
488
+ );
466
489
  pointer-events: none;
467
490
  position: absolute;
468
491
  }
469
492
 
470
- [data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::before {
471
- /* No system colour as a last resort: unthemed, the bar disappears rather
472
- than shouts, as the mark beside it already does. */
473
- background-color: var(--slotted-tabs-hover-marker-color, var(--slotted-border-strong));
474
- transition:
475
- background-color var(--slotted-control-transition-duration)
476
- var(--slotted-control-transition-easing),
477
- opacity var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
493
+ [data-ui-tabs]:dir(rtl) > :where([data-part-list]) > [data-part-marker] {
494
+ --_left-inset: var(--_end-inset);
478
495
  }
479
496
 
480
- [data-ui='tabs'] > :where([data-part='list']) > [data-part='item']::after {
481
- background-color: var(--slotted-tabs-marker-color, var(--slotted-tone-accent-solid));
482
- transform: scale(0, 1);
483
- transform-origin: var(--_marker-origin) 50%;
484
- transition:
485
- opacity var(--slotted-control-transition-duration) var(--slotted-control-transition-easing),
486
- transform var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
497
+ [data-ui-tabs]:not([data-orientation='vertical'])
498
+ > [data-part-list]:has(> [data-part-item][data-selected] > [data-part-dismiss])
499
+ > [data-part-marker] {
500
+ --_end-inset: 0px;
487
501
  }
488
502
 
489
- [data-ui='tabs'][data-variant='line']
490
- > :where([data-part='list'])
491
- > [data-part='item']:not([data-selected]):has(
492
- > [data-part='tab']:not(:disabled):hover
493
- )::before {
494
- opacity: 1;
503
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-marker][hidden] {
504
+ display: none;
495
505
  }
496
506
 
497
- /** Press takes the bar to the label's own ink: the two say one thing. */
498
- [data-ui='tabs'][data-variant='line']
499
- > :where([data-part='list'])
500
- > [data-part='item']:not([data-selected]):has(
501
- > [data-part='tab']:not(:disabled):active
502
- )::before {
503
- background-color: var(--slotted-tabs-active-marker-color, var(--slotted-text));
507
+ /**
508
+ * It slides only when the selection moves: `createIndicator` places the bar
509
+ * on the new tab, writes the shift and the scale from the box it was drawn
510
+ * in, and raises `data-moving`, which starts the slide from there back to
511
+ * nought; a re-measurement of the same tab drops it, so the first position,
512
+ * a font arriving and a resized list place the bar without motion. The slide
513
+ * takes the medium duration on a curve that starts and ends slowly,
514
+ * `--slotted-tabs-marker-easing`: the control duration on the theme's
515
+ * decelerating curve covered a third of the distance in its first frame and
516
+ * read as a jump. Under reduced motion it jumps.
517
+ */
518
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-marker][data-moving] {
519
+ --_marker-easing: var(--slotted-tabs-marker-easing, cubic-bezier(0.4, 0, 0.2, 1));
520
+
521
+ animation: slotted-tabs-marker-slide var(--slotted-duration-medium) var(--_marker-easing);
522
+ transform-origin: 0 0;
523
+ }
524
+
525
+ @keyframes slotted-tabs-marker-slide {
526
+ from {
527
+ translate: var(--_indicator-shift, 0px 0px);
528
+ scale: var(--_indicator-scale, 1 1);
529
+ }
504
530
  }
505
531
 
506
- [data-ui='tabs'] > :where([data-part='list']) > [data-part='item'][data-selected]::after {
507
- opacity: 1;
508
- transform: scale(1, 1);
509
- transform-origin: var(--_marker-origin-selected) 50%;
532
+ @media (prefers-reduced-motion: reduce) {
533
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-marker][data-moving] {
534
+ animation: none;
535
+ }
510
536
  }
511
537
 
512
538
  /**
513
- * Forward is toward the end of the list. The tab being left collapses toward
514
- * the end and the tab being entered grows from the start, which is one bar
515
- * travelling. `transform-origin` takes no logical keyword, so the two
516
- * percentages swap under `:dir(rtl)` — the one place in this stylesheet
517
- * where a rule is mirrored by hand.
539
+ * The hover bar: the marker's geometry under the tab the pointer is on, in a
540
+ * neutral at a reduced opacity, saying where the mark would go. It is drawn
541
+ * on the item, and it fades rather than slides — a bar that travelled on
542
+ * every pass of the pointer would be the selection's motion with nothing
543
+ * selected. It reaches past the item by the track's width to lie on it, and
544
+ * stops at the tab where a closing control follows: that control is the
545
+ * tab's inline padding on either side of the glyph.
518
546
  */
519
- [data-ui='tabs'][data-activation-direction='forward'] {
520
- --_marker-origin: 100%;
521
- --_marker-origin-selected: 0%;
547
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-item]::before {
548
+ background-color: var(--slotted-tabs-hover-marker-color, var(--slotted-border-strong));
549
+ block-size: var(--_marker-size);
550
+ border-radius: var(--_marker-radius);
551
+ content: '';
552
+ inset-block-end: calc(var(--_marker-offset) - var(--_track-reach, 0px));
553
+ inset-inline: var(--_marker-inset);
554
+ min-inline-size: var(--slotted-space-6, 1rem);
555
+ opacity: 0;
556
+ pointer-events: none;
557
+ position: absolute;
558
+ transition:
559
+ background-color var(--slotted-control-transition-duration)
560
+ var(--slotted-control-transition-easing),
561
+ opacity var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
522
562
  }
523
563
 
524
- [data-ui='tabs'][data-activation-direction='backward'] {
525
- --_marker-origin: 0%;
526
- --_marker-origin-selected: 100%;
564
+ [data-ui-tabs]:not([data-orientation='vertical'])
565
+ > :where([data-part-list])
566
+ > [data-part-item]:has(> [data-part-dismiss])::before {
567
+ inset-inline-end: calc(2 * var(--_padding-inline) + var(--_icon-size));
527
568
  }
528
569
 
529
- [data-ui='tabs'][data-activation-direction='forward']:dir(rtl) {
530
- --_marker-origin: 0%;
531
- --_marker-origin-selected: 100%;
570
+ [data-ui-tabs][data-variant='line']
571
+ > :where([data-part-list])
572
+ > [data-part-item]:not([data-selected]):has(> [data-part-tab]:not(:disabled):hover)::before {
573
+ opacity: var(--slotted-tabs-hover-marker-opacity, 0.5);
532
574
  }
533
575
 
534
- [data-ui='tabs'][data-activation-direction='backward']:dir(rtl) {
535
- --_marker-origin: 100%;
536
- --_marker-origin-selected: 0%;
576
+ /** Press takes the bar to the label's own ink: the two say one thing. */
577
+ [data-ui-tabs][data-variant='line']
578
+ > :where([data-part-list])
579
+ > [data-part-item]:not([data-selected]):has(> [data-part-tab]:not(:disabled):active)::before {
580
+ background-color: var(--slotted-tabs-active-marker-color, var(--slotted-text));
537
581
  }
538
582
 
539
- /** A quarter turn: the bar stands on the list's own edge and grows in block. */
540
- [data-ui='tabs'][data-orientation='vertical']
541
- > :where([data-part='list'])
542
- > [data-part='item']::before,
543
- [data-ui='tabs'][data-orientation='vertical']
544
- > :where([data-part='list'])
545
- > [data-part='item']::after {
546
- block-size: auto;
547
- border-start-end-radius: var(--slotted-radius-xs, 2px);
548
- border-start-start-radius: 0;
549
- border-end-end-radius: var(--slotted-radius-xs, 2px);
583
+ /**
584
+ * A quarter turn: both bars stand on the list's inline-end edge and run along
585
+ * the block axis, where the inset defaults to nought because a tab has no
586
+ * block padding.
587
+ */
588
+ [data-ui-tabs][data-orientation='vertical'] > :where([data-part-list]) > [data-part-marker] {
589
+ block-size: var(--_length);
550
590
  inline-size: var(--_marker-size);
551
- inset-block: 0;
552
- inset-inline: auto calc(-1 * var(--_track-size));
591
+ inset-block: calc(
592
+ var(--_indicator-start, 0px) + var(--_start-inset) + (var(--_span) - var(--_length)) / 2
593
+ )
594
+ auto;
595
+ inset-inline: auto calc(var(--_marker-lift, 0px) + var(--_marker-offset));
553
596
  }
554
597
 
555
- [data-ui='tabs'][data-orientation='vertical']
556
- > :where([data-part='list'])
557
- > [data-part='item']::after {
558
- transform: scale(1, 0);
559
- transform-origin: 50% var(--_marker-origin);
560
- }
561
-
562
- [data-ui='tabs'][data-orientation='vertical']
563
- > :where([data-part='list'])
564
- > [data-part='item'][data-selected]::after {
565
- transform: scale(1, 1);
566
- transform-origin: 50% var(--_marker-origin-selected);
598
+ [data-ui-tabs][data-orientation='vertical']
599
+ > :where([data-part-list])
600
+ > [data-part-item]::before {
601
+ block-size: auto;
602
+ inline-size: var(--_marker-size);
603
+ inset-block: var(--_marker-inset);
604
+ inset-inline: auto calc(var(--_marker-offset) - var(--_track-reach, 0px));
605
+ min-block-size: var(--slotted-space-6, 1rem);
606
+ min-inline-size: 0;
567
607
  }
568
608
 
569
609
  /* ── Contained ────────────────────────────────────────────────────────── */
@@ -581,7 +621,10 @@
581
621
  * the first capture showed; the `line` variant keeps the full width, because
582
622
  * there the track is the edge of the panel below it.
583
623
  */
584
- [data-ui='tabs'][data-variant='contained'] > [data-part='list'] {
624
+ [data-ui-tabs][data-variant='contained'] > [data-part-list] {
625
+ /* The marker underlines the label inside the selected surface, a step above its edge. */
626
+ --_marker-lift: calc(2 * var(--slotted-space-1, 0.125rem));
627
+
585
628
  background-color: var(--slotted-tabs-contained-background, var(--slotted-surface-sunken));
586
629
  border-radius: var(--slotted-radius-md, 6px);
587
630
  justify-self: start;
@@ -589,39 +632,19 @@
589
632
  padding: var(--slotted-space-1, 0.125rem);
590
633
  }
591
634
 
592
- [data-ui='tabs'][data-variant='contained'] > :where([data-part='list']) > [data-part='item'] {
635
+ [data-ui-tabs][data-variant='contained'] > :where([data-part-list]) > [data-part-item] {
593
636
  border-radius: var(--slotted-radius-sm, 4px);
594
637
  transition: background-color var(--slotted-control-transition-duration)
595
638
  var(--slotted-control-transition-easing);
596
639
  }
597
640
 
598
- [data-ui='tabs'][data-variant='contained']
599
- > :where([data-part='list'])
600
- > [data-part='item'][data-selected] {
641
+ [data-ui-tabs][data-variant='contained']
642
+ > :where([data-part-list])
643
+ > [data-part-item][data-selected] {
601
644
  background-color: var(--slotted-tabs-contained-selected-background, var(--slotted-surface));
602
645
  box-shadow: var(--slotted-shadow-xs);
603
646
  }
604
647
 
605
- /**
606
- * Inset from the corners by the radius, so the bar underlines the label
607
- * instead of tracing the edge of the surface it sits on. Sitting on the
608
- * edge, it read as a second border of a different colour, and the capture
609
- * showed a pill with one blue side.
610
- */
611
- [data-ui='tabs'][data-variant='contained']
612
- > :where([data-part='list'])
613
- > [data-part='item']::after {
614
- inset-block-end: var(--slotted-space-1, 0.125rem);
615
- inset-inline: var(--slotted-radius-sm, 4px);
616
- }
617
-
618
- [data-ui='tabs'][data-variant='contained'][data-orientation='vertical']
619
- > :where([data-part='list'])
620
- > [data-part='item']::after {
621
- inset-block: var(--slotted-radius-sm, 4px);
622
- inset-inline: auto var(--slotted-space-1, 0.125rem);
623
- }
624
-
625
648
  /* ── The dismiss control ──────────────────────────────────────────────── */
626
649
 
627
650
  /**
@@ -631,17 +654,16 @@
631
654
  * through `aria-keyshortcuts`.
632
655
  *
633
656
  * It is aligned by its mark rather than by its box. The box overhangs the X
634
- * on every side, so a box set flush against the label leaves a hole where the
635
- * overhang is; both margins take the overhang back, which puts the mark one
636
- * inner gap from the last letter and the tab's own inset from the end of the
637
- * item. The first capture showed the other arrangement: a square the height of
638
- * the control, hard against the words, with the whole tab's padding on the
639
- * far side of it.
640
- */
641
- [data-ui='tabs']
642
- > :where([data-part='list'])
643
- > :where([data-part='item'])
644
- > [data-part='dismiss'] {
657
+ * on every side, and both margins take the overhang back, which puts the mark
658
+ * the tab's own inset from the last letter and from the end of the item. An
659
+ * inner gap before the mark left the round ground of the hover touching the
660
+ * words; the first capture showed a square the height of the control, hard
661
+ * against the words, with the whole tab's padding on the far side of it.
662
+ *
663
+ * Neutral, as the dialog's close and the paging controls: removing a tab
664
+ * closes a view and destroys nothing, so the danger tone overstated it.
665
+ */
666
+ [data-ui-tabs] > :where([data-part-list]) > :where([data-part-item]) > [data-part-dismiss] {
645
667
  --slotted-icon-size: var(--_icon-size);
646
668
 
647
669
  align-items: center;
@@ -650,7 +672,7 @@
650
672
  background-color: transparent;
651
673
  block-size: var(--_dismiss-size);
652
674
  border: 0;
653
- border-radius: var(--slotted-control-radius, 4px);
675
+ border-radius: var(--slotted-radius-full, 9999px);
654
676
  color: var(--slotted-tabs-dismiss-color, var(--slotted-text-muted, GrayText));
655
677
  cursor: pointer;
656
678
  display: inline-flex;
@@ -658,7 +680,7 @@
658
680
  inline-size: var(--_dismiss-size);
659
681
  justify-content: center;
660
682
  margin-inline-end: calc(var(--_padding-inline) - var(--_dismiss-overhang));
661
- margin-inline-start: max(0px, calc(var(--_inner-gap) - var(--_dismiss-overhang)));
683
+ margin-inline-start: calc(var(--_padding-inline) - var(--_dismiss-overhang));
662
684
  padding: 0;
663
685
  transition:
664
686
  background-color var(--slotted-control-transition-duration)
@@ -670,49 +692,46 @@
670
692
  is the one a mouse sees: a transparent box centred on the control reaches
671
693
  44 pixels, and the ground and the ring stay square. */
672
694
  @media (pointer: coarse) {
673
- [data-ui='tabs']
674
- > :where([data-part='list'])
675
- > :where([data-part='item'])
676
- > [data-part='dismiss'] {
695
+ [data-ui-tabs] > :where([data-part-list]) > :where([data-part-item]) > [data-part-dismiss] {
677
696
  position: relative;
678
697
  }
679
698
 
680
- [data-ui='tabs']
681
- > :where([data-part='list'])
682
- > :where([data-part='item'])
683
- > [data-part='dismiss']::after {
699
+ [data-ui-tabs]
700
+ > :where([data-part-list])
701
+ > :where([data-part-item])
702
+ > [data-part-dismiss]::after {
684
703
  content: '';
685
704
  inset: min(0px, calc((100% - var(--slotted-target-comfortable, 44px)) / 2));
686
705
  position: absolute;
687
706
  }
688
707
  }
689
708
 
690
- [data-ui='tabs']
691
- > :where([data-part='list'])
692
- > :where([data-part='item'])
693
- > [data-part='dismiss']:not(:disabled):hover {
709
+ [data-ui-tabs]
710
+ > :where([data-part-list])
711
+ > :where([data-part-item])
712
+ > [data-part-dismiss]:not(:disabled):hover {
694
713
  background-color: var(
695
714
  --slotted-tabs-dismiss-hover-background,
696
- var(--slotted-tone-danger-surface)
715
+ var(--slotted-tone-secondary-subtle-hover)
697
716
  );
698
- color: var(--slotted-tabs-dismiss-hover-color, var(--slotted-tone-danger-text));
717
+ color: var(--slotted-tabs-dismiss-hover-color, var(--slotted-text, CanvasText));
699
718
  }
700
719
 
701
- [data-ui='tabs']
702
- > :where([data-part='list'])
703
- > :where([data-part='item'])
704
- > [data-part='dismiss']:not(:disabled):active {
720
+ [data-ui-tabs]
721
+ > :where([data-part-list])
722
+ > :where([data-part-item])
723
+ > [data-part-dismiss]:not(:disabled):active {
705
724
  background-color: var(
706
725
  --slotted-tabs-dismiss-active-background,
707
- var(--slotted-tone-danger-border)
726
+ var(--slotted-tone-secondary-subtle-active)
708
727
  );
709
- color: var(--slotted-tabs-dismiss-active-color, var(--slotted-text-inverse));
728
+ color: var(--slotted-tabs-dismiss-active-color, var(--slotted-text, CanvasText));
710
729
  }
711
730
 
712
- [data-ui='tabs']
713
- > :where([data-part='list'])
714
- > :where([data-part='item'])
715
- > [data-part='dismiss']:disabled {
731
+ [data-ui-tabs]
732
+ > :where([data-part-list])
733
+ > :where([data-part-item])
734
+ > [data-part-dismiss]:disabled {
716
735
  color: var(--slotted-disabled-foreground, GrayText);
717
736
  cursor: not-allowed;
718
737
  }
@@ -735,7 +754,7 @@
735
754
  * list as they move, and Cloudscape's own guidance warns against extra
736
755
  * stops before a tab list.
737
756
  */
738
- [data-ui='tabs'] > [data-part='scroll'] {
757
+ [data-ui-tabs] > [data-part-scroll] {
739
758
  --slotted-icon-size: var(--_icon-size);
740
759
 
741
760
  align-items: center;
@@ -767,31 +786,30 @@
767
786
  * is read from the control itself — a paging control at its end is
768
787
  * disabled — so the family needs no second signal for it.
769
788
  */
770
- [data-ui='tabs']:has(> [data-part='scroll'][data-direction='start']:not(:disabled))
771
- > [data-part='list'] {
789
+ [data-ui-tabs]:has(> [data-part-scroll][data-direction='start']:not(:disabled))
790
+ > [data-part-list] {
772
791
  mask-image: linear-gradient(to right, transparent, black var(--_fade));
773
792
  }
774
793
 
775
- [data-ui='tabs']:has(> [data-part='scroll'][data-direction='end']:not(:disabled))
776
- > [data-part='list'] {
794
+ [data-ui-tabs]:has(> [data-part-scroll][data-direction='end']:not(:disabled)) > [data-part-list] {
777
795
  mask-image: linear-gradient(to left, transparent, black var(--_fade));
778
796
  }
779
797
 
780
798
  /* The start edge is the right one in a right-to-left document. */
781
- [data-ui='tabs']:dir(rtl):has(> [data-part='scroll'][data-direction='start']:not(:disabled))
782
- > [data-part='list'] {
799
+ [data-ui-tabs]:dir(rtl):has(> [data-part-scroll][data-direction='start']:not(:disabled))
800
+ > [data-part-list] {
783
801
  mask-image: linear-gradient(to left, transparent, black var(--_fade));
784
802
  }
785
803
 
786
- [data-ui='tabs']:dir(rtl):has(> [data-part='scroll'][data-direction='end']:not(:disabled))
787
- > [data-part='list'] {
804
+ [data-ui-tabs]:dir(rtl):has(> [data-part-scroll][data-direction='end']:not(:disabled))
805
+ > [data-part-list] {
788
806
  mask-image: linear-gradient(to right, transparent, black var(--_fade));
789
807
  }
790
808
 
791
- [data-ui='tabs']:has(> [data-part='scroll'][data-direction='start']:not(:disabled)):has(
792
- > [data-part='scroll'][data-direction='end']:not(:disabled)
809
+ [data-ui-tabs]:has(> [data-part-scroll][data-direction='start']:not(:disabled)):has(
810
+ > [data-part-scroll][data-direction='end']:not(:disabled)
793
811
  )
794
- > [data-part='list'] {
812
+ > [data-part-list] {
795
813
  mask-image: linear-gradient(
796
814
  to right,
797
815
  transparent,
@@ -805,19 +823,19 @@
805
823
  for both axes. As wide as its glyph and as tall as the row, it stood 24
806
824
  wide and 44 tall beside tabs grown to the comfortable height. */
807
825
  @media (pointer: coarse) {
808
- [data-ui='tabs'] > [data-part='scroll'] {
826
+ [data-ui-tabs] > [data-part-scroll] {
809
827
  min-block-size: max(var(--slotted-target-comfortable, 44px), var(--_height));
810
828
  min-inline-size: max(var(--slotted-target-comfortable, 44px), var(--_height));
811
829
  }
812
830
  }
813
831
 
814
- [data-ui='tabs'] > [data-part='scroll'][data-direction='start'] {
832
+ [data-ui-tabs] > [data-part-scroll][data-direction='start'] {
815
833
  grid-column: 1;
816
834
  grid-row: 1;
817
835
  margin-inline-end: var(--slotted-tabs-actions-gap, var(--slotted-space-4, 0.5rem));
818
836
  }
819
837
 
820
- [data-ui='tabs'] > [data-part='scroll'][data-direction='end'] {
838
+ [data-ui-tabs] > [data-part-scroll][data-direction='end'] {
821
839
  grid-column: 3;
822
840
  margin-inline-start: var(--slotted-tabs-actions-gap, var(--slotted-space-4, 0.5rem));
823
841
  grid-row: 1;
@@ -832,26 +850,26 @@
832
850
  * own mirror holds: the two compose, so the start control points back in
833
851
  * both directions.
834
852
  */
835
- [data-ui='tabs'] > [data-part='scroll'][data-direction='start'] > [data-ui='icon'] {
853
+ [data-ui-tabs] > [data-part-scroll][data-direction='start'] > [data-ui-icon] {
836
854
  rotate: 0.5turn;
837
855
  }
838
856
 
839
857
  /* The ground moves under the pointer and nothing else does (R2). */
840
- [data-ui='tabs'] > [data-part='scroll']:not(:disabled):hover {
858
+ [data-ui-tabs] > [data-part-scroll]:not(:disabled):hover {
841
859
  background-color: var(
842
860
  --slotted-tabs-hover-background,
843
861
  var(--slotted-tone-secondary-subtle-hover)
844
862
  );
845
863
  }
846
864
 
847
- [data-ui='tabs'] > [data-part='scroll']:not(:disabled):active {
865
+ [data-ui-tabs] > [data-part-scroll]:not(:disabled):active {
848
866
  background-color: var(
849
867
  --slotted-tabs-active-background,
850
868
  var(--slotted-tone-secondary-subtle-active)
851
869
  );
852
870
  }
853
871
 
854
- [data-ui='tabs'] > [data-part='scroll']:focus-visible {
872
+ [data-ui-tabs] > [data-part-scroll]:focus-visible {
855
873
  outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
856
874
  outline-offset: calc(-1 * var(--slotted-focus-ring-width, 2px));
857
875
  }
@@ -862,18 +880,18 @@
862
880
  * not removed — so the list never moves under the pointer that is using
863
881
  * the other one.
864
882
  */
865
- [data-ui='tabs'] > [data-part='scroll']:disabled {
883
+ [data-ui-tabs] > [data-part-scroll]:disabled {
866
884
  cursor: default;
867
885
  visibility: hidden;
868
886
  }
869
887
 
870
- [data-ui='tabs'][data-orientation='vertical'] > [data-part='scroll'] {
888
+ [data-ui-tabs][data-orientation='vertical'] > [data-part-scroll] {
871
889
  display: none;
872
890
  }
873
891
 
874
892
  /* ── Actions, and the panels ──────────────────────────────────────────── */
875
893
 
876
- [data-ui='tabs'] > [data-part='actions'] {
894
+ [data-ui-tabs] > [data-part-actions] {
877
895
  align-items: center;
878
896
  align-self: center;
879
897
  display: flex;
@@ -883,7 +901,7 @@
883
901
  min-inline-size: 0;
884
902
  }
885
903
 
886
- [data-ui='tabs'][data-orientation='vertical'] > [data-part='actions'] {
904
+ [data-ui-tabs][data-orientation='vertical'] > [data-part-actions] {
887
905
  grid-column: 1;
888
906
  grid-row: 2;
889
907
  }
@@ -902,14 +920,14 @@
902
920
  * their own element, which is one declaration against a layout that
903
921
  * otherwise breaks for every reader on a narrow screen.
904
922
  */
905
- [data-ui='tabs'] > [data-part='panel'] {
923
+ [data-ui-tabs] > [data-part-panel] {
906
924
  grid-column: 1 / -1;
907
925
  grid-row: 2;
908
926
  min-inline-size: 0;
909
927
  overflow-wrap: anywhere;
910
928
  }
911
929
 
912
- [data-ui='tabs'][data-orientation='vertical'] > [data-part='panel'] {
930
+ [data-ui-tabs][data-orientation='vertical'] > [data-part-panel] {
913
931
  grid-column: 2;
914
932
  grid-row: 1 / -1;
915
933
  }
@@ -920,7 +938,7 @@
920
938
  * than relied on. It is the attribute and not a class because a screen
921
939
  * reader, find-in-page and `:target` all already understand it.
922
940
  */
923
- [data-ui='tabs'] > [data-part='panel'][hidden] {
941
+ [data-ui-tabs] > [data-part-panel][hidden] {
924
942
  display: none;
925
943
  }
926
944
 
@@ -930,7 +948,7 @@
930
948
  * own content and has no padding of its own, so an inset ring is drawn
931
949
  * through the first line of it.
932
950
  */
933
- [data-ui='tabs'] > [data-part='panel']:focus-visible {
951
+ [data-ui-tabs] > [data-part-panel]:focus-visible {
934
952
  outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
935
953
  outline-offset: var(--slotted-focus-ring-offset, 2px);
936
954
  }
@@ -943,7 +961,7 @@
943
961
  * panel instead, which needs a clipping viewport that traps horizontal
944
962
  * scrolling and fights the list's own scroll.
945
963
  */
946
- [data-ui='tabs'] > [data-part='panel']:not([hidden]) {
964
+ [data-ui-tabs] > [data-part-panel]:not([hidden]) {
947
965
  animation: slotted-tabs-panel-in var(--slotted-duration-short) var(--slotted-easing-enter);
948
966
  }
949
967
 
@@ -961,11 +979,11 @@
961
979
  * adds is the track: a disabled family should not read as a live one with
962
980
  * grey labels.
963
981
  */
964
- [data-ui='tabs'][data-disabled][data-variant='line'][data-track='line'] > [data-part='list'] {
965
- border-color: var(--slotted-disabled-border, GrayText);
982
+ [data-ui-tabs][data-disabled] {
983
+ --_track-color: var(--slotted-disabled-border, GrayText);
966
984
  }
967
985
 
968
- [data-ui='tabs'][data-disabled][data-variant='contained'] > [data-part='list'] {
986
+ [data-ui-tabs][data-disabled][data-variant='contained'] > [data-part-list] {
969
987
  background-color: var(--slotted-disabled-background, Canvas);
970
988
  }
971
989
 
@@ -974,24 +992,41 @@
974
992
  * decoration in an unthemed page should disappear rather than shout, and a
975
993
  * marker painted `GrayText` with no theme would be the loudest thing on it.
976
994
  */
977
- [data-ui='tabs'][data-disabled]
978
- > :where([data-part='list'])
979
- > [data-part='item'][data-selected]::after {
995
+ [data-ui-tabs][data-disabled] > :where([data-part-list]) > [data-part-marker] {
980
996
  background-color: var(--slotted-disabled-border);
981
997
  }
982
998
 
983
999
  /* ── Forced colours ──────────────────────────────────────────────────── */
984
1000
 
985
1001
  /**
986
- * The theme maps every scheme token to a system colour, so the only rule
987
- * here is about paint the theme cannot reach: a selected tab in the
988
- * contained variant has a fill that flattens to `Canvas` along with the
989
- * track it stands in.
1002
+ * The theme maps every scheme token to a system colour, so the rules here
1003
+ * are about paint the theme cannot reach: the track drawn as a shadow, the
1004
+ * marker's ground, and a selected tab in the contained variant, whose fill
1005
+ * flattens to `Canvas` along with the track it stands in.
990
1006
  */
991
1007
  @media (forced-colors: active) {
992
- [data-ui='tabs'][data-variant='contained']
993
- > :where([data-part='list'])
994
- > [data-part='item'][data-selected] {
1008
+ /* A shadow is not drawn under forced colours: the track is a border again. */
1009
+ [data-ui-tabs][data-variant='line'][data-track='line'] > [data-part-list] {
1010
+ border-block-end: var(--_track-size) solid CanvasText;
1011
+ padding-block-end: 0;
1012
+ }
1013
+
1014
+ [data-ui-tabs][data-orientation='vertical'][data-variant='line'][data-track='line']
1015
+ > [data-part-list] {
1016
+ border-block-end: 0;
1017
+ border-inline-end: var(--_track-size) solid CanvasText;
1018
+ padding-inline-end: 0;
1019
+ }
1020
+
1021
+ /* The marker's ground would flatten to Canvas with every other ground. */
1022
+ [data-ui-tabs] > :where([data-part-list]) > [data-part-marker] {
1023
+ background-color: Highlight;
1024
+ forced-color-adjust: none;
1025
+ }
1026
+
1027
+ [data-ui-tabs][data-variant='contained']
1028
+ > :where([data-part-list])
1029
+ > [data-part-item][data-selected] {
995
1030
  border: var(--slotted-border-width-thin, 1px) solid Highlight;
996
1031
  }
997
1032
  }