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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,481 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * A scrollport, and the box that frames it.
4
+ *
5
+ * The scrollbar is the platform's. Radix, Base UI, Zag, Chakra and Mantine
6
+ * all hide the native bar — `scrollbar-width: none` plus a
7
+ * `::-webkit-scrollbar` rule — and redraw it from a thumb element kept in
8
+ * sync by a scroll listener and a `ResizeObserver`. That costs the trough
9
+ * click, the scrollbar context menu, the system's own overlay behaviour, and
10
+ * the correct bar before hydration or without JavaScript.
11
+ * `docs/component-features.md` §4 asks for the opposite in this family's own
12
+ * sentence, and `scrollbar-color` reached Baseline in December 2025, so the
13
+ * themed bar no longer needs a thumb of the library's own. What that costs is
14
+ * written in `specs/components/scroll-area/contract.json`, field `scrollbar`.
15
+ *
16
+ * There is no APG pattern. The keyboard is the platform's too: a focused
17
+ * scrollport answers the arrows, the page keys, `Home`, `End` and `Space` on
18
+ * its own. What the family adds is the tab stop that makes those reachable,
19
+ * and the name that tab stop owes — and both live in the bindings, because
20
+ * both are conditional on a measurement CSS cannot make.
21
+ *
22
+ * ── Why the frame and the scrollport are two elements ───────────────────
23
+ *
24
+ * They were one, and the first capture is what refused it. The affordance is
25
+ * a mask, and a mask applies to everything the element paints — its border
26
+ * included. A bounded region with a border round it is the commonest shape
27
+ * this family has, and the veil took the top and bottom of that border with
28
+ * the content: three boxes in one capture had a crisp border down both sides
29
+ * and no border at all across the ends, which reads as a rendering fault
30
+ * rather than as an affordance.
31
+ *
32
+ * Nothing can be excluded from a mask — `mask-clip` moves where the mask is
33
+ * painted and never what it covers — so the frame has to be on an element the
34
+ * mask is not on. `root` is that element: it holds the border, the radius and
35
+ * the maximum, and `viewport` inside it holds the scroll and the veil.
36
+ */
37
+ [data-ui='scroll-area'] {
38
+ /**
39
+ * How far the veil reaches in from an edge that has content behind it.
40
+ *
41
+ * Capped against the box, which is the construction `splitter` already uses
42
+ * for its own gaps: a fixed reach is a fifth of a tall panel and a third of
43
+ * a short one, and at a third the affordance stops being a hint and starts
44
+ * being a hole. A percentage inside a gradient resolves against that
45
+ * gradient's own axis, so one declaration caps both.
46
+ *
47
+ * It does not vary by density, and that is a decision rather than an
48
+ * omission: nothing this family draws is a control, and the one thing it
49
+ * does draw is a veil over the consumer's own content. Scaling it with
50
+ * density would change how much content is hidden rather than how much
51
+ * space is used, and the content inside already answers density through the
52
+ * tokens its own families read.
53
+ */
54
+ --_fade: min(var(--slotted-scroll-area-fade-size, var(--slotted-space-8, 1.5rem)), 20%);
55
+
56
+ /**
57
+ * The thumb takes the **strong** border step, which is the step the
58
+ * foundation holds to 3:1 against every surface a control can sit on — a
59
+ * scrollbar is a non-text element and that is its floor. The track takes
60
+ * the sunken surface, which is what a groove is: the page, one step back.
61
+ */
62
+ --_thumb: var(--slotted-scroll-area-thumb-color, var(--slotted-border-strong, ButtonBorder));
63
+ --_track: var(--slotted-scroll-area-track-color, var(--slotted-surface-sunken, Canvas));
64
+
65
+ /**
66
+ * One cell, so the scrollport takes whatever the consumer gave the box and
67
+ * nothing more. `min-block-size: 0` on both is what lets it: a grid item's
68
+ * automatic minimum is its content, and without the override a scrollport
69
+ * inside a bounded box grows to its content and scrolls nothing.
70
+ *
71
+ * The family paints no frame of its own. A border, a radius and a ground
72
+ * are the consumer's, and they go here — on the element the mask is not on.
73
+ */
74
+ display: grid;
75
+ box-sizing: border-box;
76
+ min-block-size: 0;
77
+ min-inline-size: 0;
78
+ }
79
+
80
+ [data-ui='scroll-area'] > [data-part='viewport'] {
81
+ /**
82
+ * One length per logical edge: the veil's reach where that end of the
83
+ * content is out of view, and nothing where it is showing.
84
+ *
85
+ * The resting value is the reach and the attribute *removes* it, because
86
+ * `data-at-block-start` says the reader can see that end — so the rule that
87
+ * paints is the plain one and the rule that clears is the qualified one,
88
+ * and no selector needs `:not()`.
89
+ */
90
+ --_scroll-area-fade-block-start: var(--_fade);
91
+ --_scroll-area-fade-block-end: var(--_fade);
92
+ --_scroll-area-fade-inline-start: var(--_fade);
93
+ --_scroll-area-fade-inline-end: var(--_fade);
94
+
95
+ /**
96
+ * The same four, mapped to the axes a gradient can actually be given. CSS
97
+ * has no logical gradient direction and no logical `mask-position`, so the
98
+ * mapping is written here once instead of in four rules.
99
+ */
100
+ --_fade-top: var(--_scroll-area-fade-block-start);
101
+ --_fade-bottom: var(--_scroll-area-fade-block-end);
102
+ --_fade-left: var(--_scroll-area-fade-inline-start);
103
+ --_fade-right: var(--_scroll-area-fade-inline-end);
104
+
105
+ box-sizing: border-box;
106
+ min-block-size: 0;
107
+ min-inline-size: 0;
108
+
109
+ /**
110
+ * The root is where a consumer's radius goes, and the scrollport is what
111
+ * clips: without this the content ran square into a rounded frame, and so
112
+ * did the veil.
113
+ */
114
+ border-radius: inherit;
115
+
116
+ /**
117
+ * `stable` rather than `auto` by default: content that jumps sideways the
118
+ * moment it grows past its box is the defect `scrollbar-gutter` was added
119
+ * for, and it is the one Mantine approximates with padding.
120
+ */
121
+ scrollbar-gutter: stable;
122
+
123
+ /**
124
+ * A bounded region whose last inch of scroll moves the page behind it is
125
+ * the other defect the platform named. `contain` is the default here and
126
+ * opt-in in Mantine; the remaining two values are the consumer's.
127
+ */
128
+ overscroll-behavior: contain;
129
+
130
+ /**
131
+ * `thin` rather than `auto`, and it is still the platform's bar: the trough
132
+ * click, the scrollbar context menu and the system's own behaviour all
133
+ * survive it. What it drops is the pair of stepper buttons Chrome draws on
134
+ * Linux and Windows once a scrollbar is themed at all — read in the first
135
+ * capture with a bar visible, they took thirty-two pixels off a hundred and
136
+ * seventy-six-pixel panel and read as a control from another decade beside
137
+ * the rest of the library. Every surveyed implementation draws its bar at
138
+ * about this width.
139
+ *
140
+ * A product that wants the system's full-width bar writes
141
+ * `scrollbar-width: auto` on the part; it is one declaration, and the
142
+ * reference page carries it.
143
+ */
144
+ scrollbar-width: thin;
145
+ scrollbar-color: var(--_thumb) var(--_track);
146
+
147
+ /**
148
+ * The veil, and the whole of the affordance.
149
+ *
150
+ * Four edges need more than the two pseudo-elements a box has, and a
151
+ * pseudo-element positioned inside a scrollport scrolls away with the
152
+ * content. A mask applies to the border box and stays.
153
+ *
154
+ * It is a mask rather than a gradient for the reason `table` wrote when it
155
+ * refused one: a gradient over the content has to be the colour of the
156
+ * ground beneath it, so it is wrong on every surface but one and in every
157
+ * scheme but one. A mask fades the content to transparent and needs no ink
158
+ * at all — `#000` here is alpha 1, not a colour decision.
159
+ *
160
+ * One layer per physical axis, each fading both of its ends, composed with
161
+ * `intersect` so the two alphas multiply at a corner.
162
+ */
163
+ mask-image:
164
+ linear-gradient(
165
+ to bottom,
166
+ transparent 0,
167
+ #000 var(--_fade-top),
168
+ #000 calc(100% - var(--_fade-bottom)),
169
+ transparent 100%
170
+ ),
171
+ linear-gradient(
172
+ to right,
173
+ transparent 0,
174
+ #000 var(--_fade-left),
175
+ #000 calc(100% - var(--_fade-right)),
176
+ transparent 100%
177
+ );
178
+ mask-composite: intersect;
179
+
180
+ /**
181
+ * The veil moves rather than appearing. A state change the eye has to
182
+ * follow carries a transition, and this one is the whole of the feedback
183
+ * that there is more behind an edge.
184
+ *
185
+ * `mask-image` is not an animatable property, so what transitions is the
186
+ * four lengths inside it: a registered custom property is interpolable, and
187
+ * `@property` is what registers one. The registrations are at the foot of
188
+ * this file, outside the cascade layer, where `@property` belongs.
189
+ */
190
+ transition:
191
+ --_scroll-area-fade-block-start var(--slotted-duration-short) var(--slotted-easing-standard),
192
+ --_scroll-area-fade-block-end var(--slotted-duration-short) var(--slotted-easing-standard),
193
+ --_scroll-area-fade-inline-start var(--slotted-duration-short) var(--slotted-easing-standard),
194
+ --_scroll-area-fade-inline-end var(--slotted-duration-short) var(--slotted-easing-standard);
195
+ }
196
+
197
+ /**
198
+ * The inline axis is the one the document can turn round, and a gradient
199
+ * cannot be given a logical direction. So the two physical lengths swap here
200
+ * and nowhere else: every other rule in this sheet states the logical edge,
201
+ * and the mapping is corrected once.
202
+ *
203
+ * `:dir()` rather than `[dir='rtl']`, because a document may be turned round
204
+ * by the `direction` property with no attribute to select on — and the
205
+ * attribute form would then paint the veil on the edge the reader can already
206
+ * see.
207
+ */
208
+ [data-ui='scroll-area']:dir(rtl) > [data-part='viewport'] {
209
+ --_fade-left: var(--_scroll-area-fade-inline-end);
210
+ --_fade-right: var(--_scroll-area-fade-inline-start);
211
+ }
212
+
213
+ /**
214
+ * ── Which axes scroll ──────────────────────────────────────────────────
215
+ *
216
+ * The physical pair is declared first and the logical pair after it. A
217
+ * browser that does not know `overflow-block` drops those two declarations
218
+ * and keeps the physical ones; a browser that does takes the later pair,
219
+ * which is the one that stays correct in a vertical writing mode. No
220
+ * `@supports` is needed for that — an unknown declaration is discarded at
221
+ * parse time and the cascade does the rest.
222
+ */
223
+ [data-ui='scroll-area'][data-axis='block'] > [data-part='viewport'] {
224
+ overflow-x: hidden;
225
+ overflow-y: auto;
226
+ overflow-inline: hidden;
227
+ overflow-block: auto;
228
+ }
229
+
230
+ [data-ui='scroll-area'][data-axis='inline'] > [data-part='viewport'] {
231
+ overflow-x: auto;
232
+ overflow-y: hidden;
233
+ overflow-inline: auto;
234
+ overflow-block: hidden;
235
+ }
236
+
237
+ [data-ui='scroll-area'][data-axis='both'] > [data-part='viewport'] {
238
+ overflow: auto;
239
+ }
240
+
241
+ /**
242
+ * `always` is `overflow: scroll`, which is the platform's own way of saying
243
+ * "reserve the bar whether or not it is needed". It is stated per axis so it
244
+ * does not turn a one-axis area into a two-axis one.
245
+ */
246
+ [data-ui='scroll-area'][data-scrollbars='always'][data-axis='block'] > [data-part='viewport'] {
247
+ overflow-y: scroll;
248
+ overflow-block: scroll;
249
+ }
250
+
251
+ [data-ui='scroll-area'][data-scrollbars='always'][data-axis='inline'] > [data-part='viewport'] {
252
+ overflow-x: scroll;
253
+ overflow-inline: scroll;
254
+ }
255
+
256
+ [data-ui='scroll-area'][data-scrollbars='always'][data-axis='both'] > [data-part='viewport'] {
257
+ overflow: scroll;
258
+ }
259
+
260
+ /**
261
+ * `none` removes the bar and nothing else: the content still scrolls by
262
+ * wheel, by touch, by keyboard and by `scrollTo`.
263
+ *
264
+ * `docs/component-features.md` §4 warns against it where the scrollbar is how
265
+ * the reader discovers there is more — which is why the veil is the default
266
+ * and why this value is worth having at all: an area with no bar and no veil
267
+ * says nothing.
268
+ */
269
+ [data-ui='scroll-area'][data-scrollbars='none'] > [data-part='viewport'] {
270
+ scrollbar-width: none;
271
+ }
272
+
273
+ /**
274
+ * `hover` paints the bar transparent until a pointer or the keyboard is
275
+ * inside. It is the CSS half of what Radix and Mantine spend a timer on, and
276
+ * the gutter stays reserved underneath so nothing moves when the bar appears.
277
+ *
278
+ * The `:not([data-disabled])` guard is the repository rule for every weak
279
+ * state — `packages/styles/src/states.test.mjs` — rather than a state this
280
+ * family holds: a scrollport is not a control and has no disabled form.
281
+ */
282
+ [data-ui='scroll-area'][data-scrollbars='hover'] > [data-part='viewport'] {
283
+ scrollbar-color: transparent transparent;
284
+ transition:
285
+ scrollbar-color var(--slotted-duration-short) var(--slotted-easing-standard),
286
+ --_scroll-area-fade-block-start var(--slotted-duration-short) var(--slotted-easing-standard),
287
+ --_scroll-area-fade-block-end var(--slotted-duration-short) var(--slotted-easing-standard),
288
+ --_scroll-area-fade-inline-start var(--slotted-duration-short) var(--slotted-easing-standard),
289
+ --_scroll-area-fade-inline-end var(--slotted-duration-short) var(--slotted-easing-standard);
290
+ }
291
+
292
+ [data-ui='scroll-area'][data-scrollbars='hover']:not([data-disabled]):hover
293
+ > [data-part='viewport'],
294
+ [data-ui='scroll-area'][data-scrollbars='hover']:focus-within > [data-part='viewport'] {
295
+ scrollbar-color: var(--_thumb) var(--_track);
296
+ }
297
+
298
+ /* ── The gutter ────────────────────────────────────────────────────────── */
299
+
300
+ [data-ui='scroll-area'][data-gutter='auto'] > [data-part='viewport'] {
301
+ scrollbar-gutter: auto;
302
+ }
303
+
304
+ [data-ui='scroll-area'][data-gutter='both-edges'] > [data-part='viewport'] {
305
+ scrollbar-gutter: stable both-edges;
306
+ }
307
+
308
+ /* ── Scroll chaining ───────────────────────────────────────────────────── */
309
+
310
+ [data-ui='scroll-area'][data-overscroll='auto'] > [data-part='viewport'] {
311
+ overscroll-behavior: auto;
312
+ }
313
+
314
+ [data-ui='scroll-area'][data-overscroll='none'] > [data-part='viewport'] {
315
+ overscroll-behavior: none;
316
+ }
317
+
318
+ /**
319
+ * Chaining is held back only on the axes the area scrolls. An axis it does
320
+ * not scroll is still an axis of a scroll container — `overflow: hidden` is
321
+ * one — and under `contain` Chrome kept the page's wheel inside a one-line
322
+ * code block. These come after the two above, so a chosen `none` holds the
323
+ * scrolling axis and lets the other through.
324
+ */
325
+ [data-ui='scroll-area'][data-axis='inline'] > [data-part='viewport'] {
326
+ overscroll-behavior-y: auto;
327
+ overscroll-behavior-block: auto;
328
+ }
329
+
330
+ [data-ui='scroll-area'][data-axis='block'] > [data-part='viewport'] {
331
+ overscroll-behavior-x: auto;
332
+ overscroll-behavior-inline: auto;
333
+ }
334
+
335
+ /* ── The veil, edge by edge ────────────────────────────────────────────── */
336
+
337
+ [data-ui='scroll-area'][data-at-block-start] > [data-part='viewport'] {
338
+ --_scroll-area-fade-block-start: 0px;
339
+ }
340
+
341
+ [data-ui='scroll-area'][data-at-block-end] > [data-part='viewport'] {
342
+ --_scroll-area-fade-block-end: 0px;
343
+ }
344
+
345
+ [data-ui='scroll-area'][data-at-inline-start] > [data-part='viewport'] {
346
+ --_scroll-area-fade-inline-start: 0px;
347
+ }
348
+
349
+ [data-ui='scroll-area'][data-at-inline-end] > [data-part='viewport'] {
350
+ --_scroll-area-fade-inline-end: 0px;
351
+ }
352
+
353
+ /**
354
+ * `end` veils only the edges there is more content *toward*. The start of a
355
+ * scroller holds what the reader has already passed, and a product that
356
+ * would rather not dim it says so here.
357
+ */
358
+ [data-ui='scroll-area'][data-fade='end'] > [data-part='viewport'] {
359
+ --_scroll-area-fade-block-start: 0px;
360
+ --_scroll-area-fade-inline-start: 0px;
361
+ }
362
+
363
+ /**
364
+ * No veil at all, and no mask either: a mask with nothing to hide still puts
365
+ * the element on its own compositing layer, and a long list pays for that on
366
+ * every frame.
367
+ */
368
+ [data-ui='scroll-area'][data-fade='none'] > [data-part='viewport'],
369
+ /**
370
+ * A vertical writing mode turns the box, and a gradient cannot be given a
371
+ * logical direction. Rather than veil the wrong two edges, the family veils
372
+ * none: the four attributes stay correct, so a consumer who wants the
373
+ * affordance there draws it from them. The binding writes `data-sideways`
374
+ * because only script can read a writing mode.
375
+ */
376
+ [data-ui='scroll-area'][data-sideways] > [data-part='viewport'] {
377
+ mask-image: none;
378
+ }
379
+
380
+ /* ── The inset that scrolls with the content ───────────────────────────── */
381
+
382
+ /**
383
+ * Padding on a scrollport is not applied at the end edge of its scrollable
384
+ * overflow in every engine, so an inset that has to survive a scroll to the
385
+ * end goes on an element inside it. The part carries one by default — the
386
+ * step a card's own inset takes — because nearly every area wants one and a
387
+ * part that only held the consumer's padding would be a part nobody used.
388
+ * A consumer whose content carries its own inset renders no `content` at
389
+ * all, or sets the token to nought.
390
+ *
391
+ * `min-inline-size: fit-content` so a row wider than the scrollport widens
392
+ * this box with it, and the inset survives at the far end.
393
+ */
394
+ [data-ui='scroll-area'] > [data-part='viewport'] > [data-part='content'] {
395
+ box-sizing: border-box;
396
+ min-inline-size: fit-content;
397
+ padding: var(--slotted-scroll-area-inset, var(--slotted-space-5, 0.75rem));
398
+ }
399
+
400
+ /* ── Focus ─────────────────────────────────────────────────────────────── */
401
+
402
+ /**
403
+ * The scrollport is what takes focus, and the **root** is what draws the ring.
404
+ *
405
+ * Drawn on the scrollport, the ring was cut: an outline is part of what the
406
+ * element paints, so the veil fades it exactly where the veil is — the
407
+ * capture showed a ring crisp down both sides and gone across the bottom,
408
+ * which is a focus indicator that disappears on the edge the reader is moving
409
+ * toward. A focus ring is the last thing that may be half-drawn.
410
+ *
411
+ * `:has()` rather than `:focus-within`, because a link inside the content is
412
+ * focus *within* the area and is not the area being focused.
413
+ */
414
+ [data-ui='scroll-area']:has(> [data-part='viewport']:focus-visible) {
415
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
416
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
417
+ }
418
+
419
+ /* ── Paper ─────────────────────────────────────────────────────────────── */
420
+
421
+ /**
422
+ * A printed page carries the whole content, not the slice that happened to
423
+ * be in view. None of the five surveyed implementations answers this, and
424
+ * `docs/component-features.md` §4 names printing in this family's own
425
+ * sentence.
426
+ */
427
+ @media print {
428
+ [data-ui='scroll-area'],
429
+ [data-ui='scroll-area'] > [data-part='viewport'] {
430
+ max-block-size: none;
431
+ max-inline-size: none;
432
+ overflow: visible;
433
+ mask-image: none;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * The system owns the palette under forced colours, and a veil over system
439
+ * text is a legibility loss with nothing bought. The scrollbar goes back to
440
+ * the system's own, which is what `scrollbar-color: auto` means.
441
+ */
442
+ @media (forced-colors: active) {
443
+ [data-ui='scroll-area'] > [data-part='viewport'] {
444
+ mask-image: none;
445
+ scrollbar-color: auto;
446
+ }
447
+ }
448
+ }
449
+
450
+ /**
451
+ * The four lengths are registered so they interpolate. An unregistered custom
452
+ * property animates discretely — it would snap from nothing to the full reach —
453
+ * and `@property` is what gives one a type the browser can move through.
454
+ *
455
+ * These sit outside `@layer slotted.components` because `@property` is not a
456
+ * rule a cascade layer holds; it is a registration, and the layer would only
457
+ * hide it.
458
+ */
459
+ @property --_scroll-area-fade-block-start {
460
+ syntax: '<length-percentage>';
461
+ inherits: false;
462
+ initial-value: 0px;
463
+ }
464
+
465
+ @property --_scroll-area-fade-block-end {
466
+ syntax: '<length-percentage>';
467
+ inherits: false;
468
+ initial-value: 0px;
469
+ }
470
+
471
+ @property --_scroll-area-fade-inline-start {
472
+ syntax: '<length-percentage>';
473
+ inherits: false;
474
+ initial-value: 0px;
475
+ }
476
+
477
+ @property --_scroll-area-fade-inline-end {
478
+ syntax: '<length-percentage>';
479
+ inherits: false;
480
+ initial-value: 0px;
481
+ }
@@ -0,0 +1,15 @@
1
+ [
2
+ "--slotted-border-strong",
3
+ "--slotted-duration-short",
4
+ "--slotted-easing-standard",
5
+ "--slotted-focus-ring-color",
6
+ "--slotted-focus-ring-offset",
7
+ "--slotted-focus-ring-width",
8
+ "--slotted-scroll-area-fade-size",
9
+ "--slotted-scroll-area-inset",
10
+ "--slotted-scroll-area-thumb-color",
11
+ "--slotted-scroll-area-track-color",
12
+ "--slotted-space-5",
13
+ "--slotted-space-8",
14
+ "--slotted-surface-sunken"
15
+ ]
@@ -0,0 +1,177 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * One unit of content inside a page: a header — eyebrow, heading,
4
+ * description, actions — and a body one outline step deeper.
5
+ *
6
+ * ┌──────────────────────────────────────┬──────────┐
7
+ * │ header ▸ title ▸ eyebrow │ actions │
8
+ * │ heading │ │
9
+ * │ description │ │
10
+ * ├──────────────────────────────────────┴──────────┤
11
+ * │ body │
12
+ * └─────────────────────────────────────────────────┘
13
+ *
14
+ * Every part is reached with a child combinator, because a section holds
15
+ * sections, and the families inside it — a card, a description-list — have
16
+ * a `header`, an `actions` and a `description` of their own.
17
+ */
18
+ [data-ui='section'] {
19
+ --_gap: var(--slotted-section-gap, var(--slotted-space-5, 0.75rem));
20
+ --_text: var(--slotted-text, CanvasText);
21
+ --_muted: var(--slotted-text-muted, GrayText);
22
+ --_rule: var(--slotted-section-border, var(--slotted-border-subtle, ButtonBorder));
23
+ --_heading-size: var(--slotted-section-heading-size-md, var(--slotted-font-size-lg, 1rem));
24
+ --_heading-line-height: var(--slotted-line-height-lg, 1.5);
25
+ /**
26
+ * The ground a sticky header paints, so the content passing under it is
27
+ * covered rather than read through. The page states its own ground in
28
+ * `--slotted-page-ground`; outside a page, the surface.
29
+ */
30
+ --_ground: var(--slotted-page-ground, var(--slotted-surface, Canvas));
31
+
32
+ color: var(--_text);
33
+ display: flex;
34
+ flex-direction: column;
35
+ font-family: var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif);
36
+ gap: var(--_gap);
37
+ min-inline-size: 0;
38
+ /**
39
+ * A hash link to this section lands under the page's fixed chrome rather
40
+ * than behind it. Absent a page, nothing is subtracted.
41
+ */
42
+ scroll-margin-block-start: var(--slotted-page-sticky-top, 0px);
43
+ }
44
+
45
+ [data-ui='section'][data-size='sm'] {
46
+ --_heading-size: var(--slotted-section-heading-size-sm, var(--slotted-font-size-md, 0.875rem));
47
+ --_heading-line-height: var(--slotted-line-height-md, 1.5);
48
+ }
49
+ [data-ui='section'][data-size='lg'] {
50
+ --_heading-size: var(--slotted-section-heading-size-lg, var(--slotted-font-size-xl, 1.125rem));
51
+ --_heading-line-height: var(--slotted-line-height-xl, 1.45);
52
+ }
53
+
54
+ /**
55
+ * A surface and not a card: a raised ground, a subtle edge, the large
56
+ * radius, one step of inset, and no shadow, no hover, no action.
57
+ */
58
+ [data-ui='section'][data-ground='surface'] {
59
+ --_ground: var(--slotted-section-surface, var(--slotted-surface-raised, Canvas));
60
+ /* Published, as a card's is, for the families that state a ground inside it. */
61
+ --slotted-ground: var(--_ground);
62
+ background-color: var(--_ground);
63
+ border: var(--slotted-border-width-thin, 1px) solid var(--_rule);
64
+ border-radius: var(--slotted-section-radius, var(--slotted-radius-lg, 8px));
65
+ padding: var(--slotted-section-inset, var(--slotted-space-6, 1rem));
66
+ }
67
+
68
+ /* ── The header ──────────────────────────────────────────────────────── */
69
+
70
+ [data-ui='section'] > [data-part='header'] {
71
+ align-items: flex-start;
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ gap: var(--slotted-space-3, 0.375rem) var(--slotted-space-6, 1rem);
75
+ min-inline-size: 0;
76
+ }
77
+
78
+ [data-ui='section'][data-divider] > [data-part='header'] {
79
+ border-block-end: var(--slotted-border-width-thin, 1px) solid var(--_rule);
80
+ padding-block-end: var(--slotted-space-4, 0.5rem);
81
+ }
82
+
83
+ /**
84
+ * Under the page chrome, and painted, so what scrolls under it is covered.
85
+ * The inset a surface section gives its header is kept by sticking the
86
+ * header at the same distance the surface's padding put it at.
87
+ */
88
+ [data-ui='section'][data-sticky] > [data-part='header'] {
89
+ background-color: var(--_ground);
90
+ inset-block-start: var(--slotted-page-sticky-top, 0px);
91
+ /* Stuck, the header's text and buttons met the chrome's edge with no
92
+ air. The padding gives it, and the negative margin takes the same
93
+ distance back at rest, so the header stands where it stood; the ground
94
+ it paints there is the page's own, so nothing shows. */
95
+ margin-block-start: calc(-1 * var(--slotted-space-4, 0.5rem));
96
+ padding-block-start: var(--slotted-space-4, 0.5rem);
97
+ position: sticky;
98
+ z-index: var(--slotted-z-index-sticky, 100);
99
+ }
100
+
101
+ /**
102
+ * A sticky header is chrome for what is under it, so the body republishes
103
+ * where the chrome ends: the page's own value, read once on the root under
104
+ * a name of its own, plus the header's measured height. A table's column
105
+ * header, a nested section's header or a consumer's sticky aside inside
106
+ * the body reads the same property and stops under this header rather
107
+ * than under the page's; nested layers add up, one section inside another.
108
+ * The alias on the root is what keeps the redefinition from referring to
109
+ * itself.
110
+ */
111
+ [data-ui='section'][data-sticky] {
112
+ --_above: var(--slotted-page-sticky-top, 0px);
113
+ }
114
+ [data-ui='section'][data-sticky] > [data-part='body'] {
115
+ --slotted-page-sticky-top: calc(var(--_above) + var(--slotted-section-header-size, 0px));
116
+ --slotted-table-sticky-offset: var(--slotted-page-sticky-top);
117
+ }
118
+
119
+ [data-ui='section'] > [data-part='header'] > [data-part='title'] {
120
+ display: flex;
121
+ flex: 1 1 16rem;
122
+ flex-direction: column;
123
+ gap: var(--slotted-space-1, 0.125rem);
124
+ min-inline-size: 0;
125
+ }
126
+
127
+ [data-ui='section'] > [data-part='header'] > [data-part='title'] > [data-part='eyebrow'] {
128
+ color: var(--_muted);
129
+ font-size: var(--slotted-font-size-xs, 0.75rem);
130
+ font-weight: var(--slotted-font-weight-semibold, 600);
131
+ letter-spacing: 0.06em;
132
+ line-height: var(--slotted-line-height-xs, 1.4);
133
+ text-transform: uppercase;
134
+ }
135
+
136
+ [data-ui='section'] > [data-part='header'] > [data-part='title'] > [data-part='heading'] {
137
+ color: var(--_text);
138
+ font-family: var(
139
+ --slotted-font-family-heading,
140
+ var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif)
141
+ );
142
+ font-size: var(--_heading-size);
143
+ font-weight: var(--slotted-font-weight-semibold, 600);
144
+ letter-spacing: -0.01em;
145
+ line-height: var(--_heading-line-height);
146
+ margin: 0;
147
+ min-inline-size: 0;
148
+ overflow-wrap: anywhere;
149
+ text-wrap: pretty;
150
+ }
151
+
152
+ [data-ui='section'] > [data-part='header'] > [data-part='title'] > [data-part='description'] {
153
+ color: var(--_muted);
154
+ font-size: var(--slotted-font-size-sm, 0.8125rem);
155
+ line-height: var(--slotted-line-height-sm, 1.45);
156
+ margin: 0;
157
+ max-inline-size: var(--slotted-section-description-measure, 70ch);
158
+ text-wrap: pretty;
159
+ }
160
+
161
+ [data-ui='section'] > [data-part='header'] > [data-part='actions'] {
162
+ align-items: center;
163
+ display: flex;
164
+ flex: none;
165
+ gap: var(--slotted-control-gap-md, 8px);
166
+ margin-inline-start: auto;
167
+ }
168
+ [data-ui='section'] > [data-part='header'] > [data-part='actions']:empty {
169
+ display: none;
170
+ }
171
+
172
+ /* ── The body ────────────────────────────────────────────────────────── */
173
+
174
+ [data-ui='section'] > [data-part='body'] {
175
+ min-inline-size: 0;
176
+ }
177
+ }