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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/package.json +276 -3
  4. package/src/accordion/accordion.css +96 -0
  5. package/src/accordion/accordion.tokens.json +14 -0
  6. package/src/action-bar/action-bar.css +455 -0
  7. package/src/action-bar/action-bar.tokens.json +50 -0
  8. package/src/action-sheet/action-sheet.css +462 -0
  9. package/src/action-sheet/action-sheet.tokens.json +59 -0
  10. package/src/alert/alert.css +764 -0
  11. package/src/alert/alert.tokens.json +77 -0
  12. package/src/app-shell/app-shell.css +1899 -0
  13. package/src/app-shell/app-shell.tokens.json +88 -0
  14. package/src/aspect-ratio/aspect-ratio.css +131 -0
  15. package/src/aspect-ratio/aspect-ratio.tokens.json +1 -0
  16. package/src/attachment/attachment.css +906 -0
  17. package/src/attachment/attachment.tokens.json +85 -0
  18. package/src/avatar/avatar.css +598 -0
  19. package/src/avatar/avatar.tokens.json +64 -0
  20. package/src/avatar-group/avatar-group.css +268 -0
  21. package/src/avatar-group/avatar-group.tokens.json +16 -0
  22. package/src/badge/badge.css +462 -0
  23. package/src/badge/badge.tokens.json +58 -0
  24. package/src/breadcrumb/breadcrumb.css +441 -0
  25. package/src/breadcrumb/breadcrumb.tokens.json +50 -0
  26. package/src/button/button.css +754 -0
  27. package/src/button/button.tokens.json +72 -0
  28. package/src/button-group/button-group.css +455 -0
  29. package/src/button-group/button-group.tokens.json +11 -0
  30. package/src/calendar/calendar.css +846 -0
  31. package/src/calendar/calendar.tokens.json +67 -0
  32. package/src/card/card.css +602 -0
  33. package/src/card/card.tokens.json +68 -0
  34. package/src/carousel/carousel.css +860 -0
  35. package/src/carousel/carousel.tokens.json +62 -0
  36. package/src/cascader/cascader.css +1320 -0
  37. package/src/cascader/cascader.tokens.json +103 -0
  38. package/src/chart/chart.css +1473 -0
  39. package/src/chart/chart.tokens.json +107 -0
  40. package/src/checkbox/checkbox.css +1011 -0
  41. package/src/checkbox/checkbox.tokens.json +102 -0
  42. package/src/checkbox-group/checkbox-group.css +70 -0
  43. package/src/checkbox-group/checkbox-group.tokens.json +6 -0
  44. package/src/code-block/code-block.css +555 -0
  45. package/src/code-block/code-block.tokens.json +69 -0
  46. package/src/collapsible/collapsible.css +546 -0
  47. package/src/collapsible/collapsible.tokens.json +55 -0
  48. package/src/color-picker/color-picker.css +607 -0
  49. package/src/color-picker/color-picker.tokens.json +58 -0
  50. package/src/column-manager/column-manager.css +452 -0
  51. package/src/column-manager/column-manager.tokens.json +43 -0
  52. package/src/combobox/combobox.css +1048 -0
  53. package/src/combobox/combobox.tokens.json +112 -0
  54. package/src/command-palette/command-palette.css +1000 -0
  55. package/src/command-palette/command-palette.tokens.json +75 -0
  56. package/src/container/container.css +198 -0
  57. package/src/container/container.tokens.json +18 -0
  58. package/src/context-menu/context-menu.css +104 -0
  59. package/src/context-menu/context-menu.tokens.json +11 -0
  60. package/src/currency-field/currency-field.css +787 -0
  61. package/src/currency-field/currency-field.tokens.json +80 -0
  62. package/src/dashboard-grid/dashboard-grid.css +748 -0
  63. package/src/dashboard-grid/dashboard-grid.tokens.json +67 -0
  64. package/src/data-grid/data-grid.css +2484 -0
  65. package/src/data-grid/data-grid.tokens.json +100 -0
  66. package/src/data-list/data-list.css +455 -0
  67. package/src/data-list/data-list.tokens.json +35 -0
  68. package/src/date-field/date-field.css +560 -0
  69. package/src/date-field/date-field.tokens.json +80 -0
  70. package/src/date-picker/date-picker.css +420 -0
  71. package/src/date-picker/date-picker.tokens.json +49 -0
  72. package/src/date-range-picker/date-range-picker.css +983 -0
  73. package/src/date-range-picker/date-range-picker.tokens.json +109 -0
  74. package/src/datetime-field/datetime-field.css +850 -0
  75. package/src/datetime-field/datetime-field.tokens.json +91 -0
  76. package/src/datetime-picker/datetime-picker.css +843 -0
  77. package/src/datetime-picker/datetime-picker.tokens.json +78 -0
  78. package/src/datetime-range-picker/datetime-range-picker.css +1375 -0
  79. package/src/datetime-range-picker/datetime-range-picker.tokens.json +135 -0
  80. package/src/description-list/description-list.css +675 -0
  81. package/src/description-list/description-list.tokens.json +66 -0
  82. package/src/detail-panel/detail-panel.css +329 -0
  83. package/src/detail-panel/detail-panel.tokens.json +33 -0
  84. package/src/dialog/dialog.css +968 -0
  85. package/src/dialog/dialog.tokens.json +66 -0
  86. package/src/divider/divider.css +131 -0
  87. package/src/divider/divider.tokens.json +18 -0
  88. package/src/drawer/drawer.css +1303 -0
  89. package/src/drawer/drawer.tokens.json +75 -0
  90. package/src/entity-picker/entity-picker.css +1367 -0
  91. package/src/entity-picker/entity-picker.tokens.json +118 -0
  92. package/src/event-calendar/event-calendar.css +2148 -0
  93. package/src/event-calendar/event-calendar.tokens.json +87 -0
  94. package/src/field/field.css +707 -0
  95. package/src/field/field.tokens.json +58 -0
  96. package/src/field-group/field-group.css +226 -0
  97. package/src/field-group/field-group.tokens.json +33 -0
  98. package/src/filter-bar/filter-bar.css +1306 -0
  99. package/src/filter-bar/filter-bar.tokens.json +78 -0
  100. package/src/form/form.css +218 -0
  101. package/src/form/form.tokens.json +32 -0
  102. package/src/fullscreen/fullscreen.css +376 -0
  103. package/src/fullscreen/fullscreen.tokens.json +50 -0
  104. package/src/grid/grid.css +173 -0
  105. package/src/grid/grid.tokens.json +20 -0
  106. package/src/highlight/highlight.css +135 -0
  107. package/src/highlight/highlight.tokens.json +16 -0
  108. package/src/hover-card/hover-card.css +93 -0
  109. package/src/hover-card/hover-card.tokens.json +17 -0
  110. package/src/icon/icon.css +136 -0
  111. package/src/icon/icon.tokens.json +14 -0
  112. package/src/image/image.css +449 -0
  113. package/src/image/image.tokens.json +27 -0
  114. package/src/import-export-center/import-export-center.css +1175 -0
  115. package/src/import-export-center/import-export-center.tokens.json +84 -0
  116. package/src/inline/inline.css +149 -0
  117. package/src/inline/inline.tokens.json +14 -0
  118. package/src/inline-message/inline-message.css +396 -0
  119. package/src/inline-message/inline-message.tokens.json +19 -0
  120. package/src/kbd/kbd.css +490 -0
  121. package/src/kbd/kbd.tokens.json +40 -0
  122. package/src/link/link.css +237 -0
  123. package/src/link/link.tokens.json +25 -0
  124. package/src/list/list.css +240 -0
  125. package/src/list/list.tokens.json +42 -0
  126. package/src/list-item/list-item.css +937 -0
  127. package/src/list-item/list-item.tokens.json +114 -0
  128. package/src/listbox/listbox.css +674 -0
  129. package/src/listbox/listbox.tokens.json +72 -0
  130. package/src/loading-overlay/loading-overlay.css +730 -0
  131. package/src/loading-overlay/loading-overlay.tokens.json +48 -0
  132. package/src/markdown/markdown.css +168 -0
  133. package/src/markdown/markdown.tokens.json +18 -0
  134. package/src/menu/menu.css +817 -0
  135. package/src/menu/menu.tokens.json +78 -0
  136. package/src/menubar/menubar.css +170 -0
  137. package/src/menubar/menubar.tokens.json +32 -0
  138. package/src/message/message.css +1453 -0
  139. package/src/message/message.tokens.json +85 -0
  140. package/src/message-scroller/message-scroller.css +371 -0
  141. package/src/message-scroller/message-scroller.tokens.json +47 -0
  142. package/src/multi-select/multi-select.css +1427 -0
  143. package/src/multi-select/multi-select.tokens.json +114 -0
  144. package/src/navigation-menu/navigation-menu.css +1029 -0
  145. package/src/navigation-menu/navigation-menu.tokens.json +70 -0
  146. package/src/notification-center/notification-center.css +856 -0
  147. package/src/notification-center/notification-center.tokens.json +84 -0
  148. package/src/number-field/number-field.css +794 -0
  149. package/src/number-field/number-field.tokens.json +80 -0
  150. package/src/otp-input/otp-input.css +525 -0
  151. package/src/otp-input/otp-input.tokens.json +69 -0
  152. package/src/page/page.css +179 -0
  153. package/src/page/page.tokens.json +20 -0
  154. package/src/page-header/page-header.css +1181 -0
  155. package/src/page-header/page-header.tokens.json +71 -0
  156. package/src/pagination/pagination.css +500 -0
  157. package/src/pagination/pagination.tokens.json +56 -0
  158. package/src/popover/popover.css +526 -0
  159. package/src/popover/popover.tokens.json +60 -0
  160. package/src/progress-indicator/progress-indicator.css +1584 -0
  161. package/src/progress-indicator/progress-indicator.tokens.json +50 -0
  162. package/src/prompt-composer/prompt-composer.css +680 -0
  163. package/src/prompt-composer/prompt-composer.tokens.json +99 -0
  164. package/src/prose/prose.css +733 -0
  165. package/src/prose/prose.tokens.json +79 -0
  166. package/src/radio-group/radio-group.css +778 -0
  167. package/src/radio-group/radio-group.tokens.json +103 -0
  168. package/src/rating/rating.css +1071 -0
  169. package/src/rating/rating.tokens.json +88 -0
  170. package/src/result-state/result-state.css +980 -0
  171. package/src/result-state/result-state.tokens.json +63 -0
  172. package/src/runtime-properties.mjs +321 -0
  173. package/src/saved-views/saved-views.css +394 -0
  174. package/src/saved-views/saved-views.tokens.json +41 -0
  175. package/src/scroll-area/scroll-area.css +481 -0
  176. package/src/scroll-area/scroll-area.tokens.json +15 -0
  177. package/src/section/section.css +177 -0
  178. package/src/section/section.tokens.json +39 -0
  179. package/src/segmented-control/segmented-control.css +564 -0
  180. package/src/segmented-control/segmented-control.tokens.json +67 -0
  181. package/src/select/select.css +1593 -0
  182. package/src/select/select.tokens.json +110 -0
  183. package/src/side-navigation/side-navigation.css +1096 -0
  184. package/src/side-navigation/side-navigation.tokens.json +87 -0
  185. package/src/skeleton/skeleton.css +431 -0
  186. package/src/skeleton/skeleton.tokens.json +29 -0
  187. package/src/slider/slider.css +792 -0
  188. package/src/slider/slider.tokens.json +75 -0
  189. package/src/sparkline/sparkline.css +282 -0
  190. package/src/sparkline/sparkline.tokens.json +45 -0
  191. package/src/spinner/spinner.css +224 -0
  192. package/src/spinner/spinner.tokens.json +8 -0
  193. package/src/split-button/split-button.css +253 -0
  194. package/src/split-button/split-button.tokens.json +12 -0
  195. package/src/splitter/splitter.css +336 -0
  196. package/src/splitter/splitter.tokens.json +24 -0
  197. package/src/stack/stack.css +128 -0
  198. package/src/stack/stack.tokens.json +14 -0
  199. package/src/stat-card/stat-card.css +641 -0
  200. package/src/stat-card/stat-card.tokens.json +95 -0
  201. package/src/status-indicator/status-indicator.css +481 -0
  202. package/src/status-indicator/status-indicator.tokens.json +38 -0
  203. package/src/stepper/stepper.css +629 -0
  204. package/src/stepper/stepper.tokens.json +72 -0
  205. package/src/switch/switch.css +703 -0
  206. package/src/switch/switch.tokens.json +75 -0
  207. package/src/table/table.css +1482 -0
  208. package/src/table/table.tokens.json +69 -0
  209. package/src/table-of-contents/table-of-contents.css +533 -0
  210. package/src/table-of-contents/table-of-contents.tokens.json +53 -0
  211. package/src/tabs/tabs.css +998 -0
  212. package/src/tabs/tabs.tokens.json +82 -0
  213. package/src/tag/tag.css +768 -0
  214. package/src/tag/tag.tokens.json +85 -0
  215. package/src/tags-input/tags-input.css +757 -0
  216. package/src/tags-input/tags-input.tokens.json +84 -0
  217. package/src/text-field/text-field.css +900 -0
  218. package/src/text-field/text-field.tokens.json +87 -0
  219. package/src/textarea/textarea.css +365 -0
  220. package/src/textarea/textarea.tokens.json +59 -0
  221. package/src/time-field/time-field.css +719 -0
  222. package/src/time-field/time-field.tokens.json +86 -0
  223. package/src/time-picker/time-picker.css +665 -0
  224. package/src/time-picker/time-picker.tokens.json +66 -0
  225. package/src/time-range-picker/time-range-picker.css +1287 -0
  226. package/src/time-range-picker/time-range-picker.tokens.json +134 -0
  227. package/src/timeline/timeline.css +1596 -0
  228. package/src/timeline/timeline.tokens.json +96 -0
  229. package/src/toast/toast.css +943 -0
  230. package/src/toast/toast.tokens.json +90 -0
  231. package/src/toggle/toggle.css +290 -0
  232. package/src/toggle/toggle.tokens.json +54 -0
  233. package/src/toggle-group/toggle-group.css +463 -0
  234. package/src/toggle-group/toggle-group.tokens.json +54 -0
  235. package/src/toolbar/toolbar.css +434 -0
  236. package/src/toolbar/toolbar.tokens.json +49 -0
  237. package/src/tooltip/tooltip.css +368 -0
  238. package/src/tooltip/tooltip.tokens.json +39 -0
  239. package/src/top-navigation/top-navigation.css +1287 -0
  240. package/src/top-navigation/top-navigation.tokens.json +91 -0
  241. package/src/tour/tour.css +680 -0
  242. package/src/tour/tour.tokens.json +67 -0
  243. package/src/transfer/transfer.css +571 -0
  244. package/src/transfer/transfer.tokens.json +49 -0
  245. package/src/tree/tree.css +1000 -0
  246. package/src/tree/tree.tokens.json +80 -0
  247. package/src/typography/typography.css +211 -0
  248. package/src/typography/typography.tokens.json +31 -0
  249. package/src/upload/upload.css +1706 -0
  250. package/src/upload/upload.tokens.json +123 -0
  251. package/src/visually-hidden/visually-hidden.css +52 -0
  252. package/src/visually-hidden/visually-hidden.tokens.json +1 -0
  253. package/src/wizard/wizard.css +740 -0
  254. package/src/wizard/wizard.tokens.json +63 -0
@@ -0,0 +1,846 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The month grid ─────────────────────────────────────────────────────
4
+ *
5
+ * Seven columns of two-digit numbers, which makes this the densest grid the
6
+ * library draws and the one place where `dense` either holds or collapses.
7
+ * Everything below is sized from the control scales, so the cell shrinks
8
+ * with the type rather than independently of it, and the touch target is
9
+ * recovered outside the paint where the scale falls under the minimum.
10
+ *
11
+ * The grid is a real `table` with the `grid` role on it. The APG's own note
12
+ * is why nothing here writes `row`, `columnheader` or `gridcell`: those are
13
+ * implied by `tr`, `th` and `td`, and writing them again would be the
14
+ * duplication `patterns.md` refuses.
15
+ */
16
+ [data-ui='calendar'][data-part='root'] {
17
+ /**
18
+ * The reflow container. It is on the root and the grids are one element
19
+ * in, because `container-type: inline-size` makes an element a query
20
+ * container for its descendants and not for itself — the rule the briefing
21
+ * records, and the reason `stepper` and `description-list` have the same
22
+ * split.
23
+ */
24
+ container-type: inline-size;
25
+ container-name: slotted-calendar;
26
+
27
+ --_font-size: var(--slotted-control-font-size-md, 13px);
28
+ --_gap: var(--slotted-control-gap-md, 8px);
29
+ --_icon-size: var(--slotted-control-icon-size-md, 16px);
30
+ /**
31
+ * A cell is square, and its side is the control height rather than a scale
32
+ * of its own: a calendar cell is a control the reader presses, and one
33
+ * drawn to a different rhythm from the buttons above it reads as a second
34
+ * component. `dense` takes it to 26 pixels, above the WCAG minimum, and
35
+ * the coarse-pointer block the theme emits takes it to 44 on touch.
36
+ */
37
+ --_cell-size: var(--slotted-calendar-cell-size, var(--slotted-control-height-md, 34px));
38
+ /** Seven days, four months, four years: the shape of the three views. */
39
+ --_columns: 7;
40
+ --_months: 1;
41
+ --_month-gap: var(--slotted-space-6, 1rem);
42
+ /** The week number is a column of its own when a product asks for one. */
43
+ --_week-number-columns: 0;
44
+ --_grid-size: calc(var(--_cell-size) * (var(--_columns) + var(--_week-number-columns)));
45
+
46
+ background-color: var(--slotted-calendar-background, transparent);
47
+ box-sizing: border-box;
48
+ color: var(--slotted-calendar-color, var(--slotted-text, CanvasText));
49
+ display: flex;
50
+ flex-direction: column;
51
+ font-family: var(--slotted-control-font-family, system-ui, sans-serif);
52
+ font-size: var(--_font-size);
53
+ gap: var(--_gap);
54
+ /**
55
+ * The width is stated from the scales rather than measured from the grid
56
+ * inside, and it has to be: `container-type: inline-size` applies
57
+ * `contain: inline-size`, so the contents no longer contribute anything to
58
+ * the element's own width. Written `fit-content`, the root resolved to
59
+ * almost nothing and the heading was clipped to one character while the
60
+ * table overflowed it — a picture that looked like a calendar and was a
61
+ * collapsed box, which is the defect `references/stories.md` records for
62
+ * `pagination` and the audit exists to catch.
63
+ *
64
+ * A stated width also survives the flex container inside a popover, where
65
+ * a contained element contributing nothing would collapse the surface.
66
+ */
67
+ /**
68
+ * The row that pages the month is the other thing this width has to hold.
69
+ *
70
+ * `dense` takes a cell to 26 pixels, so seven columns are 182 — and
71
+ * `setembro de 2026` between two triggers needs rather more than that. The
72
+ * heading was clipped at both ends and read `etembro de 2026`, which is a
73
+ * month nobody can read above a grid whose only subject is that month.
74
+ * `contain: inline-size` means the contents cannot ask for the room
75
+ * themselves, so the floor is stated: twenty characters of the heading's
76
+ * own type, the three glyph boxes of the two triggers and the view mark,
77
+ * and the gaps between them. It is written in `ch` and in the icon scale
78
+ * rather than in pixels, so it moves with the type and with density instead
79
+ * of being a guess that is right at one step.
80
+ *
81
+ * At `comfortable` the grid is wider than the floor and nothing changes. A
82
+ * locale with a longer month name still loses its tail, from a floor high
83
+ * enough to keep the name readable, and `grids` centres the month under the
84
+ * row it belongs to.
85
+ */
86
+ --_heading-floor: calc(20ch + 3 * var(--_icon-size) + 4 * var(--_gap));
87
+
88
+ inline-size: max(
89
+ calc(var(--_grid-size) * var(--_months) + var(--_month-gap) * (var(--_months) - 1)),
90
+ var(--_heading-floor)
91
+ );
92
+ max-inline-size: 100%;
93
+ }
94
+
95
+ [data-ui='calendar'][data-part='root'][data-week-numbers] {
96
+ --_week-number-columns: 0.75;
97
+ }
98
+
99
+ [data-ui='calendar'][data-part='root'][data-months='2'] {
100
+ --_months: 2;
101
+ }
102
+
103
+ [data-ui='calendar'][data-part='root'][data-months='3'] {
104
+ --_months: 3;
105
+ }
106
+
107
+ /**
108
+ * ── Where the reflow happens ──────────────────────────────────────────
109
+ *
110
+ * The cell is not stepped at a breakpoint; it is the room there is, divided
111
+ * by the columns there are, bounded above by the control height and below by
112
+ * the WCAG 2.2 minimum. `100cqi` is the root's own inline size, and these
113
+ * two elements are the first that may read it — a container query, and a
114
+ * container query unit, resolve against an **ancestor** container, so the
115
+ * root setting its own cell size from its own width did nothing at all. The
116
+ * first 240-pixel capture of this family showed a calendar at full size
117
+ * overflowing its stage, with a rule in the sheet that looked like it
118
+ * should have prevented exactly that.
119
+ *
120
+ * Continuous rather than stepped, because a literal threshold cannot be
121
+ * right at three densities: seven comfortable columns are 238 pixels and
122
+ * seven dense ones are 182, so any number that fits one shrinks the other
123
+ * while it still had room. CSS forbids a custom property in a container
124
+ * condition, which is what makes `clamp()` the answer here rather than a
125
+ * query.
126
+ */
127
+ [data-ui='calendar'] [data-part='header'],
128
+ [data-ui='calendar'] [data-part='grids'],
129
+ [data-ui='calendar'] [data-part='months'],
130
+ [data-ui='calendar'] [data-part='years'] {
131
+ --_cell-size: clamp(
132
+ var(--slotted-target-min, 24px),
133
+ calc(100cqi / (var(--_columns) + var(--_week-number-columns)) / var(--_months)),
134
+ var(--slotted-calendar-cell-size, var(--slotted-control-height-md, 34px))
135
+ );
136
+ }
137
+
138
+ /* ── The heading row ──────────────────────────────────────────────────
139
+ *
140
+ * The month, and the two triggers that page it. The heading sits between
141
+ * them rather than beside them, because it is the thing being changed and a
142
+ * label at one end would make the pair read as two unrelated buttons.
143
+ */
144
+ [data-ui='calendar'] [data-part='header'] {
145
+ align-items: center;
146
+ display: flex;
147
+ gap: var(--slotted-space-2, 0.25rem);
148
+ /* The row is as wide as the grid under it, so the two triggers sit over
149
+ the first and last columns rather than floating above the middle. */
150
+ inline-size: 100%;
151
+ justify-content: space-between;
152
+ }
153
+
154
+ /**
155
+ * The month and year, and the way up to the month and year views. It is a
156
+ * button because pressing it does something; it looks like a heading because
157
+ * that is what it says. The live region is on it: paging with a key changes
158
+ * nothing that focus lands on, so without this nothing announces the month.
159
+ */
160
+ [data-ui='calendar'] [data-part='heading'] {
161
+ --slotted-icon-size: var(--_icon-size);
162
+
163
+ appearance: none;
164
+ background-color: transparent;
165
+ border: 0;
166
+ border-radius: var(--slotted-calendar-radius, var(--slotted-control-radius, 4px));
167
+ color: inherit;
168
+ cursor: pointer;
169
+ display: inline-flex;
170
+ align-items: center;
171
+ gap: var(--slotted-space-2, 0.25rem);
172
+ font: inherit;
173
+ font-weight: var(--slotted-font-weight-semibold, 600);
174
+ /**
175
+ * It takes the room between the two triggers, and gives it back only when
176
+ * there is not enough — `setembro de 2026` and two triggers are 239 pixels
177
+ * against a seven-column grid of 238, so without this the trigger at the
178
+ * end fell to a second line.
179
+ *
180
+ * The first capture of this family showed the opposite failure, and it is
181
+ * worth naming because the two look like the same rule: the heading read
182
+ * `d`, one character, with the grid overflowing under it. That was the
183
+ * root's `inline-size: fit-content` under `contain: inline-size`, not this
184
+ * — a contained root has no contents to be sized by, so it collapsed and
185
+ * took the heading with it. With the root's width stated, shrinking here
186
+ * is bounded by the grid and stops long before a character.
187
+ */
188
+ flex: 1 1 auto;
189
+ /**
190
+ * `safe`, and it is the whole difference between a month that is cut and a
191
+ * month that is unreadable.
192
+ *
193
+ * Centred without it, an overflowing heading is clipped at **both** ends:
194
+ * at `dense` the row read `etembro de 2026` with the view mark sliced off,
195
+ * which is a month nobody can read above a grid whose only subject is that
196
+ * month. `safe` falls back to start alignment exactly when the item would
197
+ * overflow, so the name always begins where it begins and only its tail is
198
+ * lost. Found from `datetime-picker`'s panel, reproduced on this family
199
+ * alone at `dense` and 720 pixels.
200
+ *
201
+ * `text-overflow: ellipsis` stood here and did nothing: it applies to the
202
+ * inline content of a block container, and the label is an anonymous flex
203
+ * item beside the mark. It is removed rather than left to look like a
204
+ * guard that is holding.
205
+ */
206
+ justify-content: safe center;
207
+ min-inline-size: 0;
208
+ padding-block: var(--slotted-space-2, 0.25rem);
209
+ padding-inline: var(--slotted-space-2, 0.25rem);
210
+ /* One line, whatever the locale writes: a month name that wrapped would
211
+ take the grid down with it. */
212
+ overflow: hidden;
213
+ white-space: nowrap;
214
+ transition: background-color var(--slotted-control-transition-duration)
215
+ var(--slotted-control-transition-easing);
216
+ -webkit-tap-highlight-color: transparent;
217
+ }
218
+
219
+ /* The chevron after the name is a `Glyph` the binding draws. The way up is
220
+ closed at the top: a year view has nowhere further to go, so the binding
221
+ leaves the mark that offered it out rather than showing it disabled. */
222
+
223
+ /* ── The two page triggers ────────────────────────────────────────────
224
+ *
225
+ * The chevrons are `Glyph` elements in `directional` icon boxes, mirrored
226
+ * under `dir="rtl"` by the icon's own rule, which is how one glyph serves
227
+ * both document directions.
228
+ */
229
+ [data-ui='calendar'] [data-part='previous'],
230
+ [data-ui='calendar'] [data-part='next'] {
231
+ --slotted-icon-size: var(--_icon-size);
232
+
233
+ align-items: center;
234
+ appearance: none;
235
+ background-color: transparent;
236
+ block-size: var(--_cell-size);
237
+ border: 0;
238
+ border-radius: var(--slotted-calendar-radius, var(--slotted-control-radius, 4px));
239
+ color: var(--slotted-calendar-trigger-color, var(--slotted-text-muted, GrayText));
240
+ cursor: pointer;
241
+ display: inline-flex;
242
+ flex: 0 0 auto;
243
+ inline-size: var(--_cell-size);
244
+ justify-content: center;
245
+ padding: 0;
246
+ touch-action: manipulation;
247
+ transition:
248
+ background-color var(--slotted-control-transition-duration)
249
+ var(--slotted-control-transition-easing),
250
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
251
+ -webkit-tap-highlight-color: transparent;
252
+ }
253
+
254
+ /**
255
+ * One glyph, turned. The chevron points along the writing direction, so the
256
+ * "previous" trigger is the "next" artwork rotated a half turn; the icon's
257
+ * RTL mirror composes with the turn, so both follow `dir` without a second
258
+ * glyph — the rule `pagination` set.
259
+ */
260
+ [data-ui='calendar'] [data-part='previous'] > [data-ui='icon'] {
261
+ rotate: 180deg;
262
+ }
263
+
264
+ /* ── The grids ────────────────────────────────────────────────────────
265
+ *
266
+ * One month, or two, or three. They wrap rather than scroll: a calendar
267
+ * narrower than its months is a calendar with fewer months, and a sideways
268
+ * scrollbar under a grid is a control nobody finds.
269
+ */
270
+ [data-ui='calendar'] [data-part='grids'] {
271
+ display: flex;
272
+ /**
273
+ * A second month that does not fit goes under the first rather than being
274
+ * withdrawn. Hiding it needs a literal breakpoint — CSS forbids a custom
275
+ * property in a container condition — and one literal cannot be right at
276
+ * three densities: two months are 492 pixels at `comfortable` and 380 at
277
+ * `dense`, so any threshold that fits one hides a month that fits in the
278
+ * other. Wrapping is answered by the same arithmetic at every density, and
279
+ * it never takes away a month the consumer asked for.
280
+ */
281
+ flex-wrap: wrap;
282
+ gap: var(--_month-gap);
283
+ justify-content: center;
284
+ }
285
+
286
+ [data-ui='calendar'] [data-part='grid'] {
287
+ border-collapse: collapse;
288
+ /* A table sizes itself from its content and would give a 31-day column one
289
+ pixel more than a 30-day one. Fixed layout hands every column the same
290
+ width, which is what makes the seven read as a week. */
291
+ table-layout: fixed;
292
+ }
293
+
294
+ /**
295
+ * The month's own name. With one month it is out of the picture and not out
296
+ * of the document — the heading above already shows it, and a screen reader
297
+ * reaching the grid still needs it.
298
+ */
299
+ [data-ui='calendar'] [data-part='grid-caption'] {
300
+ block-size: 1px;
301
+ clip-path: inset(50%);
302
+ inline-size: 1px;
303
+ overflow: hidden;
304
+ position: absolute;
305
+ white-space: nowrap;
306
+ }
307
+
308
+ /**
309
+ * With more than one month it is shown, because the heading then names the
310
+ * span rather than a month and nothing else would say which grid is which.
311
+ * A reader looking at two grids and one title has to count columns to find
312
+ * out where October starts.
313
+ */
314
+ [data-ui='calendar'][data-part='root']:is([data-months='2'], [data-months='3'])
315
+ [data-part='grid-caption'] {
316
+ block-size: auto;
317
+ clip-path: none;
318
+ color: var(--slotted-calendar-weekday-color, var(--slotted-text-muted, GrayText));
319
+ font-size: var(--slotted-font-size-xs, 0.75rem);
320
+ font-weight: var(--slotted-font-weight-semibold, 600);
321
+ inline-size: auto;
322
+ overflow: visible;
323
+ padding-block-end: var(--slotted-space-2, 0.25rem);
324
+ position: static;
325
+ }
326
+
327
+ /**
328
+ * The weekday. Two characters is what a column can hold; the full name
329
+ * reaches assistive technology through `abbr`, which is the APG's own
330
+ * answer and the difference between hearing "Mo" and hearing "Monday".
331
+ */
332
+ [data-ui='calendar'] [data-part='week-header'] {
333
+ block-size: var(--_cell-size);
334
+ color: var(--slotted-calendar-weekday-color, var(--slotted-text-muted, GrayText));
335
+ font-size: var(--slotted-font-size-xs, 0.75rem);
336
+ font-weight: var(--slotted-font-weight-medium, 500);
337
+ inline-size: var(--_cell-size);
338
+ padding: 0;
339
+ /* Never truncated. A weekday cut to one character is not a weekday, and
340
+ the locale already chose how short its own narrow form is. */
341
+ text-overflow: clip;
342
+ }
343
+
344
+ [data-ui='calendar'] [data-part='week-number'] {
345
+ color: var(--slotted-calendar-week-number-color, var(--slotted-text-muted, GrayText));
346
+ font-size: var(--slotted-font-size-xs, 0.75rem);
347
+ font-variant-numeric: tabular-nums;
348
+ font-weight: var(--slotted-font-weight-regular, 400);
349
+ /* Narrower than a day: it annotates the row rather than belonging to it,
350
+ and a column as wide as a day would read as an eighth day. */
351
+ inline-size: calc(var(--_cell-size) * 0.75);
352
+ padding: 0;
353
+ /* The line that says the numbers are not part of the week. */
354
+ border-inline-end: var(--slotted-border-width-thin, 1px) solid
355
+ var(--slotted-calendar-week-number-border-color, var(--slotted-border-subtle, ButtonBorder));
356
+ }
357
+
358
+ [data-ui='calendar'] [data-part='cell'] {
359
+ padding: 0;
360
+ /* The containing block of the range band below. */
361
+ position: relative;
362
+ }
363
+
364
+ /**
365
+ * The day the reader presses.
366
+ *
367
+ * It is the `td`'s child rather than the `td` itself, because the range band
368
+ * has to run edge to edge across a cell while the day itself stays a circle
369
+ * inside it — two shapes, and one element cannot be both.
370
+ */
371
+ [data-ui='calendar'] [data-part='day'] {
372
+ /**
373
+ * A rule short of the cell on every side, in every cell.
374
+ *
375
+ * Two chosen days side by side are two solid boxes, and filling the cell
376
+ * put their rounded corners against each other with no air between —
377
+ * which reads as one shape with a dent rather than as two days. The inset
378
+ * is taken everywhere rather than only where it shows, so no day is a
379
+ * different size from the day beside it.
380
+ */
381
+ --_inset: var(--slotted-border-width-thin, 1px);
382
+
383
+ align-items: center;
384
+ block-size: calc(var(--_cell-size) - var(--_inset) * 2);
385
+ inline-size: calc(var(--_cell-size) - var(--_inset) * 2);
386
+ margin: var(--_inset);
387
+ border-radius: var(--slotted-calendar-day-radius, var(--slotted-radius-sm, 4px));
388
+ box-sizing: border-box;
389
+ color: inherit;
390
+ cursor: pointer;
391
+ display: flex;
392
+ font-variant-numeric: tabular-nums;
393
+ /**
394
+ * A column, and the second row is today's dot — reserved in every cell,
395
+ * painted in one. Left to today alone the mark either overlapped the digit
396
+ * or moved it, and a month whose ninth sits two pixels higher than its
397
+ * tenth is a month with a dent in it.
398
+ */
399
+ flex-direction: column;
400
+ /* The digit and the dot are not the same thing, and a mark against the
401
+ descender of a nine reads as part of it. */
402
+ gap: var(--slotted-space-1, 0.125rem);
403
+ justify-content: center;
404
+ /* The digit's own box, and nothing more: the leading a paragraph needs is
405
+ air the cell has to give to the dot instead. */
406
+ line-height: 1;
407
+ /* Above the range band, which is painted on the cell behind it. */
408
+ position: relative;
409
+ transition:
410
+ background-color var(--slotted-control-transition-duration)
411
+ var(--slotted-control-transition-easing),
412
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
413
+ user-select: none;
414
+ -webkit-tap-highlight-color: transparent;
415
+ }
416
+
417
+ /**
418
+ * The hit area, recovered outside the paint. At `dense` the cell is 26
419
+ * pixels and the WCAG 2.2 minimum is 24, so nothing is needed there; a theme
420
+ * whose control scale is smaller gets the target back without the family
421
+ * having to know which theme it is under.
422
+ */
423
+ [data-ui='calendar'] [data-part='day']::after {
424
+ content: '';
425
+ inset: min(0px, calc((100% - var(--slotted-target-min, 24px)) / 2));
426
+ position: absolute;
427
+ }
428
+
429
+ /* ── The month and year views ─────────────────────────────────────────
430
+ *
431
+ * Four columns rather than seven, and a cell wide enough for a month's short
432
+ * name. A reader entering a birth date must not press "previous month" four
433
+ * hundred times, which is what these two exist to prevent.
434
+ */
435
+ [data-ui='calendar'] [data-part='months'],
436
+ [data-ui='calendar'] [data-part='years'] {
437
+ display: grid;
438
+ gap: var(--slotted-space-2, 0.25rem);
439
+ grid-template-columns: repeat(4, 1fr);
440
+ /* The same box the day grid occupies, so switching view does not resize
441
+ the surface the reader is looking at. */
442
+ inline-size: calc(var(--_cell-size) * var(--_columns));
443
+ }
444
+
445
+ [data-ui='calendar'] [data-part='period'] {
446
+ align-items: center;
447
+ appearance: none;
448
+ background-color: transparent;
449
+ block-size: var(--_cell-size);
450
+ border: 0;
451
+ border-radius: var(--slotted-calendar-day-radius, var(--slotted-radius-sm, 4px));
452
+ color: inherit;
453
+ cursor: pointer;
454
+ display: flex;
455
+ font: inherit;
456
+ justify-content: center;
457
+ padding-inline: var(--slotted-space-2, 0.25rem);
458
+ transition:
459
+ background-color var(--slotted-control-transition-duration)
460
+ var(--slotted-control-transition-easing),
461
+ color var(--slotted-control-transition-duration) var(--slotted-control-transition-easing);
462
+ -webkit-tap-highlight-color: transparent;
463
+ }
464
+
465
+ /* ── What a cell can say ──────────────────────────────────────────────
466
+ *
467
+ * Seven facts, and every one of them paired with a shape rather than carried
468
+ * by colour alone. Under `forced-colors` a palette becomes four system
469
+ * colours, and every surveyed calendar loses six of these seven there.
470
+ */
471
+
472
+ /**
473
+ * Today. Marked apart from the selection, as §6 of the feature guide asks in
474
+ * those words: a reader who has chosen today must still see both facts.
475
+ *
476
+ * A dot rather than a ring, and the reason is in the first focus capture of
477
+ * this family: today was drawn as a ring and focus is drawn as a ring, so a
478
+ * reader arrowing onto today saw one box and could not tell which of the two
479
+ * it was. Two facts need two shapes. The dot is `currentcolor`, so it takes
480
+ * the ink of whatever state the day is in — the accent text at rest, the
481
+ * fill's own ink when the day is chosen, the disabled grey when nothing is
482
+ * operable — and it survives `forced-colors`, where `currentcolor` is
483
+ * already the system's ink.
484
+ */
485
+ [data-ui='calendar'] [data-part='day'][data-today] {
486
+ color: var(--slotted-calendar-today-color, var(--slotted-tone-accent-text));
487
+ font-weight: var(--slotted-font-weight-semibold, 600);
488
+ }
489
+
490
+ [data-ui='calendar'] [data-part='day']::before {
491
+ /* Reserved in every cell and painted in one, so the row keeps its line. */
492
+ background-color: transparent;
493
+ block-size: var(--slotted-space-2, 0.25rem);
494
+ border-radius: var(--slotted-radius-full, 9999px);
495
+ content: '';
496
+ flex: 0 0 auto;
497
+ inline-size: var(--slotted-space-2, 0.25rem);
498
+ /* After the digit: a `::before` is the first flex item until it is told
499
+ otherwise, and the mark belongs under the number. */
500
+ order: 1;
501
+ }
502
+
503
+ [data-ui='calendar'] [data-part='day'][data-today]::before {
504
+ background-color: currentcolor;
505
+ }
506
+
507
+ /**
508
+ * A day of the month either side. Quiet enough to read as context and legible
509
+ * enough to press, because a reader who wants 1 October while looking at
510
+ * September should be able to.
511
+ */
512
+ [data-ui='calendar'] [data-part='cell'][data-outside-month] [data-part='day'] {
513
+ color: var(--slotted-calendar-outside-color, var(--slotted-text-muted, GrayText));
514
+ }
515
+
516
+ /**
517
+ * Refused by the predicate. It stays focusable — `aria-disabled`, never
518
+ * `disabled` — so a reader can arrow onto it and be told why, and the strike
519
+ * is what says so without colour.
520
+ */
521
+ [data-ui='calendar'] [data-part='cell'][data-unavailable] [data-part='day'] {
522
+ color: var(--slotted-calendar-unavailable-color, var(--slotted-disabled-foreground));
523
+ cursor: not-allowed;
524
+ text-decoration: line-through;
525
+ text-decoration-thickness: var(--slotted-border-width-thin, 1px);
526
+ }
527
+
528
+ /**
529
+ * The band behind a run.
530
+ *
531
+ * It is painted on the cell rather than on the day, so it runs edge to edge
532
+ * while the day keeps its own shape on top of it — two shapes, and one
533
+ * element cannot be both. It covers the whole cell at both ends as well as
534
+ * in the middle: starting it at the midpoint of the end cell left a sliver
535
+ * of band showing past the solid end, which read as an extra border hanging
536
+ * half a cell outside the run.
537
+ *
538
+ * **It has no edge, and it is not a tone surface.** `accent-surface` is a
539
+ * ground calibrated to sit under text rather than to separate from it, and
540
+ * in `high-contrast` it is the page's own white — so a run drawn with it
541
+ * disappeared in one scheme out of three. The rules that fixed that were
542
+ * worse: two horizontal lines that stopped at every row break and ran out
543
+ * from under both ends.
544
+ *
545
+ * The foundation already names the answer for a ground with no edge, and
546
+ * `highlight` already draws it: mix the tone's `text` role, the one role
547
+ * every scheme keeps legible over the surface, at the share
548
+ * `--slotted-highlight-tint-opacity` holds. One share, one gesture, all
549
+ * three schemes, and nothing to align across a row break.
550
+ */
551
+ [data-ui='calendar'] [data-part='cell'][data-in-range]::before,
552
+ [data-ui='calendar'] [data-part='cell'][data-range-start]::before,
553
+ [data-ui='calendar'] [data-part='cell'][data-range-end]::before {
554
+ background-color: var(
555
+ --slotted-calendar-range-background,
556
+ color-mix(
557
+ in oklab,
558
+ var(--slotted-tone-accent-text) calc(var(--slotted-highlight-tint-opacity, 0.075) * 100%),
559
+ transparent
560
+ )
561
+ );
562
+ content: '';
563
+ inset: 0;
564
+ position: absolute;
565
+ }
566
+
567
+ /**
568
+ * The run's two outer corners, and no inner ones: the band is one shape that
569
+ * begins and ends, and a square cut at either end reads as a run that was
570
+ * clipped rather than one that stops.
571
+ */
572
+ [data-ui='calendar'] [data-part='cell'][data-range-start]::before {
573
+ border-end-start-radius: var(--slotted-calendar-day-radius, var(--slotted-radius-sm, 4px));
574
+ border-start-start-radius: var(--slotted-calendar-day-radius, var(--slotted-radius-sm, 4px));
575
+ }
576
+
577
+ [data-ui='calendar'] [data-part='cell'][data-range-end]::before {
578
+ border-end-end-radius: var(--slotted-calendar-day-radius, var(--slotted-radius-sm, 4px));
579
+ border-start-end-radius: var(--slotted-calendar-day-radius, var(--slotted-radius-sm, 4px));
580
+ }
581
+
582
+ /**
583
+ * A run has two outer corners and no inner ones. The end that faces into the
584
+ * range is square, so the solid meets the band edge to edge; the end that
585
+ * faces out keeps the day's own radius. A range of one day is both ends at
586
+ * once and stays round on both sides, which is why each rule excludes the
587
+ * other.
588
+ */
589
+ [data-ui='calendar']
590
+ [data-part='cell'][data-range-start]:not([data-range-end])
591
+ [data-part='day'] {
592
+ border-end-end-radius: 0;
593
+ border-start-end-radius: 0;
594
+ }
595
+
596
+ [data-ui='calendar']
597
+ [data-part='cell'][data-range-end]:not([data-range-start])
598
+ [data-part='day'] {
599
+ border-end-start-radius: 0;
600
+ border-start-start-radius: 0;
601
+ }
602
+
603
+ /**
604
+ * Inside the run the day gives up its inset and its corners, so the tint is
605
+ * continuous from one cell to the next and the run reads as one band rather
606
+ * than as a row of tinted squares with gaps between them.
607
+ */
608
+ [data-ui='calendar']
609
+ [data-part='cell']:is([data-in-range], [data-range-start], [data-range-end])
610
+ [data-part='day'] {
611
+ --_inset: 0px;
612
+ }
613
+
614
+ /**
615
+ * A day inside the run takes the accent's own text tone. The tint alone is a
616
+ * band read through; the ink is what says the day belongs to the run in a
617
+ * scheme where the band is faint, and it is the second half of the pair the
618
+ * foundation asks for where there is no edge to make one with.
619
+ */
620
+ [data-ui='calendar'] [data-part='cell'][data-in-range] [data-part='day'] {
621
+ border-radius: 0;
622
+ color: var(--slotted-calendar-in-range-color, var(--slotted-tone-accent-text));
623
+ }
624
+
625
+ /**
626
+ * ── The pending band ───────────────────────────────────────────────────
627
+ *
628
+ * While the run has one end, the band runs from it to the day the reader is
629
+ * on — under the pointer, or focused — and that band is a proposal rather
630
+ * than a choice. It takes the same tint at half the share, so the chosen run
631
+ * and the pending one are the same gesture at two weights, and the pending
632
+ * end takes an edge in the accent's border role rather than the solid the
633
+ * chosen end takes: a proposal is drawn as an outline, a choice as a fill.
634
+ */
635
+ [data-ui='calendar'] [data-part='cell'][data-preview]::before {
636
+ background-color: var(
637
+ --slotted-calendar-preview-background,
638
+ color-mix(
639
+ in oklab,
640
+ var(--slotted-tone-accent-text) calc(var(--slotted-highlight-tint-opacity, 0.075) * 50%),
641
+ transparent
642
+ )
643
+ );
644
+ }
645
+
646
+ [data-ui='calendar']
647
+ [data-part='cell'][data-preview]:is([data-range-start], [data-range-end]):not([data-selected])
648
+ [data-part='day'] {
649
+ box-shadow: inset 0 0 0 var(--slotted-border-width-thin, 1px)
650
+ var(--slotted-calendar-preview-border-color, var(--slotted-tone-accent-border));
651
+ color: var(--slotted-calendar-in-range-color, var(--slotted-tone-accent-text));
652
+ }
653
+
654
+ /* ── States ───────────────────────────────────────────────────────────
655
+ *
656
+ * Written strongest last, and each weaker one guarded against every stronger
657
+ * one it can meet, which is the order `STATE_PRECEDENCE` sets.
658
+ */
659
+
660
+ [data-ui='calendar'] [data-part='day']:hover:not([data-disabled]),
661
+ [data-ui='calendar'] [data-part='period']:hover:not(:disabled):not([data-disabled]),
662
+ [data-ui='calendar'] [data-part='heading']:hover:not(:disabled):not([data-disabled]),
663
+ [data-ui='calendar'] [data-part='previous']:hover:not(:disabled):not([data-disabled]),
664
+ [data-ui='calendar'] [data-part='next']:hover:not(:disabled):not([data-disabled]) {
665
+ background-color: var(
666
+ --slotted-calendar-hover-background,
667
+ var(--slotted-tone-secondary-subtle-hover)
668
+ );
669
+ }
670
+
671
+ [data-ui='calendar'] [data-part='day']:active:not([data-disabled]),
672
+ [data-ui='calendar'] [data-part='period']:active:not(:disabled):not([data-disabled]),
673
+ [data-ui='calendar'] [data-part='previous']:active:not(:disabled):not([data-disabled]),
674
+ [data-ui='calendar'] [data-part='next']:active:not(:disabled):not([data-disabled]) {
675
+ background-color: var(
676
+ --slotted-calendar-active-background,
677
+ var(--slotted-tone-secondary-subtle-active)
678
+ );
679
+ }
680
+
681
+ /**
682
+ * Inside a run the pointer takes the accent one step up rather than the
683
+ * neutral hover every other day takes. A grey wash over a blue band read as
684
+ * a hole in the run, and the reader's own selection is what the band is
685
+ * about — the pointer should deepen it, not neutralise it.
686
+ */
687
+ [data-ui='calendar']
688
+ [data-part='cell'][data-in-range]
689
+ [data-part='day']:hover:not([data-disabled]) {
690
+ background-color: var(
691
+ --slotted-calendar-range-hover-background,
692
+ var(--slotted-tone-accent-subtle-hover)
693
+ );
694
+ }
695
+
696
+ [data-ui='calendar']
697
+ [data-part='cell'][data-in-range]
698
+ [data-part='day']:active:not([data-disabled]) {
699
+ background-color: var(
700
+ --slotted-calendar-range-active-background,
701
+ var(--slotted-tone-accent-subtle-active)
702
+ );
703
+ }
704
+
705
+ /**
706
+ * A date the predicate refused answers neither the pointer nor the press,
707
+ * and it is written after the two rules above for that reason: the guards
708
+ * they carry are for `data-disabled`, and this state is the family's own.
709
+ */
710
+ [data-ui='calendar']
711
+ [data-part='cell'][data-unavailable]
712
+ [data-part='day']:hover:not([data-disabled]),
713
+ [data-ui='calendar']
714
+ [data-part='cell'][data-unavailable]
715
+ [data-part='day']:active:not([data-disabled]) {
716
+ background-color: transparent;
717
+ }
718
+
719
+ /**
720
+ * The chosen date. A fill rather than a ring, so it and today can be true at
721
+ * once and still read as two facts.
722
+ */
723
+ [data-ui='calendar'] [data-part='cell'][data-selected] [data-part='day'],
724
+ [data-ui='calendar'] [data-part='period'][data-selected] {
725
+ background-color: var(--slotted-calendar-selected-background, var(--slotted-tone-accent-solid));
726
+ color: var(--slotted-calendar-selected-color, var(--slotted-tone-accent-on-solid));
727
+ font-weight: var(--slotted-font-weight-semibold, 600);
728
+ }
729
+
730
+ [data-ui='calendar'] [data-part='day']:focus-visible,
731
+ [data-ui='calendar'] [data-part='period']:focus-visible,
732
+ [data-ui='calendar'] [data-part='heading']:focus-visible,
733
+ [data-ui='calendar'] [data-part='previous']:focus-visible,
734
+ [data-ui='calendar'] [data-part='next']:focus-visible {
735
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
736
+ /* Inside the cell rather than outside it: an offset ring on a grid of
737
+ touching cells would overlap the four neighbours and read as a smear. */
738
+ outline-offset: calc(var(--slotted-focus-ring-offset, 2px) * -1);
739
+ }
740
+
741
+ [data-ui='calendar'][data-part='root'][data-invalid] {
742
+ --slotted-focus-ring-color: var(
743
+ --slotted-calendar-invalid-focus-ring-color,
744
+ var(--slotted-focus-ring-color-invalid, Highlight)
745
+ );
746
+ }
747
+
748
+ /*
749
+ * Width as well as colour (WCAG 1.4.1): in `high-contrast` the invalid ring
750
+ * is the valid ring's black. The calendar has no frame to edge, so the ring
751
+ * is where invalid shows, and it widens by the edge every invalid control
752
+ * adds — inward, like the ring it replaces, so no neighbouring cell is
753
+ * covered.
754
+ */
755
+ [data-ui='calendar'][data-part='root'][data-invalid] [data-part='day']:focus-visible,
756
+ [data-ui='calendar'][data-part='root'][data-invalid] [data-part='period']:focus-visible,
757
+ [data-ui='calendar'][data-part='root'][data-invalid] [data-part='heading']:focus-visible,
758
+ [data-ui='calendar'][data-part='root'][data-invalid] [data-part='previous']:focus-visible,
759
+ [data-ui='calendar'][data-part='root'][data-invalid] [data-part='next']:focus-visible {
760
+ --_invalid-ring: calc(
761
+ var(--slotted-focus-ring-width, 2px) +
762
+ var(--slotted-calendar-invalid-edge-width, var(--slotted-control-invalid-edge-width, 1px))
763
+ );
764
+
765
+ outline-offset: calc(var(--_invalid-ring) * -1);
766
+ outline-width: var(--_invalid-ring);
767
+ }
768
+
769
+ /* A calendar the reader may read and not change: the days stop offering
770
+ themselves, and nothing is faded, because the value is still real. */
771
+ [data-ui='calendar'][data-part='root'][data-readonly] [data-part='day'],
772
+ [data-ui='calendar'][data-part='root'][data-readonly] [data-part='period'] {
773
+ cursor: default;
774
+ }
775
+
776
+ [data-ui='calendar'][data-part='root'][data-disabled] {
777
+ color: var(--slotted-calendar-disabled-color, var(--slotted-disabled-foreground));
778
+ }
779
+
780
+ /* Nothing a person cannot reach is painted in the tone that says "act on
781
+ me": today keeps its dot, in the ink a disabled control is allowed. */
782
+ [data-ui='calendar'][data-part='root'][data-disabled] [data-part='day'][data-today] {
783
+ color: var(--slotted-disabled-foreground);
784
+ }
785
+
786
+ [data-ui='calendar'][data-part='root'][data-disabled]
787
+ [data-part='cell'][data-selected]
788
+ [data-part='day'] {
789
+ background-color: var(--slotted-disabled-background);
790
+ color: var(--slotted-disabled-foreground);
791
+ }
792
+
793
+ [data-ui='calendar'][data-part='root'][data-disabled] [data-part='day'],
794
+ [data-ui='calendar'][data-part='root'][data-disabled] [data-part='period'],
795
+ [data-ui='calendar'][data-part='root'][data-disabled] [data-part='previous'],
796
+ [data-ui='calendar'][data-part='root'][data-disabled] [data-part='next'],
797
+ [data-ui='calendar'][data-part='root'][data-disabled] [data-part='heading'] {
798
+ cursor: not-allowed;
799
+ }
800
+
801
+ [data-ui='calendar'] [data-part='previous']:disabled,
802
+ [data-ui='calendar'] [data-part='next']:disabled {
803
+ color: var(--slotted-disabled-foreground);
804
+ cursor: not-allowed;
805
+ }
806
+
807
+ /* ── Reflow ───────────────────────────────────────────────────────────
808
+ *
809
+ * The component answers its own inline size, not the viewport's: a calendar
810
+ * in a 320-pixel drawer and the same calendar on a 1400-pixel page are the
811
+ * same width problem, and only a container query sees both.
812
+ *
813
+ * The breakpoints are literal because CSS forbids a custom property in a
814
+ * container condition, which `docs/token-foundation.md` records.
815
+ */
816
+ /**
817
+ * The months close up once there is no room to keep them a full step apart.
818
+ * It is the one query left in the sheet, and it is on a descendant rather
819
+ * than on the root because a container cannot query itself.
820
+ */
821
+ @container slotted-calendar (max-width: 320px) {
822
+ [data-ui='calendar'] [data-part='grids'] {
823
+ --_month-gap: var(--slotted-space-4, 0.5rem);
824
+ }
825
+ }
826
+
827
+ @media (forced-colors: active) {
828
+ /**
829
+ * A mixed tint has nothing to mix in a palette the system replaced, so the
830
+ * run takes an edge here and only here — two rules the operating system
831
+ * keeps, on a band that would otherwise be the page's own ground.
832
+ */
833
+ [data-ui='calendar'] [data-part='cell'][data-in-range]::before,
834
+ [data-ui='calendar'] [data-part='cell'][data-range-start]::before,
835
+ [data-ui='calendar'] [data-part='cell'][data-range-end]::before {
836
+ border-block: var(--slotted-border-width-thin, 1px) solid
837
+ var(--slotted-calendar-range-border-color, var(--slotted-tone-accent-border));
838
+ }
839
+
840
+ /* A proposal keeps its own distance from a choice here too: the pending
841
+ band's edges are dashed where the chosen run's are solid. */
842
+ [data-ui='calendar'] [data-part='cell'][data-preview]::before {
843
+ border-block-style: dashed;
844
+ }
845
+ }
846
+ }