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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,1899 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * The outer frame of a route: the regions that do not change when the
4
+ * content does.
5
+ *
6
+ * div[data-ui=app-shell]
7
+ * ├─ skip ▸ a · a · a
8
+ * ├─ banner ▸ brand ▸ nav-toggle · … | centre ▸ … | actions ▸ …
9
+ * ├─ nav ▸ nav-header · nav-body · nav-footer
10
+ * ├─ scrim
11
+ * ├─ main ▸ page ▸ …
12
+ * ├─ aside ▸ aside-header ▸ … · aside-toggle | aside-body | aside-footer
13
+ * ├─ footer
14
+ * └─ layer
15
+ *
16
+ * ── One grid of measures, and every row in the frame stands on it ─────────
17
+ *
18
+ * `--_gutter` is the air around anything the frame holds. `--_row-inset` is
19
+ * how far a *row* sits inside the region that holds it, and `--_row-mark` is
20
+ * the box its mark occupies. Three numbers, published as tokens, and every
21
+ * left edge in the shell is one of two sums of them: the mark column at
22
+ * `gutter + inset`, and the text column at `gutter + inset + mark + gap`.
23
+ *
24
+ * The control in the bar, the workspace mark at the head of the navigation,
25
+ * every destination's mark and the account at its foot therefore share one
26
+ * edge; the wordmark, the labels, the group headings and the account's name
27
+ * share the other. Before the three numbers existed there were five edges
28
+ * within twenty pixels of each other, which is what a column of things that
29
+ * do not line up looks like.
30
+ *
31
+ * `side-navigation`, wave 8, reads the same three and needs no agreement
32
+ * with this family beyond them.
33
+ *
34
+ * ── The chrome recedes and the content advances ───────────────────────────
35
+ *
36
+ * Under `canvas="inset"`, the default, the frame is painted the sunken
37
+ * ground and carries no surface of its own; the content is a raised card
38
+ * inset from it with a radius and an edge, and **no rule is drawn anywhere**
39
+ * — the gutter separates. `canvas="flush"` is the arrangement every surveyed
40
+ * implementation ships: regions on the surface, content on the ground, a
41
+ * hairline between. In the light scheme the two page surfaces are two per
42
+ * cent apart, which is why the hairline was doing all the work there and
43
+ * none of it here.
44
+ *
45
+ * ── The mode decides the geometry; the width only decides the mode ────────
46
+ *
47
+ * `data-nav-mode` is the single source of truth for where the navigation is
48
+ * drawn, and the binding writes it. `overlay` is a layer at *any* width,
49
+ * because a consumer may state the axis on a wide screen. Keying the layer's
50
+ * geometry on the container query instead was a defect: with the mode forced
51
+ * on a wide screen the script applied the veil, the focus scope and `inert`
52
+ * while the stylesheet left the region a static column underneath, so the
53
+ * screen went dark and nothing else happened.
54
+ *
55
+ * The container query is the *fallback*, and only that: before the binding
56
+ * has measured — a server render, the frame before hydration — there is no
57
+ * attribute, and a wide shell draws the column while a narrow one draws
58
+ * nothing. That is the safe half of the answer either way.
59
+ *
60
+ * `container-type: inline-size` makes an element a query container for its
61
+ * descendants and not for itself, so the root never rewrites its own
62
+ * template: it states its tracks once and each region places itself.
63
+ *
64
+ * ── One element in all three modes ────────────────────────────────────────
65
+ *
66
+ * The rail is the same `nav` at another inline size, which is what keeps the
67
+ * focus and the scroll position where the reader left them; Carbon remounts
68
+ * its side nav between the two and loses both.
69
+ */
70
+ [data-ui='app-shell'] {
71
+ /**
72
+ * The air around anything the frame holds. Composed from the control gap
73
+ * rather than stated on the space scale, so it answers density: a dense
74
+ * screen tightens its frame along with its controls.
75
+ */
76
+ --_gutter: var(
77
+ --slotted-app-shell-gutter,
78
+ calc(var(--slotted-control-gap-lg, 8px) + var(--slotted-space-2, 0.25rem))
79
+ );
80
+ /** How far a row sits inside its region, over and above the gutter. */
81
+ --_row-inset: var(--slotted-app-shell-row-inset-size, var(--slotted-space-4, 0.5rem));
82
+ /** The box a row's mark occupies: a control's own size, so the two agree. */
83
+ --_row-mark: var(--slotted-app-shell-row-mark-size, var(--slotted-control-height-md, 34px));
84
+ --_row-gap: var(--slotted-control-gap-md, 8px);
85
+
86
+ /**
87
+ * A row's type. A list of destinations is text and not a control, so it
88
+ * composes the type scale rather than the control scale — the two agree at
89
+ * thirteen pixels today and a theme may move either. `normal` is not a
90
+ * line height: it varies by font, and a two-line row needs the number.
91
+ */
92
+ --_row-font-size: var(
93
+ --slotted-app-shell-row-text-size,
94
+ var(--slotted-font-size-sm, 0.8125rem)
95
+ );
96
+ --_row-line-height: var(
97
+ --slotted-app-shell-row-text-line-height,
98
+ var(--slotted-line-height-sm, 1.45)
99
+ );
100
+
101
+ /**
102
+ * Published, because the rows inside the regions are not this family's:
103
+ * `side-navigation` and `top-navigation` read them in wave 8, and a
104
+ * consumer who brings its own list has nothing to align to otherwise. Six
105
+ * numbers, and every left edge, row height and label in the frame is one.
106
+ */
107
+ --slotted-app-shell-row-inset: var(--_row-inset);
108
+ --slotted-app-shell-row-mark: var(--_row-mark);
109
+ --slotted-app-shell-row-gap: var(--_row-gap);
110
+ --slotted-app-shell-row-height: var(--_row-mark);
111
+ --slotted-app-shell-row-font-size: var(--_row-font-size);
112
+ --slotted-app-shell-row-line-height: var(--_row-line-height);
113
+
114
+ --_nav-size: var(--slotted-app-shell-nav-size-md, 15rem);
115
+ /**
116
+ * The layer is wider than the column. A column shares the screen with the
117
+ * content and earns every pixel; a layer covers content the reader cannot
118
+ * reach, so its width costs nothing but the strip that says where to press
119
+ * to leave — one mark and the gutters, kept by the `min()` below.
120
+ */
121
+ --_nav-overlay-size: var(--slotted-app-shell-nav-overlay-size, 20rem);
122
+ --_aside-size: var(--slotted-app-shell-aside-size-md, 20rem);
123
+ /**
124
+ * A rail is one mark column with a row's inset on each side, so a closed
125
+ * region is exactly as wide as the marks it holds and reads as a centred
126
+ * strip rather than as a column with its air on one side.
127
+ */
128
+ --_rail-size: var(
129
+ --slotted-app-shell-rail-size,
130
+ calc(var(--_row-mark) + var(--_row-inset) * 2)
131
+ );
132
+ /**
133
+ * How far the frame's own edge is from the regions that run along it. It is
134
+ * the gutter on the inset plane, where every region is a card standing off
135
+ * the edge, and nothing on the flush plane, where they meet it. The bar and
136
+ * the footer read it so that their rows stand on the same left edge as the
137
+ * column's — measured, the control in the bar was twelve pixels to the
138
+ * right of the marks under `flush` and over them under `inset`.
139
+ */
140
+ --_edge: var(--_gutter);
141
+ /**
142
+ * How far the navigation's rows stand inside the region's own box, and
143
+ * how much of the region's width its edges take. Both are nothing on the
144
+ * inset plane, where the column is the frame's ground and its box is the
145
+ * strip a reader sees; each other plane gives the region an edge — the
146
+ * screen's, a card's, a seam — and a row whose ground meets an edge reads
147
+ * as a column with no padding. The inset is on both sides, so the closed
148
+ * strip stays centred in itself, and the closed width is written once,
149
+ * from both, for every plane.
150
+ */
151
+ --_nav-inset: 0px;
152
+ --_nav-edges: 0px;
153
+
154
+ --_frame: var(--slotted-app-shell-frame, var(--slotted-surface-sunken, Canvas));
155
+ --_canvas: var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas));
156
+ --_ink: var(--slotted-app-shell-color, var(--slotted-text, CanvasText));
157
+ /**
158
+ * The bar of a scrolling body: the thumb at the strong border step, which
159
+ * is the step `scroll-area` gives it and the one the foundation holds to
160
+ * 3:1 against every surface; the track the column's own ground, because a
161
+ * groove down the side of a navigation column is a second column.
162
+ */
163
+ --_scrollbar-thumb: var(
164
+ --slotted-app-shell-scrollbar-thumb-color,
165
+ var(--slotted-border-strong, ButtonBorder)
166
+ );
167
+ --_scrollbar-track: var(--slotted-app-shell-scrollbar-track-color, transparent);
168
+
169
+ /**
170
+ * ── The palette, region by region ──────────────────────────────────────
171
+ *
172
+ * Every region reads its own ground and its own ink, and every one of them
173
+ * falls back to the plane's. A shell whose column is dark over a light
174
+ * frame is one declaration, and it does not have to be paid for by every
175
+ * shell that wants none of it. `--_frame` and `--_canvas` stay as the two
176
+ * coarse knobs; these are what a product reaches for when the coarse pair
177
+ * is not enough.
178
+ *
179
+ * The defaults differ by plane and that is the whole of what `canvas`
180
+ * decides: under `inset` the regions are transparent and the frame shows
181
+ * through, under `flush` the regions carry the surface and the content
182
+ * carries the ground. Stating one of these properties overrules the plane
183
+ * for that region alone.
184
+ *
185
+ * Each chain is written *at the region* rather than resolved into a local
186
+ * on the frame, and that is not a style: a `var()` resolves where the
187
+ * declaration stands, so a default held on the root resolves in the root's
188
+ * scope and hands a region carrying `data-slotted-scheme="dark"` the light
189
+ * scheme's ink over its own dark ground. Written here, the last fallback is
190
+ * read inside the region, so a repainted column is one declaration and its
191
+ * ink follows the scheme it was given.
192
+ *
193
+ * A tint is a background. A *palette* — the tones the controls inside the
194
+ * region read, the borders, the focus ring — is `data-slotted-scheme` on
195
+ * the region itself, because a ground swapped without its tones leaves
196
+ * every control calibrated against a page it is no longer on.
197
+ */
198
+ --_rule: var(--slotted-app-shell-border-color, var(--slotted-border-subtle, ButtonBorder));
199
+ --_rule-width: var(--slotted-border-width-thin, 1px);
200
+ --_radius: var(--slotted-app-shell-radius, var(--slotted-radius-lg, 8px));
201
+ --_elevation: var(--slotted-app-shell-elevation, var(--slotted-shadow-xs));
202
+ /**
203
+ * The line a seam draws, stated once. `separation="tone"` sets it to
204
+ * `none` and the forced-colours block below sets it back, so every seam
205
+ * rule is written once and reads the axis through this property.
206
+ */
207
+ --_seam: var(--_rule-width) solid var(--_rule);
208
+ --_seam-width: var(--_rule-width);
209
+ /** The corner of a region whose corner is the card's: the radius inside the edge. */
210
+ --_inner-radius: calc(var(--_radius) - var(--_rule-width));
211
+ --_scrim: var(--slotted-app-shell-scrim, var(--slotted-scrim, Canvas));
212
+ --_banner-size: var(
213
+ --slotted-app-shell-banner-block-size,
214
+ calc(var(--slotted-control-height-lg, 38px) + var(--slotted-space-5, 0.75rem) * 2)
215
+ );
216
+ --_duration: var(--slotted-duration-medium);
217
+ --_easing: var(--slotted-easing-standard, cubic-bezier(0.2, 0, 0, 1));
218
+
219
+ background-color: var(--_frame);
220
+ /**
221
+ * A wash or a photograph behind the frame is a theme's decision, not a
222
+ * prop: one layer over the frame's colour, sized to cover the frame and
223
+ * nothing when the token is unset.
224
+ */
225
+ background-image: var(--slotted-app-shell-frame-image, none);
226
+ background-position: center;
227
+ background-size: cover;
228
+ color: var(--_ink);
229
+ container-name: slotted-app-shell;
230
+ container-type: inline-size;
231
+ display: grid;
232
+ font-family: var(--slotted-font-family-sans, ui-sans-serif, system-ui, sans-serif);
233
+ /**
234
+ * Four rows — banner, body, the row the secondary pane drops into when the
235
+ * shell is narrow, and footer — and three columns, each sized by what is
236
+ * in it. A track with nothing in it is nought, so the rows and columns a
237
+ * layout does not use cost it nothing.
238
+ */
239
+ grid-template-columns: auto minmax(0, 1fr) auto;
240
+ grid-template-rows: auto minmax(0, 1fr) auto auto;
241
+ min-block-size: 100%;
242
+ min-inline-size: 0;
243
+ /**
244
+ * ── The frame clips its inline axis ───────────────────────────────────
245
+ *
246
+ * A closed layer stands one whole width outside the frame, and a
247
+ * transformed box still counts towards the scrollable overflow of every
248
+ * scroll container above it — `visibility: hidden` takes it off the screen
249
+ * and not out of the geometry. Measured in the `navModes` story: the closed
250
+ * end-side panel gave the document 192 pixels of horizontal overflow once
251
+ * the demo frame's clip was lifted, and inside that frame the focus scope's
252
+ * first `focus()` scrolled the frame 240 pixels to the hidden panel, which
253
+ * the slide then clamped back frame by frame — the whole page jumped and
254
+ * returned (`tools/harness` reading, Chrome 150).
255
+ *
256
+ * `clip` and not `hidden`: `hidden` is a scroll container, which is the
257
+ * thing `focus()` would scroll; `clip` is not, and it does not establish a
258
+ * formatting context either, so nothing about the grid changes. Per axis
259
+ * rather than once, as `progress-indicator` states it: the block axis stays
260
+ * open because the frame grows with its content under `scroll='document'`,
261
+ * and a focus ring on the frame's last row has to be drawn past its edge.
262
+ * No `overflow-clip-margin`: it applies only when both axes clip, and the
263
+ * margin it leaves is a strip of the layer still counted — four pixels of
264
+ * the same jump, measured. The logical pair follows the physical one for
265
+ * the engines that read it, as `scroll-area` writes the same rule.
266
+ */
267
+ overflow-x: clip;
268
+ overflow-y: visible;
269
+ overflow-inline: clip;
270
+ overflow-block: visible;
271
+ position: relative;
272
+ }
273
+
274
+ [data-ui='app-shell'][data-nav-size='sm'] {
275
+ --_nav-size: var(--slotted-app-shell-nav-size-sm, 13rem);
276
+ }
277
+ [data-ui='app-shell'][data-nav-size='lg'] {
278
+ --_nav-size: var(--slotted-app-shell-nav-size-lg, 18rem);
279
+ }
280
+ [data-ui='app-shell'][data-aside-size='sm'] {
281
+ --_aside-size: var(--slotted-app-shell-aside-size-sm, 16rem);
282
+ }
283
+ [data-ui='app-shell'][data-aside-size='lg'] {
284
+ --_aside-size: var(--slotted-app-shell-aside-size-lg, 26rem);
285
+ }
286
+
287
+ /**
288
+ * `content` is the shell that owns the viewport: it is the height it was
289
+ * given and only the content scrolls. `100%` chains to the viewport when the
290
+ * shell is the top of the document, and the maximum is what closes the gap
291
+ * every surveyed implementation leaves open — a mobile browser's collapsing
292
+ * toolbar makes `100vh` taller than the space there is, and `100dvh` is the
293
+ * space there is.
294
+ */
295
+ [data-ui='app-shell'][data-scroll='content'] {
296
+ block-size: 100%;
297
+ /**
298
+ * Both axes, under `content` alone: the shell's height is the space it was
299
+ * given and never its content's, so containing it costs nothing — and it
300
+ * is what lets the pane, as a row under the content at a narrow width,
301
+ * take half the shell in `cqb` rather than the whole of it.
302
+ */
303
+ container-type: size;
304
+ max-block-size: 100dvh;
305
+ }
306
+
307
+ /* ── Skip links ──────────────────────────────────────────────────────── */
308
+
309
+ /**
310
+ * The first focusable thing in the document, and out of the flow until it is
311
+ * focused: the reader who does not need it never meets it, and the reader
312
+ * who does finds it before the chrome.
313
+ */
314
+ [data-ui='app-shell'] > [data-part='skip'] {
315
+ inset-block-start: 0;
316
+ inset-inline-start: 0;
317
+ position: absolute;
318
+ z-index: var(--slotted-z-index-popover, 1100);
319
+ }
320
+
321
+ [data-ui='app-shell'] > [data-part='skip'] > a {
322
+ background-color: var(--_canvas);
323
+ border: var(--_rule-width) solid var(--_rule);
324
+ border-radius: var(--slotted-control-radius, 4px);
325
+ box-shadow: var(--slotted-shadow-md);
326
+ color: var(--slotted-text, CanvasText);
327
+ display: inline-block;
328
+ font-size: var(--slotted-control-font-size-md, 13px);
329
+ font-weight: var(--slotted-font-weight-medium, 500);
330
+ inset-block-start: var(--_gutter);
331
+ inset-inline-start: var(--_gutter);
332
+ padding-block: var(--slotted-space-3, 0.375rem);
333
+ padding-inline: var(--slotted-space-5, 0.75rem);
334
+ position: absolute;
335
+ text-decoration: none;
336
+ transition: translate var(--_duration) var(--_easing);
337
+ translate: 0 -200%;
338
+ white-space: nowrap;
339
+ }
340
+
341
+ [data-ui='app-shell'] > [data-part='skip'] > a:focus-visible {
342
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
343
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
344
+ translate: 0 0;
345
+ }
346
+
347
+ /* ── What each region is, before where it is ─────────────────────────── */
348
+
349
+ /**
350
+ * Two clusters and the space between them, each starting on the row inset so
351
+ * the control in the bar stands over the marks in the column. The shell owns
352
+ * where a top bar's things go and owns none of what they are:
353
+ * `top-navigation` in wave 8 fills `actions`.
354
+ */
355
+ /**
356
+ * The bar is a query container of its own, for the same reason the column is:
357
+ * under `primary='nav'` it is the frame's width less the navigation's, so
358
+ * what it holds has to answer *its* width rather than the frame's. Nothing
359
+ * in the family reads the query — the bar carries an application's own search
360
+ * and its own account, and those are the things that shed a label first.
361
+ */
362
+ [data-ui='app-shell'] > [data-part='banner'] {
363
+ align-items: center;
364
+ background-color: var(--slotted-app-shell-banner-surface, transparent);
365
+ color: var(
366
+ --slotted-app-shell-banner-color,
367
+ var(--slotted-app-shell-color, var(--slotted-text, CanvasText))
368
+ );
369
+ container-name: slotted-app-shell-banner;
370
+ container-type: inline-size;
371
+ display: flex;
372
+ gap: var(--_row-gap);
373
+ grid-area: 1 / 1 / 2 / -1;
374
+ min-block-size: var(--_banner-size);
375
+ min-inline-size: 0;
376
+ padding-inline: var(--_edge);
377
+ }
378
+
379
+ [data-ui='app-shell'] [data-part='brand'] {
380
+ align-items: center;
381
+ display: flex;
382
+ flex: 0 1 auto;
383
+ font-weight: var(--slotted-font-weight-semibold, 600);
384
+ gap: var(--_row-gap);
385
+ min-inline-size: 0;
386
+ padding-inline-start: var(--_row-inset);
387
+ }
388
+
389
+ /** Pushed to the end by the space it takes, so an empty cluster costs nothing. */
390
+ /**
391
+ * Nothing in the bar may push the frame wider than the space there is. A
392
+ * wordmark is one word and one word does not wrap, so under `primary='nav'`
393
+ * at 420 pixels the mark reported ninety-three pixels of content inside a
394
+ * sixty-five-pixel box and the whole frame grew with it. What the bar holds
395
+ * shortens instead; the control is exempt, because a control that shrinks
396
+ * below its target is a defect of a different kind.
397
+ */
398
+ [data-ui='app-shell'] [data-part='brand'] > *:not([data-part='nav-toggle']) {
399
+ min-inline-size: 0;
400
+ overflow: var(--slotted-app-shell-brand-overflow, hidden);
401
+ text-overflow: ellipsis;
402
+ white-space: var(--slotted-app-shell-brand-wrap, nowrap);
403
+ }
404
+ [data-ui='app-shell'] > [data-part='banner'] > [data-part='actions'] {
405
+ align-items: center;
406
+ display: flex;
407
+ flex: 0 1 auto;
408
+ min-inline-size: 0;
409
+ gap: var(--slotted-control-gap-sm, 6px);
410
+ margin-inline-start: auto;
411
+ padding-inline-end: var(--_row-inset);
412
+ }
413
+ /**
414
+ * A control keeps its width. Left to shrink with the cluster, a button in a
415
+ * bar too narrow for it broke its own label one letter per line, which is a
416
+ * defect of a different kind from a bar that has run out of room. What runs
417
+ * out of room is the middle, which is what `centre` is for.
418
+ */
419
+ [data-ui='app-shell'] > [data-part='banner'] > [data-part='actions'] > * {
420
+ flex-shrink: 0;
421
+ }
422
+
423
+ /**
424
+ * The middle of the bar, and the one cluster that grows: a search, a trail, a
425
+ * route title, tabs. Without it a bar is two clusters and a void — which is
426
+ * what the first reference page photographed, a control at one end and three
427
+ * at the other with twelve hundred pixels of nothing between. What it holds
428
+ * is the consumer's; that it may be narrower than what it holds is the
429
+ * shell's, for the reason `actions` carries `min-inline-size: 0`.
430
+ */
431
+ [data-ui='app-shell'] > [data-part='banner'] > [data-part='centre'] {
432
+ align-items: center;
433
+ display: flex;
434
+ flex: 1 1 auto;
435
+ gap: var(--_row-gap);
436
+ min-inline-size: 0;
437
+ }
438
+
439
+ /**
440
+ * A query container of its own, so the family inside it answers the rail
441
+ * without the shell telling it anything. The rail is a width, and what a
442
+ * destination shows at that width — a mark alone, a mark and a word — is
443
+ * `side-navigation`'s decision to take against the room it has.
444
+ */
445
+ [data-ui='app-shell'] > [data-part='nav'] {
446
+ background-color: var(--slotted-app-shell-nav-surface, transparent);
447
+ box-sizing: border-box;
448
+ color: var(
449
+ --slotted-app-shell-nav-color,
450
+ var(--slotted-app-shell-color, var(--slotted-text, CanvasText))
451
+ );
452
+ container-name: slotted-app-shell-nav;
453
+ container-type: inline-size;
454
+ display: flex;
455
+ flex-direction: column;
456
+ grid-area: 2 / 1 / 3 / 2;
457
+ min-block-size: 0;
458
+ /**
459
+ * A grid item's automatic minimum size is its content's, so a rail asked
460
+ * for fifty pixels stood at whatever the longest destination measured.
461
+ */
462
+ min-inline-size: 0;
463
+ overflow: hidden;
464
+ transition:
465
+ inline-size var(--_duration) var(--_easing),
466
+ visibility var(--_duration) step-start;
467
+ }
468
+
469
+ /**
470
+ * The column pins its ends and scrolls its middle. Ant and Mantine let the
471
+ * list overflow the region, which takes the account control off the screen
472
+ * the moment an application has more destinations than height.
473
+ *
474
+ * The ends hold a row, and a row here is often two lines — a workspace and
475
+ * its perimeter, a person and their role. Block padding is what gives a
476
+ * two-line block its own air; without it both were crushed against the group
477
+ * heading below and the frame's footer beneath.
478
+ */
479
+ [data-ui='app-shell'] > [data-part='nav'] > [data-part='nav-header'],
480
+ [data-ui='app-shell'] > [data-part='nav'] > [data-part='nav-footer'] {
481
+ align-items: center;
482
+ display: flex;
483
+ flex: 0 0 auto;
484
+ gap: var(--_row-gap);
485
+ min-block-size: var(--_rail-size);
486
+ min-inline-size: 0;
487
+ padding-block: var(--_row-inset);
488
+ }
489
+ /**
490
+ * The inset is the rows', not the region's: on the parts, a seam drawn by
491
+ * one of them — the rule over the foot — still runs the column's full width,
492
+ * and a strip of the rail's own beside the column needs none.
493
+ */
494
+ [data-ui='app-shell']
495
+ > [data-part='nav']
496
+ > :is([data-part='nav-header'], [data-part='nav-body'], [data-part='nav-footer']) {
497
+ padding-inline: var(--_nav-inset);
498
+ }
499
+ /**
500
+ * The body scrolls the way `scroll-area` scrolls, and for the reasons written
501
+ * there. `scrollbar-gutter: stable` reserves the bar's column before the
502
+ * list overflows, so a destination added at the bottom moves no row
503
+ * sideways. `scrollbar-width: thin` is still the platform's bar — the trough
504
+ * click and the context menu survive — and it is what makes the bar the
505
+ * family's to colour: an overlay bar drawn by the platform over the rows sat
506
+ * on the chevron of every parent, because a row ends one inset from the
507
+ * column's edge and the platform's bar is wider than that inset. A thin
508
+ * classic bar stands in the gutter instead, beside the rows.
509
+ *
510
+ * The inline axis is hidden, not `auto`: while the region's width moves
511
+ * between open and rail, the rows stand wider than the body, and an `auto`
512
+ * inline axis drew a horizontal bar for the length of the transition.
513
+ */
514
+ [data-ui='app-shell'] > [data-part='nav'] > [data-part='nav-body'] {
515
+ align-items: stretch;
516
+ display: flex;
517
+ flex: 1 1 auto;
518
+ flex-direction: column;
519
+ gap: var(--slotted-space-1, 0.125rem);
520
+ min-block-size: 0;
521
+ overflow-x: hidden;
522
+ overflow-y: auto;
523
+ overflow-inline: hidden;
524
+ overflow-block: auto;
525
+ overscroll-behavior: contain;
526
+ padding-block: var(--slotted-space-4, 0.5rem);
527
+ scrollbar-color: var(--_scrollbar-thumb) var(--_scrollbar-track);
528
+ scrollbar-gutter: stable;
529
+ scrollbar-width: thin;
530
+ }
531
+
532
+ [data-ui='app-shell'] > [data-part='main'] {
533
+ background-color: var(
534
+ --slotted-app-shell-main-surface,
535
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
536
+ );
537
+ color: var(
538
+ --slotted-app-shell-main-color,
539
+ var(--slotted-app-shell-color, var(--slotted-text, CanvasText))
540
+ );
541
+ grid-area: 2 / 2 / 3 / 3;
542
+ /**
543
+ * Whatever the content stacks, it stacks inside the content: a sticky
544
+ * page-header raises itself three steps over the sticky layer, and
545
+ * without a context of its own here it would paint over the wait drawn
546
+ * on this region's edge.
547
+ */
548
+ isolation: isolate;
549
+ min-block-size: 0;
550
+ min-inline-size: 0;
551
+ }
552
+ [data-ui='app-shell'][data-scroll='content'] > [data-part='main'] {
553
+ overflow: auto;
554
+ }
555
+
556
+ /**
557
+ * The wait for the next page, on the line between the chrome and the
558
+ * content. The frame is the box of `main` — its grid area here, its margins
559
+ * and its corners wherever a canvas states them, because every such rule
560
+ * below names both parts — and it paints nothing: the one progress bar it
561
+ * holds stands on its block-start edge. It is a frame rather than a strip
562
+ * so the card's corners clip the bar; a strip two pixels tall given the
563
+ * card's radius has its radii scaled down to half its height, and its ends
564
+ * would be square against a rounded card.
565
+ *
566
+ * Strict containment keeps the travelling bar's per-frame layout inside
567
+ * this box, and its paint inside the corners.
568
+ */
569
+ [data-ui='app-shell'] > [data-part='progress'] {
570
+ contain: strict;
571
+ grid-area: 2 / 2 / 3 / 3;
572
+ inset-block-start: calc(-1 * var(--_progress-lift, 0px));
573
+ min-block-size: 0;
574
+ min-inline-size: 0;
575
+ overflow: clip;
576
+ pointer-events: none;
577
+ position: relative;
578
+ z-index: 1;
579
+ }
580
+ /**
581
+ * The content scrolls with the document under `document`, and the frame
582
+ * with it; the bar holds the top of the viewport once the banner, which is
583
+ * not sticky there, has gone.
584
+ */
585
+ [data-ui='app-shell'][data-scroll='document'] > [data-part='progress'] {
586
+ --slotted-progress-indicator-position: sticky;
587
+ }
588
+
589
+ /** A skip link's destination takes focus; it draws no ring for a pointer. */
590
+ [data-ui='app-shell'] > [data-part='main']:focus {
591
+ outline: none;
592
+ }
593
+ [data-ui='app-shell'] > [data-part='main']:focus-visible {
594
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
595
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
596
+ }
597
+
598
+ /**
599
+ * Below the content until the shell is wide enough to hold it beside: at a
600
+ * narrow width a column beside the content is two columns of nothing.
601
+ *
602
+ * The pane opens and closes the way the navigation does, and its closed
603
+ * state is the navigation's `hidden`: out of the layout, out of the tab
604
+ * order, out of the accessibility tree. A pane has no rail — its content is
605
+ * the thing it shows or nothing — and a region at nought pixels that still
606
+ * lays its children out puts content outside the box that owns it.
607
+ */
608
+ [data-ui='app-shell'] > [data-part='aside'] {
609
+ background-color: var(
610
+ --slotted-app-shell-aside-surface,
611
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
612
+ );
613
+ box-sizing: border-box;
614
+ color: var(
615
+ --slotted-app-shell-aside-color,
616
+ var(--slotted-app-shell-color, var(--slotted-text, CanvasText))
617
+ );
618
+ display: flex;
619
+ flex-direction: column;
620
+ grid-area: 3 / 1 / 4 / -1;
621
+ min-block-size: 0;
622
+ min-inline-size: 0;
623
+ }
624
+ [data-ui='app-shell'][data-scroll='content'] > [data-part='aside'] {
625
+ overflow: auto;
626
+ }
627
+ [data-ui='app-shell']:not([data-aside-mode='overlay']) > [data-part='aside']:not([data-open]) {
628
+ display: none;
629
+ }
630
+
631
+ /**
632
+ * The pane pins its ends and scrolls its middle, the discipline the column
633
+ * already has: a pane with more content than height took its actions off the
634
+ * screen. The head holds the title and the control that closes the pane; the
635
+ * foot holds the actions that close its task. Both stand on the row inset
636
+ * plus the edge, so the title's left edge is the content card's.
637
+ */
638
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-header'],
639
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-footer'] {
640
+ align-items: center;
641
+ display: flex;
642
+ flex: 0 0 auto;
643
+ gap: var(--_row-gap);
644
+ min-block-size: var(--_rail-size);
645
+ min-inline-size: 0;
646
+ padding-block: var(--_row-inset);
647
+ padding-inline: calc(var(--_row-inset) + var(--_row-gap));
648
+ }
649
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-header'] {
650
+ font-weight: var(--slotted-font-weight-semibold, 600);
651
+ }
652
+ /** The title takes the room and the control keeps its own. */
653
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-header'] > :not([data-part]) {
654
+ flex: 1 1 auto;
655
+ min-inline-size: 0;
656
+ overflow: hidden;
657
+ text-overflow: ellipsis;
658
+ white-space: nowrap;
659
+ }
660
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-footer'] {
661
+ justify-content: flex-end;
662
+ }
663
+ /**
664
+ * The pane's body scrolls as the column's does, bar and gutter included. It
665
+ * stands on the inset the head and the foot stand on, so what it holds lines
666
+ * up with the pane's title and no consumer pads it by hand.
667
+ */
668
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-body'] {
669
+ flex: 1 1 auto;
670
+ min-block-size: 0;
671
+ min-inline-size: 0;
672
+ overflow-x: hidden;
673
+ overflow-y: auto;
674
+ overflow-inline: hidden;
675
+ overflow-block: auto;
676
+ overscroll-behavior: contain;
677
+ padding-block: var(--slotted-space-5, 0.75rem);
678
+ padding-inline: calc(var(--_row-inset) + var(--_row-gap));
679
+ scrollbar-color: var(--_scrollbar-thumb) var(--_scrollbar-track);
680
+ scrollbar-gutter: stable;
681
+ scrollbar-width: thin;
682
+ }
683
+ /* Content that brings its own inset — a page, a detail panel, a list to the edges. */
684
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-body'][data-inset='none'] {
685
+ padding: 0;
686
+ }
687
+
688
+ /**
689
+ * The pane's inner edge, which the reader moves: a focusable `separator`
690
+ * from the WAI-ARIA Window Splitter pattern. Rendering the part is the
691
+ * capability, and the column is the only arrangement it moves — a layer keeps
692
+ * its size step and a row under the content has no inline edge — so it is
693
+ * drawn nowhere here and shown by the wide query alone.
694
+ *
695
+ * A strip lying over the pane's inner edge, inside the box: the pane scrolls
696
+ * under `scroll="content"`, and a scroll container clips what stands outside
697
+ * it. The strip is the rows' own inset wide, so it covers padding and no
698
+ * content. `::before` is the reach, transparent and the WCAG 2.2 floor wide.
699
+ * At rest it draws nothing; under the hand, the drag and the keyboard its
700
+ * outer border becomes the line, and the keyboard alone adds the ring.
701
+ */
702
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-resizer'] {
703
+ --_resizer-thickness: var(--slotted-space-6, 1rem);
704
+ --_resizer-reach: var(--slotted-target-min, 24px);
705
+ background: transparent;
706
+ border: 0 solid transparent;
707
+ border-inline-start-width: var(--slotted-border-width-thick, 2px);
708
+ box-sizing: border-box;
709
+ cursor: col-resize;
710
+ display: none;
711
+ inline-size: var(--_resizer-thickness);
712
+ inset-block: 0;
713
+ inset-inline-start: 0;
714
+ position: absolute;
715
+ touch-action: none;
716
+ transition: border-color var(--_duration) var(--_easing);
717
+ z-index: 1;
718
+ }
719
+ [data-ui='app-shell'][data-nav-side='end'] > [data-part='aside'] > [data-part='aside-resizer'] {
720
+ border-inline-end-width: var(--slotted-border-width-thick, 2px);
721
+ border-inline-start-width: 0;
722
+ inset-inline-end: 0;
723
+ inset-inline-start: auto;
724
+ }
725
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-resizer']::before {
726
+ content: '';
727
+ inset-block: 0;
728
+ inset-inline: calc((var(--_resizer-reach) - var(--_resizer-thickness)) / -2);
729
+ position: absolute;
730
+ }
731
+ [data-ui='app-shell']
732
+ > [data-part='aside']
733
+ > [data-part='aside-resizer']:not([data-disabled]):hover,
734
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-resizer']:focus-visible,
735
+ [data-ui='app-shell'][data-dragging] > [data-part='aside'] > [data-part='aside-resizer'] {
736
+ border-color: var(
737
+ --slotted-app-shell-resizer-color,
738
+ var(--slotted-border-strong, ButtonBorder)
739
+ );
740
+ }
741
+ [data-ui='app-shell'] > [data-part='aside'] > [data-part='aside-resizer']:focus-visible {
742
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
743
+ outline-offset: calc(-1 * var(--slotted-focus-ring-offset, 2px));
744
+ }
745
+
746
+ /**
747
+ * The application's own footer, and the one region a product most often does
748
+ * not have: a bar across the bottom of every route costs a row of the screen
749
+ * for a version string. It exists because `contentinfo` has to be reachable
750
+ * where a product does have one — a licence, a status, an environment — and
751
+ * it takes only the height of its own line.
752
+ */
753
+ [data-ui='app-shell'] > [data-part='footer'] {
754
+ align-items: center;
755
+ background-color: var(--slotted-app-shell-footer-surface, transparent);
756
+ color: var(
757
+ --slotted-app-shell-footer-color,
758
+ var(--slotted-app-shell-color, var(--slotted-text-muted, GrayText))
759
+ );
760
+ display: flex;
761
+ font-size: var(--slotted-control-font-size-sm, 12px);
762
+ gap: var(--_row-gap);
763
+ grid-area: 4 / 1 / 5 / -1;
764
+ min-inline-size: 0;
765
+ padding-block: var(--slotted-space-3, 0.375rem);
766
+ padding-inline: calc(var(--_edge) + var(--_row-inset));
767
+ }
768
+
769
+ /**
770
+ * The application's portal root. `display: contents` leaves no box at all,
771
+ * so a layer put here is laid out by its own rules and the grid never sees
772
+ * it. What it is a containing block for is the shell rather than the
773
+ * viewport, because `container-type` contains layout.
774
+ */
775
+ [data-ui='app-shell'] > [data-part='layer'] {
776
+ display: contents;
777
+ }
778
+
779
+ /* ── The inset canvas ────────────────────────────────────────────────── */
780
+
781
+ /**
782
+ * Everything the frame holds is a box inset from it by the gutter, and the
783
+ * gutter is a *margin* rather than padding on purpose: the navigation's own
784
+ * box is then the strip a reader sees, so a rail is centred in itself rather
785
+ * than being a column with its air on one side. That was the reviewer's
786
+ * finding — the closed strip had twelve pixels to its right, where the
787
+ * content card begins, and nothing to its left.
788
+ */
789
+ [data-ui='app-shell'][data-canvas='inset'] > [data-part='nav'] {
790
+ /* Invisible on the transparent default; a product that paints the column
791
+ gets a region with the plane's corners rather than a square block. */
792
+ border-radius: var(--_radius);
793
+ margin-block: var(--_gutter);
794
+ margin-inline-start: var(--_gutter);
795
+ }
796
+ [data-ui='app-shell'][data-canvas='floating'] > [data-part='main'],
797
+ [data-ui='app-shell'][data-canvas='floating'] > [data-part='aside'],
798
+ [data-ui='app-shell'][data-canvas='inset'] > [data-part='main'],
799
+ [data-ui='app-shell'][data-canvas='inset'] > [data-part='aside'] {
800
+ border: var(--_rule-width) solid var(--_rule);
801
+ border-radius: var(--_radius);
802
+ box-shadow: var(--_elevation);
803
+ margin-block: var(--_gutter);
804
+ margin-inline: var(--_gutter);
805
+ }
806
+ [data-ui='app-shell'][data-canvas='floating'] > [data-part='aside'],
807
+ [data-ui='app-shell'][data-canvas='inset'] > [data-part='aside'] {
808
+ margin-inline-start: 0;
809
+ }
810
+ [data-ui='app-shell'][data-canvas='floating'] > [data-part='footer'],
811
+ [data-ui='app-shell'][data-canvas='panel'] > [data-part='footer'],
812
+ [data-ui='app-shell'][data-canvas='inset'] > [data-part='footer'] {
813
+ padding-block-start: 0;
814
+ }
815
+ /**
816
+ * The card is a wide screen's arrangement. On a phone, where the navigation
817
+ * has become a layer, a gutter on every side and rounded corners against the
818
+ * screen's own frame a column of content that has no room to spare. The
819
+ * content then meets the frame's edges and keeps one rule, under the banner,
820
+ * for the seam the gutter used to draw. Keyed on the mode, as every layer
821
+ * rule is; the width answers only for a shell not yet measured, below.
822
+ */
823
+ [data-ui='app-shell'][data-canvas='floating'][data-nav-mode='overlay'] > [data-part='main'],
824
+ [data-ui='app-shell'][data-canvas='inset'][data-nav-mode='overlay'] > [data-part='main'] {
825
+ border-block-end: 0;
826
+ border-inline: 0;
827
+ border-radius: 0;
828
+ box-shadow: none;
829
+ margin: 0;
830
+ }
831
+
832
+ /* ── The flush canvas ────────────────────────────────────────────────── */
833
+
834
+ /**
835
+ * The plane turned inside out: the regions carry the surface and the content
836
+ * carries the ground. Stated as the same six properties, so a product that
837
+ * repaints one region keeps its choice under either plane.
838
+ */
839
+ [data-ui='app-shell'][data-canvas='flush'] {
840
+ --_edge: var(--_row-inset);
841
+ --_nav-edges: var(--_seam-width);
842
+ --_nav-inset: var(--_row-inset);
843
+ }
844
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='banner'] {
845
+ background-color: var(
846
+ --slotted-app-shell-banner-surface,
847
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
848
+ );
849
+ }
850
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='nav'] {
851
+ background-color: var(
852
+ --slotted-app-shell-nav-surface,
853
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
854
+ );
855
+ }
856
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='main'] {
857
+ background-color: var(--slotted-app-shell-main-surface, transparent);
858
+ }
859
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='aside'] {
860
+ background-color: var(
861
+ --slotted-app-shell-aside-surface,
862
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
863
+ );
864
+ }
865
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='footer'] {
866
+ background-color: var(
867
+ --slotted-app-shell-footer-surface,
868
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
869
+ );
870
+ }
871
+ /**
872
+ * Block air, the gutter; inline air, the row inset on both sides, from
873
+ * `--_nav-inset`. A padding on one side alone is what put the closed strip
874
+ * off-centre on this plane once; none at all put every row's ground against
875
+ * the screen's edge and against the seam.
876
+ */
877
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='nav'] {
878
+ padding-block: var(--_gutter);
879
+ }
880
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='banner'] {
881
+ border-block-end: var(--_seam);
882
+ }
883
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='footer'] {
884
+ border-block-start: var(--_seam);
885
+ }
886
+ [data-ui='app-shell'][data-canvas='flush'] > [data-part='aside'] {
887
+ border-block-start: var(--_seam);
888
+ }
889
+ /* The seams inside a region: wherever regions touch without a gutter, which
890
+ is every seam of `flush` and every seam inside the `panel` card. */
891
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])
892
+ > [data-part='nav']
893
+ > [data-part='nav-footer'] {
894
+ border-block-start: var(--_seam);
895
+ }
896
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])
897
+ > [data-part='aside']
898
+ > [data-part='aside-header'] {
899
+ border-block-end: var(--_seam);
900
+ }
901
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])
902
+ > [data-part='aside']
903
+ > [data-part='aside-footer'] {
904
+ border-block-start: var(--_seam);
905
+ }
906
+
907
+ /* ── The floating canvas ─────────────────────────────────────────────── */
908
+
909
+ /**
910
+ * The inset plane with the navigation lifted onto a card of its own: two
911
+ * cards on the sunken ground, a gutter apart, each with the edge, the radius
912
+ * and the elevation the content card has. The margin is the inset plane's,
913
+ * so the strip a reader sees is still the region's own box. A rail is one
914
+ * mark column and a row's inset each side, and the card's edge is added to
915
+ * it rather than taken from the marks.
916
+ */
917
+ [data-ui='app-shell'][data-canvas='floating'] > [data-part='nav'] {
918
+ background-color: var(
919
+ --slotted-app-shell-nav-surface,
920
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
921
+ );
922
+ border: var(--_rule-width) solid var(--_rule);
923
+ border-radius: var(--_radius);
924
+ box-shadow: var(--_elevation);
925
+ margin-block: var(--_gutter);
926
+ margin-inline-start: var(--_gutter);
927
+ }
928
+ /* A card has an edge the column never had: the rows stand one inset in, as
929
+ they do on the overlay card, and the bar's rows stand over them. */
930
+ [data-ui='app-shell'][data-canvas='floating'] {
931
+ --_edge: calc(var(--_gutter) + var(--_rule-width) + var(--_row-inset));
932
+ --_nav-edges: calc(2 * var(--_rule-width));
933
+ --_nav-inset: var(--_row-inset);
934
+ }
935
+ [data-ui='app-shell'][data-canvas='floating'][data-nav-side='end'] > [data-part='nav'] {
936
+ margin-inline: 0 var(--_gutter);
937
+ }
938
+
939
+ /* ── A card over more than one region ────────────────────────────────── */
940
+
941
+ /**
942
+ * The card that holds the banner and the content under `primary="main"`,
943
+ * and every region under `panel`, is one box: the root's `::before`, placed
944
+ * on the grid area the regions share. Two boxes with half a card each drew
945
+ * the shadow of one across the other at the seam. The pseudo-element is the
946
+ * grid's first item, so it paints under every region, and the regions over
947
+ * it keep their own grounds — clipped at the card's corners where their
948
+ * corners are the card's, so a sticky header scrolling inside the content
949
+ * does not square off the card's rounded edge.
950
+ *
951
+ * A layer is a phone's arrangement, and a phone has no room for a frame: the
952
+ * card goes wherever the navigation is a layer, as the inset card does.
953
+ */
954
+ [data-ui='app-shell']:is(
955
+ [data-canvas='floating'],
956
+ [data-canvas='inset']
957
+ )[data-primary='main']::before,
958
+ [data-ui='app-shell'][data-canvas='panel']::before {
959
+ background-color: var(
960
+ --slotted-app-shell-main-surface,
961
+ var(--slotted-app-shell-canvas, var(--slotted-surface))
962
+ );
963
+ border: var(--_rule-width) solid var(--_rule);
964
+ border-radius: var(--_radius);
965
+ box-shadow: var(--_elevation);
966
+ content: '';
967
+ grid-area: 1 / 2 / 3 / 3;
968
+ margin: var(--_gutter);
969
+ min-inline-size: 0;
970
+ }
971
+ [data-ui='app-shell'][data-canvas='panel']::before {
972
+ grid-area: 2 / 1 / 3 / -1;
973
+ margin: 0;
974
+ }
975
+ [data-ui='app-shell'][data-canvas='panel']:is(
976
+ [data-primary='nav'],
977
+ [data-primary='main']
978
+ )::before {
979
+ grid-area: 1 / 1 / 3 / -1;
980
+ }
981
+ [data-ui='app-shell'][data-nav-mode='overlay'][data-canvas]::before {
982
+ content: none;
983
+ }
984
+
985
+ /* ── The banner in the plane of the content ──────────────────────────── */
986
+
987
+ /**
988
+ * The banner and the content are one card: the banner takes the card's top
989
+ * edge and sides and draws no ground of its own, the content takes the rest
990
+ * and keeps its ground with the card's lower corners. Between them is a
991
+ * seam, and `separation` decides it.
992
+ */
993
+ [data-ui='app-shell']:is([data-canvas='floating'], [data-canvas='inset'])[data-primary='main']
994
+ > [data-part='banner'] {
995
+ margin-block-start: calc(var(--_gutter) + var(--_rule-width));
996
+ margin-inline: calc(var(--_gutter) + var(--_rule-width));
997
+ padding-inline: 0;
998
+ }
999
+ [data-ui='app-shell']:is([data-canvas='floating'], [data-canvas='inset'])[data-primary='main']
1000
+ > [data-part='main'] {
1001
+ border: none;
1002
+ border-radius: 0 0 var(--_inner-radius) var(--_inner-radius);
1003
+ box-shadow: none;
1004
+ margin-block: 0 calc(var(--_gutter) + var(--_rule-width));
1005
+ margin-inline: calc(var(--_gutter) + var(--_rule-width));
1006
+ }
1007
+ [data-ui='app-shell'][data-primary='main'] > [data-part='banner'],
1008
+ [data-ui='app-shell'][data-primary='main'][data-nav-side='end'] > [data-part='banner'],
1009
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='nav'] > [data-part='banner'],
1010
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='nav'][data-nav-side='end']
1011
+ > [data-part='banner'] {
1012
+ border-block-end: var(--_seam);
1013
+ grid-column: 2 / 3;
1014
+ }
1015
+ [data-ui='app-shell'][data-primary='main'][data-nav-mode='overlay'] > [data-part='banner'] {
1016
+ margin: 0;
1017
+ padding-inline: var(--_edge);
1018
+ }
1019
+ [data-ui='app-shell']:is(
1020
+ [data-canvas='floating'],
1021
+ [data-canvas='inset']
1022
+ )[data-primary='main'][data-nav-mode='overlay']
1023
+ > [data-part='main'] {
1024
+ border-radius: 0;
1025
+ margin: 0;
1026
+ }
1027
+
1028
+ /* ── The panel canvas ────────────────────────────────────────────────── */
1029
+
1030
+ /**
1031
+ * One card holds every region but the footer, and the banner too once the
1032
+ * column or the content is primary. The frame's air is the root's padding,
1033
+ * so the grid's tracks are the card's inside and no region has to repeat a
1034
+ * margin the card has: the regions meet each other at seams, and the seams
1035
+ * are `separation`'s. The banner above the card stands on the frame at the
1036
+ * card's own edge, which is why the edge is nought here as under `flush`.
1037
+ *
1038
+ * The padding is dropped where the navigation is a layer. A shell not yet
1039
+ * measured below the compact width keeps it for one frame: the root cannot
1040
+ * query its own width, and that frame is the cost.
1041
+ */
1042
+ [data-ui='app-shell'][data-canvas='panel'] {
1043
+ --_edge: calc(var(--_rule-width) + var(--_row-inset));
1044
+ --_nav-edges: var(--_seam-width);
1045
+ --_nav-inset: var(--_row-inset);
1046
+ }
1047
+ /* Once the navigation is a layer the card beside the bar is gone, and the
1048
+ bar's rows stand where the inset plane's do. */
1049
+ [data-ui='app-shell'][data-canvas='floating'][data-nav-mode='overlay'],
1050
+ [data-ui='app-shell'][data-canvas='panel'][data-nav-mode='overlay'] {
1051
+ --_edge: var(--_gutter);
1052
+ }
1053
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']) {
1054
+ box-sizing: border-box;
1055
+ padding-block-end: var(--_gutter);
1056
+ padding-inline: var(--_gutter);
1057
+ }
1058
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']):is(
1059
+ [data-primary='nav'],
1060
+ [data-primary='main']
1061
+ ) {
1062
+ padding-block-start: var(--_gutter);
1063
+ }
1064
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']) > [data-part='nav'] {
1065
+ border-end-start-radius: var(--_inner-radius);
1066
+ border-start-start-radius: var(--_inner-radius);
1067
+ margin: var(--_rule-width) 0 var(--_rule-width) var(--_rule-width);
1068
+ }
1069
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay'])[data-nav-side='end']
1070
+ > [data-part='nav'] {
1071
+ border-radius: 0 var(--_inner-radius) var(--_inner-radius) 0;
1072
+ margin: var(--_rule-width) var(--_rule-width) var(--_rule-width) 0;
1073
+ }
1074
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']) > [data-part='main'] {
1075
+ border-end-end-radius: var(--_inner-radius);
1076
+ margin-block-end: var(--_rule-width);
1077
+ margin-inline-end: var(--_rule-width);
1078
+ }
1079
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='banner']:not([data-nav-mode='overlay'])
1080
+ > [data-part='main'] {
1081
+ border-start-end-radius: var(--_inner-radius);
1082
+ margin-block-start: var(--_rule-width);
1083
+ }
1084
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']):not(
1085
+ [data-primary='banner']
1086
+ )
1087
+ > [data-part='banner'] {
1088
+ margin-block-start: var(--_rule-width);
1089
+ margin-inline-end: var(--_rule-width);
1090
+ }
1091
+ /* The corner the content shares with the card is the pane's once the pane
1092
+ stands beside it. */
1093
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']):has(
1094
+ > [data-part='aside'][data-open]
1095
+ )
1096
+ > [data-part='main'] {
1097
+ border-end-end-radius: 0;
1098
+ border-start-end-radius: 0;
1099
+ margin-inline-end: 0;
1100
+ }
1101
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']):has(
1102
+ > [data-part='aside'][data-open]
1103
+ )
1104
+ > [data-part='banner'] {
1105
+ margin-inline-end: 0;
1106
+ }
1107
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']):not(
1108
+ [data-aside-mode='overlay']
1109
+ )
1110
+ > [data-part='aside'] {
1111
+ border-end-end-radius: var(--_inner-radius);
1112
+ border-start-end-radius: var(--_inner-radius);
1113
+ margin: var(--_rule-width) var(--_rule-width) var(--_rule-width) 0;
1114
+ }
1115
+ [data-ui='app-shell'][data-canvas='panel'][data-nav-mode='overlay'] > [data-part='main'] {
1116
+ border-block-start: var(--_rule-width) solid var(--_rule);
1117
+ }
1118
+
1119
+ /* ── The line the wait is drawn on ───────────────────────────────────── */
1120
+
1121
+ /**
1122
+ * Where the line between the chrome and the content is the banner's seam
1123
+ * rather than an edge of `main`, the frame of the wait stands that seam's
1124
+ * width higher, so the bar covers the line instead of sitting under it:
1125
+ * every seam of `flush`, the seam inside the card of `primary="main"`, and
1126
+ * the seam inside the `panel` card when the banner is in it. Above a
1127
+ * `panel` card it is the card's own top edge. Under `separation="tone"`
1128
+ * the seam is nought pixels wide and so is the lift. Where `main` keeps an
1129
+ * edge of its own — the inset and floating cards, and the top rule `panel`
1130
+ * gives it once the navigation is a layer — the frame is the box of `main`
1131
+ * and covers that edge already.
1132
+ */
1133
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-primary='main']),
1134
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='nav'] {
1135
+ --_progress-lift: var(--_seam-width);
1136
+ }
1137
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='banner'] {
1138
+ --_progress-lift: var(--_rule-width);
1139
+ }
1140
+ [data-ui='app-shell'][data-canvas='panel'][data-nav-mode='overlay'] {
1141
+ --_progress-lift: 0px;
1142
+ }
1143
+
1144
+ /* ── The frame of the wait, on the box of the content ────────────────── */
1145
+
1146
+ /**
1147
+ * Every margin and every corner `main` takes above, restated for the frame
1148
+ * in the same order and at the same specificity, so the frame is the box of
1149
+ * `main` in every arrangement and the bar on its edge is clipped by the
1150
+ * card's own corners. Only the geometry: the frame draws no edge, no ground
1151
+ * and no lift. The two twins of a shell not yet measured stand beside
1152
+ * their originals in the narrow query below. `app-shell.styles.test.mjs`
1153
+ * holds every rule that gives `main` a margin or a radius to its twin.
1154
+ */
1155
+ [data-ui='app-shell'][data-canvas='floating'] > [data-part='progress'],
1156
+ [data-ui='app-shell'][data-canvas='inset'] > [data-part='progress'] {
1157
+ border-radius: var(--_radius);
1158
+ margin-block: var(--_gutter);
1159
+ margin-inline: var(--_gutter);
1160
+ }
1161
+ [data-ui='app-shell'][data-canvas='floating'][data-nav-mode='overlay'] > [data-part='progress'],
1162
+ [data-ui='app-shell'][data-canvas='inset'][data-nav-mode='overlay'] > [data-part='progress'] {
1163
+ border-radius: 0;
1164
+ margin: 0;
1165
+ }
1166
+ [data-ui='app-shell']:is([data-canvas='floating'], [data-canvas='inset'])[data-primary='main']
1167
+ > [data-part='progress'] {
1168
+ border-radius: 0 0 var(--_inner-radius) var(--_inner-radius);
1169
+ margin-block: 0 calc(var(--_gutter) + var(--_rule-width));
1170
+ margin-inline: calc(var(--_gutter) + var(--_rule-width));
1171
+ }
1172
+ [data-ui='app-shell']:is(
1173
+ [data-canvas='floating'],
1174
+ [data-canvas='inset']
1175
+ )[data-primary='main'][data-nav-mode='overlay']
1176
+ > [data-part='progress'] {
1177
+ border-radius: 0;
1178
+ margin: 0;
1179
+ }
1180
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay'])
1181
+ > [data-part='progress'] {
1182
+ border-end-end-radius: var(--_inner-radius);
1183
+ margin-block-end: var(--_rule-width);
1184
+ margin-inline-end: var(--_rule-width);
1185
+ }
1186
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='banner']:not([data-nav-mode='overlay'])
1187
+ > [data-part='progress'] {
1188
+ border-start-end-radius: var(--_inner-radius);
1189
+ margin-block-start: var(--_rule-width);
1190
+ }
1191
+ [data-ui='app-shell'][data-canvas='panel']:not([data-nav-mode='overlay']):has(
1192
+ > [data-part='aside'][data-open]
1193
+ )
1194
+ > [data-part='progress'] {
1195
+ border-end-end-radius: 0;
1196
+ border-start-end-radius: 0;
1197
+ margin-inline-end: 0;
1198
+ }
1199
+ /* ── Separation by tone ──────────────────────────────────────────────── */
1200
+
1201
+ /**
1202
+ * No line: the chrome regions take a ground of their own and the content
1203
+ * keeps the surface. The tone is 4.5 per cent of the ink over the surface,
1204
+ * 0.036 OKLab L from it in light and in dark; the two page surfaces this
1205
+ * arrangement used to rely on are 0.02 apart, which read as nothing.
1206
+ *
1207
+ * A ground swapped without its tones leaves every control calibrated against
1208
+ * a page it is not on: the subtle hover a destination and the disclosure
1209
+ * control draw was the tone itself, and pressing a row changed nothing.
1210
+ * Inside a toned region the pair is recomputed from the region's ground, and
1211
+ * every control in it reads the pair it now stands on. The chain is written
1212
+ * at the region, for the reason the palette above is: a `var()` resolves
1213
+ * where it is declared, and a region carrying a scheme of its own must mix
1214
+ * its own ink.
1215
+ */
1216
+ [data-ui='app-shell'][data-separation='tone'] {
1217
+ --_seam: none;
1218
+ --_seam-width: 0px;
1219
+ }
1220
+ [data-ui='app-shell'][data-canvas='flush'][data-separation='tone'] > [data-part='footer'] {
1221
+ --_ground: var(
1222
+ --slotted-app-shell-footer-surface,
1223
+ var(
1224
+ --slotted-app-shell-tone-surface,
1225
+ color-mix(in oklab, var(--slotted-surface), var(--slotted-text) 4.5%)
1226
+ )
1227
+ );
1228
+ }
1229
+ [data-ui='app-shell'][data-canvas='flush'][data-separation='tone']:not([data-primary='main'])
1230
+ > [data-part='banner'] {
1231
+ --_ground: var(
1232
+ --slotted-app-shell-banner-surface,
1233
+ var(
1234
+ --slotted-app-shell-tone-surface,
1235
+ color-mix(in oklab, var(--slotted-surface), var(--slotted-text) 4.5%)
1236
+ )
1237
+ );
1238
+ }
1239
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])[data-separation='tone']
1240
+ > [data-part='nav'] {
1241
+ --_ground: var(
1242
+ --slotted-app-shell-nav-surface,
1243
+ var(
1244
+ --slotted-app-shell-tone-surface,
1245
+ color-mix(in oklab, var(--slotted-surface), var(--slotted-text) 4.5%)
1246
+ )
1247
+ );
1248
+ }
1249
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])[data-separation='tone']
1250
+ > [data-part='aside'] {
1251
+ --_ground: var(
1252
+ --slotted-app-shell-aside-surface,
1253
+ var(
1254
+ --slotted-app-shell-tone-surface,
1255
+ color-mix(in oklab, var(--slotted-surface), var(--slotted-text) 4.5%)
1256
+ )
1257
+ );
1258
+ }
1259
+ [data-ui='app-shell'][data-separation='tone'] > [data-part='nav'] > [data-part='nav-rail'] {
1260
+ --_ground: var(
1261
+ --slotted-app-shell-tone-surface,
1262
+ color-mix(in oklab, var(--slotted-surface), var(--slotted-text) 4.5%)
1263
+ );
1264
+ }
1265
+ [data-ui='app-shell'][data-canvas='flush'][data-separation='tone']:not([data-primary='main'])
1266
+ > [data-part='banner'],
1267
+ [data-ui='app-shell'][data-canvas='flush'][data-separation='tone'] > [data-part='footer'],
1268
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])[data-separation='tone']
1269
+ > :is([data-part='nav'], [data-part='aside']),
1270
+ [data-ui='app-shell'][data-separation='tone'] > [data-part='nav'] > [data-part='nav-rail'] {
1271
+ --slotted-tone-secondary-subtle-hover: color-mix(
1272
+ in oklab,
1273
+ var(--_ground),
1274
+ var(--slotted-text) 4%
1275
+ );
1276
+ --slotted-tone-secondary-subtle-active: color-mix(
1277
+ in oklab,
1278
+ var(--_ground),
1279
+ var(--slotted-text) 7%
1280
+ );
1281
+
1282
+ background-color: var(--_ground);
1283
+ }
1284
+ /* The content keeps the surface under a toned frame: on the flush plane it
1285
+ carried the sunken ground, which is 0.014 from the tone. */
1286
+ [data-ui='app-shell'][data-canvas='flush'][data-separation='tone'] > [data-part='main'] {
1287
+ background-color: var(
1288
+ --slotted-app-shell-main-surface,
1289
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
1290
+ );
1291
+ }
1292
+
1293
+ /* ── A rail of its own ───────────────────────────────────────────────── */
1294
+
1295
+ /**
1296
+ * A strip of marks before the column: the first level of a two-level
1297
+ * navigation, inside the one navigation region and under its one open state.
1298
+ * The region becomes two columns, the strip at the rail's width and the
1299
+ * column at the navigation's, and closing to `rail` narrows the region to
1300
+ * the strip: the column is cut by the region's own overflow while the width
1301
+ * moves, and leaves the tab order and the accessibility tree at the end of
1302
+ * the move — `visibility` steps at the end on the way out and at the start
1303
+ * on the way in. `:has()` recognises the part, so no script writes an
1304
+ * attribute and a server render has the grid on its first paint.
1305
+ */
1306
+ [data-ui='app-shell'] > [data-part='nav']:has(> [data-part='nav-rail']) {
1307
+ display: grid;
1308
+ grid-template-columns: var(--_rail-size) minmax(var(--_nav-size), 1fr);
1309
+ grid-template-rows: auto minmax(0, 1fr) auto;
1310
+ }
1311
+ [data-ui='app-shell'] > [data-part='nav'] > [data-part='nav-rail'] {
1312
+ align-items: center;
1313
+ border-inline-end: var(--_seam);
1314
+ box-sizing: border-box;
1315
+ display: flex;
1316
+ flex-direction: column;
1317
+ gap: var(--slotted-space-1, 0.125rem);
1318
+ grid-area: 1 / 1 / -1 / 2;
1319
+ min-block-size: 0;
1320
+ overflow: auto;
1321
+ overscroll-behavior: contain;
1322
+ padding-block: var(--_row-inset);
1323
+ scrollbar-width: none;
1324
+ }
1325
+ [data-ui='app-shell']
1326
+ > [data-part='nav']:has(> [data-part='nav-rail'])
1327
+ > [data-part='nav-header'] {
1328
+ grid-area: 1 / 2 / 2 / 3;
1329
+ }
1330
+ [data-ui='app-shell'] > [data-part='nav']:has(> [data-part='nav-rail']) > [data-part='nav-body'] {
1331
+ grid-area: 2 / 2 / 3 / 3;
1332
+ }
1333
+ [data-ui='app-shell']
1334
+ > [data-part='nav']:has(> [data-part='nav-rail'])
1335
+ > [data-part='nav-footer'] {
1336
+ grid-area: 3 / 2 / 4 / 3;
1337
+ }
1338
+ /* The strip's rule is an edge, and a row whose ground meets it reads as a
1339
+ column with no padding. */
1340
+ [data-ui='app-shell']
1341
+ > [data-part='nav']:has(> [data-part='nav-rail'])
1342
+ > :is([data-part='nav-header'], [data-part='nav-body'], [data-part='nav-footer']) {
1343
+ padding-inline-start: var(--_row-inset);
1344
+ transition: visibility var(--_duration) step-start;
1345
+ }
1346
+ [data-ui='app-shell'][data-nav-mode='inline'][data-open]
1347
+ > [data-part='nav']:has(> [data-part='nav-rail']) {
1348
+ inline-size: calc(var(--_rail-size) + var(--_nav-size) + var(--_nav-edges));
1349
+ }
1350
+ /* Closed, the region is the strip and its edges: the strip holds a row's
1351
+ inset on each side of its marks already. */
1352
+ [data-ui='app-shell'][data-nav-mode='inline']:not([data-open])
1353
+ > [data-part='nav']:has(> [data-part='nav-rail']) {
1354
+ inline-size: calc(var(--_rail-size) + var(--_nav-edges));
1355
+ }
1356
+ [data-ui='app-shell'][data-nav-mode='inline']:not([data-open])
1357
+ > [data-part='nav']:has(> [data-part='nav-rail'])
1358
+ > :is([data-part='nav-header'], [data-part='nav-body'], [data-part='nav-footer']) {
1359
+ transition: visibility var(--_duration) step-end;
1360
+ visibility: hidden;
1361
+ }
1362
+ /* A closed strip is the region's edge, and the edge is the plane's to draw. */
1363
+ [data-ui='app-shell'][data-nav-mode='inline']:not([data-open])
1364
+ > [data-part='nav']
1365
+ > [data-part='nav-rail'] {
1366
+ border-inline-end: none;
1367
+ }
1368
+
1369
+ /* ── Forced colours ──────────────────────────────────────────────────── */
1370
+
1371
+ /**
1372
+ * Every ground is `Canvas` here, so a tone separates nothing: the seams are
1373
+ * drawn whatever `separation` says. The theme cannot reach a boundary the
1374
+ * family draws with a ground, which is the case the briefing allows a family
1375
+ * a forced-colours rule of its own for.
1376
+ */
1377
+ @media (forced-colors: active) {
1378
+ [data-ui='app-shell'][data-separation='tone'] {
1379
+ --_seam: var(--_rule-width) solid CanvasText;
1380
+ --_seam-width: var(--_rule-width);
1381
+ }
1382
+ }
1383
+
1384
+ /* ── The disclosure controls ─────────────────────────────────────────── */
1385
+
1386
+ /**
1387
+ * The mark draws the region it toggles: a frame with a bar down one side,
1388
+ * and a chevron saying which way pressing sends it. Three bars would say
1389
+ * "a menu is here" and say nothing about which menu or where it goes; a
1390
+ * double chevron says "all the way to the end", which is what `pagination`
1391
+ * draws it for. Both were on this control before this one was.
1392
+ *
1393
+ * Its box is the row's mark column, so the control in the bar stands over
1394
+ * the marks in the navigation rather than three pixels off them.
1395
+ *
1396
+ * The pane's control is the same control: the same box, the same ink, the
1397
+ * same pair of marks. What differs is which way the artwork faces, below.
1398
+ */
1399
+ [data-ui='app-shell'] [data-part='nav-toggle'],
1400
+ [data-ui='app-shell'] [data-part='aside-toggle'] {
1401
+ --slotted-icon-size: var(--slotted-control-icon-size-md, 16px);
1402
+
1403
+ background-color: transparent;
1404
+ block-size: var(--_row-mark);
1405
+ border: none;
1406
+ border-radius: var(--slotted-control-radius, 4px);
1407
+ /**
1408
+ * Quieter than the region it stands in, and *of* it: the control appears
1409
+ * in the bar and in the column's foot, and either may have been repainted.
1410
+ * Read from a token instead, it kept the light scheme's ink over a dark
1411
+ * column and the mark disappeared.
1412
+ */
1413
+ color: color-mix(in oklab, currentcolor 70%, transparent);
1414
+ cursor: pointer;
1415
+ display: inline-grid;
1416
+ flex: 0 0 auto;
1417
+ inline-size: var(--_row-mark);
1418
+ padding: 0;
1419
+ place-items: center;
1420
+ transition:
1421
+ background-color var(--slotted-control-transition-duration)
1422
+ var(--slotted-control-transition-easing, cubic-bezier(0.2, 0, 0, 1)),
1423
+ color var(--slotted-control-transition-duration)
1424
+ var(--slotted-control-transition-easing, cubic-bezier(0.2, 0, 0, 1));
1425
+ }
1426
+
1427
+ /**
1428
+ * The artwork is the `panel-open` or `panel-close` Glyph element, chosen by
1429
+ * the binding from the region's state. It is direction-sensitive — the bar
1430
+ * is on one side of the frame and the chevron points along the line — so
1431
+ * the family turns it whole wherever the region is not at the start of the
1432
+ * line. The pane is on the end the navigation is not, so its control is the
1433
+ * navigation's turned, and turns back under the same conditions the
1434
+ * navigation's turns. The box is not `directional`: which side the region
1435
+ * stands on is this family's fact, not the reading direction's alone.
1436
+ */
1437
+ [data-ui='app-shell'][data-nav-side='end'] [data-part='nav-toggle'] > [data-ui='icon'],
1438
+ [dir='rtl'] [data-ui='app-shell'] [data-part='nav-toggle'] > [data-ui='icon'],
1439
+ [data-ui='app-shell']:not([data-nav-side='end']) [data-part='aside-toggle'] > [data-ui='icon'],
1440
+ [dir='rtl']
1441
+ [data-ui='app-shell'][data-nav-side='end']
1442
+ [data-part='aside-toggle']
1443
+ > [data-ui='icon'] {
1444
+ scale: -1 1;
1445
+ }
1446
+ [dir='rtl']
1447
+ [data-ui='app-shell'][data-nav-side='end']
1448
+ [data-part='nav-toggle']
1449
+ > [data-ui='icon'],
1450
+ [dir='rtl']
1451
+ [data-ui='app-shell']:not([data-nav-side='end'])
1452
+ [data-part='aside-toggle']
1453
+ > [data-ui='icon'] {
1454
+ scale: 1 1;
1455
+ }
1456
+
1457
+ [data-ui='app-shell'] [data-part='nav-toggle']:enabled:hover,
1458
+ [data-ui='app-shell'] [data-part='aside-toggle']:enabled:hover {
1459
+ background-color: var(--slotted-tone-secondary-subtle-hover, ButtonFace);
1460
+ color: currentcolor;
1461
+ }
1462
+ [data-ui='app-shell'] [data-part='nav-toggle']:enabled:active,
1463
+ [data-ui='app-shell'] [data-part='aside-toggle']:enabled:active {
1464
+ background-color: var(--slotted-tone-secondary-subtle-active, ButtonFace);
1465
+ }
1466
+ [data-ui='app-shell'] [data-part='nav-toggle']:focus-visible,
1467
+ [data-ui='app-shell'] [data-part='aside-toggle']:focus-visible {
1468
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
1469
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
1470
+ }
1471
+
1472
+ /* ── The navigation as a column ──────────────────────────────────────── */
1473
+
1474
+ /**
1475
+ * Keyed on the mode and not on the width, because a consumer may state the
1476
+ * mode at any width. The container query further down is the fallback for
1477
+ * the frame before the binding has measured, and nothing else.
1478
+ */
1479
+ [data-ui='app-shell'][data-nav-mode='inline'] > [data-part='nav'] {
1480
+ inline-size: calc(var(--_rail-size) + 2 * var(--_nav-inset) + var(--_nav-edges));
1481
+ }
1482
+ [data-ui='app-shell'][data-nav-mode='inline'][data-open] > [data-part='nav'] {
1483
+ inline-size: var(--_nav-size);
1484
+ }
1485
+ /**
1486
+ * The rail centres its marks, all three rows together: a head centred over a
1487
+ * body that is not reads as two columns in one strip.
1488
+ */
1489
+ [data-ui='app-shell'][data-nav-mode='inline']:not([data-open])
1490
+ > [data-part='nav']
1491
+ > [data-part='nav-header'],
1492
+ [data-ui='app-shell'][data-nav-mode='inline']:not([data-open])
1493
+ > [data-part='nav']
1494
+ > [data-part='nav-footer'] {
1495
+ /* Two marks in one end of a rail — the control and the account — stand
1496
+ one over the other: the rail is one mark column wide, so a row of two
1497
+ is a row that overflows it. */
1498
+ flex-wrap: wrap;
1499
+ justify-content: center;
1500
+ }
1501
+ [data-ui='app-shell'][data-nav-mode='inline']:not([data-open])
1502
+ > [data-part='nav']
1503
+ > [data-part='nav-body'] {
1504
+ align-items: center;
1505
+ }
1506
+ /**
1507
+ * Nothing left behind: the region keeps no measure and no tab stop. Mantine
1508
+ * and Ant leave a zero-width navbar in the flow and `Tab` still crosses every
1509
+ * destination in it. `display: none` rather than a width of nought, because a
1510
+ * region at nought pixels still lays its children out and puts content
1511
+ * outside the box that owns it — `content-visibility: hidden` was tried and
1512
+ * preserves the last measured size instead. The cost is written: closing to
1513
+ * `hidden` does not animate, because it is a removal rather than a change of
1514
+ * width.
1515
+ */
1516
+ [data-ui='app-shell'][data-nav-mode='inline'][data-collapsed-to='hidden']:not([data-open])
1517
+ > [data-part='nav'] {
1518
+ display: none;
1519
+ }
1520
+
1521
+ [data-ui='app-shell'][data-nav-side='end'] > [data-part='nav'] {
1522
+ grid-area: 2 / 3 / 3 / 4;
1523
+ }
1524
+ [data-ui='app-shell'][data-canvas='inset'][data-nav-side='end'] > [data-part='nav'] {
1525
+ margin-inline: 0 var(--_gutter);
1526
+ }
1527
+ [data-ui='app-shell'][data-canvas='flush'][data-nav-side='end'] > [data-part='nav'] {
1528
+ padding-inline: 0 var(--_gutter);
1529
+ }
1530
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel']) > [data-part='nav'] {
1531
+ border-inline-end: var(--_seam);
1532
+ }
1533
+ [data-ui='app-shell']:is([data-canvas='flush'], [data-canvas='panel'])[data-nav-side='end']
1534
+ > [data-part='nav'] {
1535
+ border-inline-end: none;
1536
+ border-inline-start: var(--_seam);
1537
+ }
1538
+
1539
+ /** Reaching the corner: the navigation spans every row instead. */
1540
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main']) > [data-part='nav'] {
1541
+ grid-area: 1 / 1 / -1 / 2;
1542
+ }
1543
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main'])[data-nav-side='end']
1544
+ > [data-part='nav'] {
1545
+ grid-area: 1 / 3 / -1 / 4;
1546
+ }
1547
+ /**
1548
+ * The navigation's own head takes the banner's height, so the workspace mark
1549
+ * and whatever the bar carries sit on one line rather than being two headers
1550
+ * of different sizes in the same row of the screen.
1551
+ */
1552
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main'])
1553
+ > [data-part='nav']
1554
+ > [data-part='nav-header'] {
1555
+ /* The head's own padding is inside the banner's height, or the mark stands
1556
+ its padding lower than the bar beside it. */
1557
+ box-sizing: border-box;
1558
+ min-block-size: var(--_banner-size);
1559
+ }
1560
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main']) > [data-part='banner'],
1561
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main']) > [data-part='footer'] {
1562
+ grid-column: 2 / -1;
1563
+ }
1564
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main'])[data-nav-side='end']
1565
+ > [data-part='banner'],
1566
+ [data-ui='app-shell']:is([data-primary='nav'], [data-primary='main'])[data-nav-side='end']
1567
+ > [data-part='footer'] {
1568
+ grid-column: 1 / -2;
1569
+ }
1570
+ /**
1571
+ * The banner in the plane of the content stands over the content's column
1572
+ * alone: the pane beside it, as a column, spans the banner's row. Under
1573
+ * `panel` the banner is inside the card whether the column or the content
1574
+ * is primary, so `nav` draws what `main` draws there.
1575
+ */
1576
+ [data-ui='app-shell'][data-primary='main'] > [data-part='banner'],
1577
+ [data-ui='app-shell'][data-canvas='panel'][data-primary='nav'] > [data-part='banner'] {
1578
+ grid-column: 2 / 3;
1579
+ }
1580
+
1581
+ /* ── The navigation as a layer ───────────────────────────────────────── */
1582
+
1583
+ /**
1584
+ * Out of flow, so the track it was in collapses on its own and the root's
1585
+ * template is never rewritten.
1586
+ *
1587
+ * `grid-area: 2 / 1 / -1 / -1` is what makes the panel stop under the banner
1588
+ * and be the height of everything below it. An absolutely positioned grid
1589
+ * child placed into a definite area takes *that area* as its containing
1590
+ * block, not the grid's padding box — so the rows say where the panel ends
1591
+ * without anyone measuring the banner, and a banner that wraps to two lines
1592
+ * moves the panel with it.
1593
+ *
1594
+ * The alternative is a sheet over everything, which Material draws and which
1595
+ * puts the veil over the bar as well: the reader then has no fixed point on
1596
+ * the screen at all.
1597
+ */
1598
+ [data-ui='app-shell'][data-nav-mode='overlay'] > [data-part='nav'] {
1599
+ /* A layer has an edge on every plane, so its rows stand one inset in. The
1600
+ inset is read by the parts, which take it from here. */
1601
+ --_nav-inset: var(--_row-inset);
1602
+
1603
+ /* A layer over the content is opaque or it is unreadable, so the plane's
1604
+ transparent default does not reach it — the product's own choice still
1605
+ does, which is why this is the property and not the colour. */
1606
+ background-color: var(
1607
+ --slotted-app-shell-nav-surface,
1608
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
1609
+ );
1610
+ border-block-start: var(--_rule-width) solid var(--_rule);
1611
+ box-shadow: var(--slotted-shadow-lg);
1612
+ display: flex;
1613
+ grid-area: 2 / 1 / -1 / -1;
1614
+ inline-size: min(var(--_nav-overlay-size), calc(100% - var(--_row-mark) - 2 * var(--_gutter)));
1615
+ inset-block: 0;
1616
+ inset-inline-start: 0;
1617
+ margin: 0;
1618
+ padding-inline: 0;
1619
+ position: absolute;
1620
+ transition:
1621
+ translate var(--_duration) var(--slotted-easing-exit, cubic-bezier(0.3, 0, 0.8, 0.15)),
1622
+ visibility var(--_duration) step-end;
1623
+ translate: -100% 0;
1624
+ visibility: hidden;
1625
+ z-index: var(--slotted-z-index-overlay, 1000);
1626
+ }
1627
+ [data-ui='app-shell'][data-nav-mode='overlay'][data-open] > [data-part='nav'] {
1628
+ transition:
1629
+ translate var(--_duration) var(--slotted-easing-enter, cubic-bezier(0.05, 0.7, 0.1, 1)),
1630
+ visibility var(--_duration) step-start;
1631
+ translate: 0 0;
1632
+ visibility: visible;
1633
+ }
1634
+ [data-ui='app-shell'][data-nav-mode='overlay'][data-nav-side='end'] > [data-part='nav'] {
1635
+ inset-inline-end: 0;
1636
+ inset-inline-start: auto;
1637
+ translate: 100% 0;
1638
+ }
1639
+
1640
+ /**
1641
+ * On the inset plane the panel is a card like every other region, so it takes
1642
+ * the same gutter, the same radius and the same elevation rather than being
1643
+ * a sheet welded to the frame's edge — the one region that ignored the plane
1644
+ * it slid onto. The closed position then has to clear the gutter as well, or
1645
+ * the panel leaves a strip of itself against the edge.
1646
+ *
1647
+ * A card has an edge the column never had, and rows whose ground meets that
1648
+ * edge read as a panel with no padding. One row inset on the card, for all
1649
+ * three parts: the marks keep one column, and a consumer's header row that
1650
+ * writes the same inset stays over them.
1651
+ */
1652
+ [data-ui='app-shell'][data-canvas='floating'][data-nav-mode='overlay'] > [data-part='nav'],
1653
+ [data-ui='app-shell'][data-canvas='panel'][data-nav-mode='overlay'] > [data-part='nav'],
1654
+ [data-ui='app-shell'][data-canvas='inset'][data-nav-mode='overlay'] > [data-part='nav'] {
1655
+ border: var(--_rule-width) solid var(--_rule);
1656
+ border-radius: var(--_radius);
1657
+ box-shadow: var(--slotted-shadow-lg);
1658
+ margin-block: var(--_gutter);
1659
+ margin-inline-start: var(--_gutter);
1660
+ translate: calc(-100% - var(--_gutter)) 0;
1661
+ }
1662
+ [data-ui='app-shell']:not([data-canvas='flush'])[data-nav-mode='overlay'][data-nav-side='end']
1663
+ > [data-part='nav'] {
1664
+ margin-inline-end: var(--_gutter);
1665
+ margin-inline-start: 0;
1666
+ translate: calc(100% + var(--_gutter)) 0;
1667
+ }
1668
+ [data-ui='app-shell']:not([data-canvas='flush'])[data-nav-mode='overlay'][data-open]
1669
+ > [data-part='nav'] {
1670
+ translate: 0 0;
1671
+ }
1672
+
1673
+ /* ── The pane as a layer ─────────────────────────────────────────────── */
1674
+
1675
+ /**
1676
+ * The navigation's layer, from the other end of the line: the same rows of
1677
+ * the grid, the same elevation, the same pair of easings, and the mirror of
1678
+ * every inline distance. It slides in from the end the navigation is not on,
1679
+ * which is the end the pane stands at as a column.
1680
+ */
1681
+ [data-ui='app-shell'][data-aside-mode='overlay'] > [data-part='aside'] {
1682
+ background-color: var(
1683
+ --slotted-app-shell-aside-surface,
1684
+ var(--slotted-app-shell-canvas, var(--slotted-surface, Canvas))
1685
+ );
1686
+ border-block-start: var(--_rule-width) solid var(--_rule);
1687
+ box-shadow: var(--slotted-shadow-lg);
1688
+ display: flex;
1689
+ grid-area: 2 / 1 / -1 / -1;
1690
+ inline-size: var(--_aside-size);
1691
+ inset-block: 0;
1692
+ inset-inline-end: 0;
1693
+ inset-inline-start: auto;
1694
+ margin: 0;
1695
+ max-inline-size: 80%;
1696
+ position: absolute;
1697
+ transition:
1698
+ translate var(--_duration) var(--slotted-easing-exit, cubic-bezier(0.3, 0, 0.8, 0.15)),
1699
+ visibility var(--_duration) step-end;
1700
+ translate: 100% 0;
1701
+ visibility: hidden;
1702
+ z-index: var(--slotted-z-index-overlay, 1000);
1703
+ }
1704
+ [data-ui='app-shell'][data-aside-mode='overlay'] > [data-part='aside'][data-open] {
1705
+ transition:
1706
+ translate var(--_duration) var(--slotted-easing-enter, cubic-bezier(0.05, 0.7, 0.1, 1)),
1707
+ visibility var(--_duration) step-start;
1708
+ translate: 0 0;
1709
+ visibility: visible;
1710
+ }
1711
+ [data-ui='app-shell'][data-aside-mode='overlay'][data-nav-side='end'] > [data-part='aside'] {
1712
+ inset-inline-end: auto;
1713
+ inset-inline-start: 0;
1714
+ translate: -100% 0;
1715
+ }
1716
+ [data-ui='app-shell'][data-canvas='floating'][data-aside-mode='overlay'] > [data-part='aside'],
1717
+ [data-ui='app-shell'][data-canvas='panel'][data-aside-mode='overlay'] > [data-part='aside'],
1718
+ [data-ui='app-shell'][data-canvas='inset'][data-aside-mode='overlay'] > [data-part='aside'] {
1719
+ border: var(--_rule-width) solid var(--_rule);
1720
+ border-radius: var(--_radius);
1721
+ box-shadow: var(--slotted-shadow-lg);
1722
+ margin-block: var(--_gutter);
1723
+ margin-inline-end: var(--_gutter);
1724
+ translate: calc(100% + var(--_gutter)) 0;
1725
+ }
1726
+ [data-ui='app-shell']:not([data-canvas='flush'])[data-aside-mode='overlay'][data-nav-side='end']
1727
+ > [data-part='aside'] {
1728
+ margin-inline-end: 0;
1729
+ margin-inline-start: var(--_gutter);
1730
+ translate: calc(-100% - var(--_gutter)) 0;
1731
+ }
1732
+ [data-ui='app-shell']:not([data-canvas='flush'])[data-aside-mode='overlay']
1733
+ > [data-part='aside'][data-open] {
1734
+ translate: 0 0;
1735
+ }
1736
+
1737
+ /* ── Before the binding has measured ─────────────────────────────────── */
1738
+
1739
+ /**
1740
+ * The breakpoint is a literal because `@container (inline-size: var(--x))` is
1741
+ * invalid CSS. It is `COMPACT_BELOW` of `@slotted/core/measure`, the limit
1742
+ * of the compact size class; `specs/components/app-shell/contract.json`
1743
+ * states the same number under `overlayAt`, and `app-shell.styles.test.mjs`
1744
+ * compares every condition here against it.
1745
+ *
1746
+ * The first two blocks answer one question only: what a shell whose mode has
1747
+ * not been decided yet draws. A server render and the frame before hydration
1748
+ * carry no `data-nav-mode`, and a wide one is then a column while a narrow
1749
+ * one is nothing. Once the binding writes the attribute, the rules above own
1750
+ * the geometry at every width.
1751
+ *
1752
+ * The pane is the other way round: where it stands is a question of room
1753
+ * rather than of mode, so the width answers it and the width alone.
1754
+ */
1755
+ @container slotted-app-shell (inline-size >= 600px) {
1756
+ [data-ui='app-shell']:not([data-nav-mode]) > [data-part='nav'] {
1757
+ inline-size: calc(var(--_rail-size) + 2 * var(--_nav-inset) + var(--_nav-edges));
1758
+ }
1759
+ [data-ui='app-shell']:not([data-nav-mode])[data-open] > [data-part='nav'] {
1760
+ inline-size: var(--_nav-size);
1761
+ }
1762
+
1763
+ /* The pane as a column, where there is room. `:not(overlay)`: a layer is
1764
+ placed by its mode and not by the width, exactly as the navigation's is,
1765
+ and these rules are for the column and the row alone. */
1766
+ /* The extent the reader set, when there is one, and the size step when
1767
+ not. The binding writes it in `cqi`: the root is the query container, so
1768
+ the unit resolves against the shell's own inline size from inside this
1769
+ `auto` track, where a percentage would resolve against the track the
1770
+ pane itself sizes. */
1771
+ [data-ui='app-shell']:not([data-aside-mode='overlay']) > [data-part='aside'] {
1772
+ grid-area: 2 / 3 / 3 / 4;
1773
+ inline-size: var(--slotted-app-shell-aside-extent, var(--_aside-size));
1774
+ position: relative;
1775
+ }
1776
+ [data-ui='app-shell']:not([data-aside-mode='overlay'])
1777
+ > [data-part='aside']
1778
+ > [data-part='aside-resizer']:not([hidden]) {
1779
+ display: block;
1780
+ }
1781
+ [data-ui='app-shell']:not([data-aside-mode='overlay'])[data-nav-side='end']
1782
+ > [data-part='aside'] {
1783
+ grid-area: 2 / 1 / 3 / 2;
1784
+ }
1785
+ /* Under `primary="main"` the pane, as a column, stands beside the card
1786
+ that holds the banner and the content, so it spans the banner's row. */
1787
+ [data-ui='app-shell']:not([data-aside-mode='overlay'])[data-primary='main']
1788
+ > [data-part='aside'],
1789
+ [data-ui='app-shell']:not([data-aside-mode='overlay'])[data-canvas='panel'][data-primary='nav']
1790
+ > [data-part='aside'] {
1791
+ grid-row: 1 / 3;
1792
+ }
1793
+ [data-ui='app-shell']:not([data-aside-mode='overlay']):is(
1794
+ [data-canvas='floating'],
1795
+ [data-canvas='inset']
1796
+ )[data-nav-side='end']
1797
+ > [data-part='aside'] {
1798
+ margin-inline: var(--_gutter) 0;
1799
+ }
1800
+ [data-ui='app-shell']:not([data-aside-mode='overlay'])[data-canvas='flush']
1801
+ > [data-part='aside'] {
1802
+ border-block-start: none;
1803
+ }
1804
+ [data-ui='app-shell']:not([data-aside-mode='overlay']):is(
1805
+ [data-canvas='flush'],
1806
+ [data-canvas='panel']
1807
+ )
1808
+ > [data-part='aside'] {
1809
+ border-inline-start: var(--_seam);
1810
+ }
1811
+ [data-ui='app-shell']:not([data-aside-mode='overlay']):is(
1812
+ [data-canvas='flush'],
1813
+ [data-canvas='panel']
1814
+ )[data-nav-side='end']
1815
+ > [data-part='aside'] {
1816
+ border-inline-end: var(--_seam);
1817
+ border-inline-start: none;
1818
+ }
1819
+ }
1820
+
1821
+ @container slotted-app-shell (inline-size < 600px) {
1822
+ [data-ui='app-shell']:not([data-nav-mode]) > [data-part='nav'] {
1823
+ display: none;
1824
+ }
1825
+ /* The content flush to the frame, as it is once the mode says overlay. */
1826
+ [data-ui='app-shell']:not([data-nav-mode])[data-canvas='floating'] > [data-part='main'],
1827
+ [data-ui='app-shell']:not([data-nav-mode])[data-canvas='inset'] > [data-part='main'] {
1828
+ border-block-end: 0;
1829
+ border-inline: 0;
1830
+ border-radius: 0;
1831
+ box-shadow: none;
1832
+ margin: 0;
1833
+ }
1834
+ /* The card of `primary="main"` goes with the gutter. The root cannot
1835
+ query its own width, but its pseudo-element is a child of the query
1836
+ container and reads it. */
1837
+ [data-ui='app-shell']:not([data-nav-mode]):is(
1838
+ [data-canvas='floating'],
1839
+ [data-canvas='inset']
1840
+ )[data-primary='main']::before {
1841
+ content: none;
1842
+ }
1843
+ [data-ui='app-shell']:not([data-nav-mode])[data-primary='main'] > [data-part='banner'] {
1844
+ margin: 0;
1845
+ padding-inline: var(--_edge);
1846
+ }
1847
+ [data-ui='app-shell']:not([data-nav-mode])[data-canvas='panel'] > [data-part='main'] {
1848
+ border-block-start: var(--_rule-width) solid var(--_rule);
1849
+ border-radius: 0;
1850
+ }
1851
+ [data-ui='app-shell']:not([data-nav-mode])[data-canvas='floating'] > [data-part='progress'],
1852
+ [data-ui='app-shell']:not([data-nav-mode])[data-canvas='inset'] > [data-part='progress'] {
1853
+ border-radius: 0;
1854
+ margin: 0;
1855
+ }
1856
+ [data-ui='app-shell']:not([data-nav-mode])[data-canvas='panel'] > [data-part='progress'] {
1857
+ border-radius: 0;
1858
+ }
1859
+ [data-ui='app-shell']:not([data-aside-mode='overlay']):is(
1860
+ [data-canvas='floating'],
1861
+ [data-canvas='inset']
1862
+ )
1863
+ > [data-part='aside'] {
1864
+ margin-inline: var(--_gutter);
1865
+ }
1866
+ /* A row under the content takes at most half the shell, so the content
1867
+ keeps the other half: a pane whose record is taller than the screen
1868
+ left the content a line high, with the pane scrolling and the content
1869
+ gone. Only where the shell owns its height; under `document` the page
1870
+ scrolls and the row takes its content's height. */
1871
+ [data-ui='app-shell'][data-scroll='content']:not([data-aside-mode='overlay'])
1872
+ > [data-part='aside'] {
1873
+ max-block-size: 50cqb;
1874
+ }
1875
+ /* The pane as a row under the content, in the inline mode alone: a pane
1876
+ whose mode is not yet decided draws nothing at a narrow width, the safe
1877
+ half of the answer, for the reason the navigation draws nothing. */
1878
+ [data-ui='app-shell']:not([data-aside-mode]) > [data-part='aside'] {
1879
+ display: none;
1880
+ }
1881
+ }
1882
+
1883
+ /* ── The veil ────────────────────────────────────────────────────────── */
1884
+
1885
+ /** It stops where the panel stops, so the bar above stays a fixed point. */
1886
+ [data-ui='app-shell'] > [data-part='scrim'] {
1887
+ background-color: var(--_scrim);
1888
+ grid-area: 2 / 1 / -1 / -1;
1889
+ inset: 0;
1890
+ opacity: 1;
1891
+ position: absolute;
1892
+ transition: opacity var(--_duration) var(--_easing);
1893
+ z-index: calc(var(--slotted-z-index-overlay, 1000) - 1);
1894
+ }
1895
+ [data-ui='app-shell'] > [data-part='scrim'][data-status='entering'],
1896
+ [data-ui='app-shell'] > [data-part='scrim'][data-status='exiting'] {
1897
+ opacity: 0;
1898
+ }
1899
+ }