@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,85 @@
1
+ [
2
+ "--slotted-attachment-action-radius",
3
+ "--slotted-attachment-action-size",
4
+ "--slotted-attachment-action-state-layer-active",
5
+ "--slotted-attachment-action-state-layer-hover",
6
+ "--slotted-attachment-background",
7
+ "--slotted-attachment-border-color",
8
+ "--slotted-attachment-border-width",
9
+ "--slotted-attachment-card-actions-opacity",
10
+ "--slotted-attachment-card-preview-radius",
11
+ "--slotted-attachment-card-size",
12
+ "--slotted-attachment-font-family",
13
+ "--slotted-attachment-grid-min",
14
+ "--slotted-attachment-group-gap",
15
+ "--slotted-attachment-icon-size-min",
16
+ "--slotted-attachment-line-gap",
17
+ "--slotted-attachment-name-font-weight",
18
+ "--slotted-attachment-padding-lg",
19
+ "--slotted-attachment-padding-md",
20
+ "--slotted-attachment-padding-sm",
21
+ "--slotted-attachment-preview-radius",
22
+ "--slotted-attachment-preview-size",
23
+ "--slotted-attachment-radius",
24
+ "--slotted-attachment-state-layer-active",
25
+ "--slotted-attachment-state-layer-hover",
26
+ "--slotted-attachment-strip-item-size",
27
+ "--slotted-border-strong",
28
+ "--slotted-border-subtle",
29
+ "--slotted-border-width-thin",
30
+ "--slotted-control-font-family",
31
+ "--slotted-control-font-size-lg",
32
+ "--slotted-control-font-size-md",
33
+ "--slotted-control-font-size-sm",
34
+ "--slotted-control-gap-lg",
35
+ "--slotted-control-gap-md",
36
+ "--slotted-control-gap-sm",
37
+ "--slotted-control-icon-size-lg",
38
+ "--slotted-control-icon-size-md",
39
+ "--slotted-control-icon-size-sm",
40
+ "--slotted-control-invalid-edge-width",
41
+ "--slotted-control-radius",
42
+ "--slotted-control-transition-duration",
43
+ "--slotted-control-transition-easing",
44
+ "--slotted-disabled-background",
45
+ "--slotted-disabled-foreground",
46
+ "--slotted-duration-instant",
47
+ "--slotted-duration-medium",
48
+ "--slotted-duration-short",
49
+ "--slotted-easing-enter",
50
+ "--slotted-easing-exit",
51
+ "--slotted-focus-ring-color",
52
+ "--slotted-focus-ring-offset",
53
+ "--slotted-focus-ring-width",
54
+ "--slotted-font-size-xs",
55
+ "--slotted-font-weight-medium",
56
+ "--slotted-font-weight-semibold",
57
+ "--slotted-line-height-md",
58
+ "--slotted-radius-lg",
59
+ "--slotted-radius-md",
60
+ "--slotted-radius-sm",
61
+ "--slotted-shadow-xs",
62
+ "--slotted-space-1",
63
+ "--slotted-space-2",
64
+ "--slotted-space-3",
65
+ "--slotted-space-4",
66
+ "--slotted-space-5",
67
+ "--slotted-surface",
68
+ "--slotted-target-comfortable",
69
+ "--slotted-target-min",
70
+ "--slotted-text",
71
+ "--slotted-text-muted",
72
+ "--slotted-tone-danger-border",
73
+ "--slotted-tone-danger-surface",
74
+ "--slotted-tone-danger-text",
75
+ "--slotted-upload-kind-archive",
76
+ "--slotted-upload-kind-audio",
77
+ "--slotted-upload-kind-document",
78
+ "--slotted-upload-kind-foreground",
79
+ "--slotted-upload-kind-image",
80
+ "--slotted-upload-kind-other",
81
+ "--slotted-upload-kind-pdf",
82
+ "--slotted-upload-kind-presentation",
83
+ "--slotted-upload-kind-spreadsheet",
84
+ "--slotted-upload-kind-video"
85
+ ]
@@ -0,0 +1,598 @@
1
+ @layer slotted.components {
2
+ /**
3
+ * ── The avatar box ─────────────────────────────────────────────────────
4
+ *
5
+ * A picture of a person or an entity, and what stands in when there is none.
6
+ * The order is the family's whole behaviour: the picture, then the initials
7
+ * taken from the name, then a glyph the family owns — so a request that fails
8
+ * never paints the browser's torn-page mark.
9
+ *
10
+ * Two attributes on every selector, always. Both parts sit inside the root,
11
+ * so the pair is what stops a rule meant for the box from reaching the
12
+ * picture inside it.
13
+ *
14
+ * The box is square by construction: one length, read from the avatar size
15
+ * scale the theme restates per density. Everything the family scales beside
16
+ * it — the type in the initials, the glyph, the square corner — is a
17
+ * proportion of that length rather than a scale of its own, which is the
18
+ * reasoning `badge` used for the overhang of an anchored count.
19
+ */
20
+ [data-ui='avatar'][data-part='root'] {
21
+ /**
22
+ * Tone. `secondary` reads the `solid-hover` step rather than `solid` for
23
+ * the reason `badge` records: `secondary.solid` is the surface colour in
24
+ * all three schemes, so a filled neutral avatar would be a hole in the page
25
+ * rather than a disc.
26
+ */
27
+ --_solid: var(--slotted-tone-secondary-solid-hover);
28
+ --_on-solid: var(--slotted-tone-secondary-on-solid);
29
+ --_surface: var(--slotted-tone-secondary-surface);
30
+ --_border: var(--slotted-tone-secondary-border);
31
+ --_text: var(--slotted-tone-secondary-text);
32
+
33
+ /**
34
+ * The size step. Written as the public token rather than into `--_size`
35
+ * directly, so an instance can state a size no step holds — a 96-pixel
36
+ * avatar on a profile header — with `style="--slotted-avatar-size: 6rem"`.
37
+ * It is the mechanism `icon` established, and the precedence it gives is
38
+ * the right one: a declaration on the element beats one inherited from a
39
+ * container, so `size` on the avatar always wins over the container's
40
+ * opinion of it.
41
+ */
42
+ --_size: var(--slotted-avatar-size, var(--slotted-avatar-size-md, 36px));
43
+
44
+ /**
45
+ * The initials fill a proportion of the box, not a step of the type scale.
46
+ * The smallest avatar is twenty pixels at `dense` and the type scale stops
47
+ * at twelve, so a scale step would be half the circle; and the whole point
48
+ * of the box is that the letters inside it look the same size at every
49
+ * step. Exposed as a token because the right proportion depends on the
50
+ * theme's typeface: a narrow grotesque wants more than a wide one.
51
+ */
52
+ --_initials-size: var(--slotted-avatar-initials-size, calc(var(--_size) * 0.4));
53
+
54
+ /**
55
+ * The silhouette is drawn a little larger than the letters, because it is
56
+ * a shape rather than a word and reads small at the same ratio.
57
+ */
58
+ --_glyph-size: var(--slotted-avatar-glyph-size, calc(var(--_size) * 0.68));
59
+
60
+ --_radius: var(--slotted-radius-full, 9999px);
61
+ --_edge-width: var(--slotted-avatar-border-width, var(--slotted-border-width-thin, 1px));
62
+ --_edge: var(--_border);
63
+ /**
64
+ * What a filled avatar draws around itself, and for four of the five tones
65
+ * the answer is its own fill: the edge is the disc, so there is no rim.
66
+ *
67
+ * `secondary` is the exception, and it is the only one, for the reason
68
+ * `badge` records against the same colour: the neutral fill is `#e6e6e6`
69
+ * against the page's own white in `high-contrast`, so a filled neutral
70
+ * avatar with no edge is not a disc, it is a smudge. Every other tone
71
+ * carries its own boundary there.
72
+ */
73
+ --_solid-edge: var(--_border);
74
+
75
+ /**
76
+ * What separates this avatar from whatever it is drawn over. Nought until a
77
+ * container asks for it, because a lone avatar overlaps nothing; a group
78
+ * turns it on for every member. It is a colour and not a boolean, because
79
+ * an avatar inside a card, a toolbar or a coloured row is not sitting on
80
+ * the page's own surface — the same reason `badge` gives for its own ring.
81
+ */
82
+ --_ring-width: var(--slotted-avatar-ring-width, 0);
83
+ --_ring-color: var(--slotted-avatar-ring-color, var(--slotted-surface, Canvas));
84
+
85
+ /**
86
+ * What the avatar is lifted off, and nothing at rest. A lone avatar sits on
87
+ * the page; a container that raises one of its members off the others says
88
+ * so here rather than restating the whole shadow — the ring below it is the
89
+ * avatar's own decision and a container has no business overwriting it,
90
+ * which is what a second `box-shadow` on the same box would do (AG1).
91
+ */
92
+ --_elevation: var(--slotted-avatar-elevation, 0 0 transparent);
93
+
94
+ align-items: center;
95
+ /**
96
+ * `render` can make the root a `<button>`, and the browser draws two
97
+ * pixels of `outset ButtonBorder` on one of those. The family states its
98
+ * own edge and its own inset below, so whatever the user agent had in mind
99
+ * is replaced rather than merely reset.
100
+ */
101
+ appearance: none;
102
+ block-size: var(--_size);
103
+ /**
104
+ * ── The edge ─────────────────────────────────────────────────────────
105
+ *
106
+ * The element's own border, and not a ring drawn over it.
107
+ *
108
+ * A pseudo-element was the first answer, so the rim could sit above the
109
+ * photograph. It cost a seam: the disc and the ring are two rounded shapes
110
+ * rasterised apart — `box-sizing` is not inherited, so the pseudo-element
111
+ * did not even use the same box — and two antialiased curves over the same
112
+ * path do not agree in the sub-pixel. On a circle the disagreement is a
113
+ * hairline of the page showing through between the fill and its rim.
114
+ *
115
+ * One border on one box has one curve, and the engine computes the inner
116
+ * radius from the outer one. `box-sizing: border-box` keeps the avatar
117
+ * exactly `--_size` however thick the rim is, and the picture is cropped
118
+ * inside the rim rather than under it — which is the right look anyway: a
119
+ * photograph in a frame, not a photograph with a line across it.
120
+ */
121
+ border-color: var(--_edge);
122
+ border-radius: var(--_radius);
123
+ /* Three longhands rather than the shorthand: an undefined colour makes a
124
+ `var()` shorthand invalid at computed-value time, and the width would
125
+ fall back to `medium` — three pixels — rather than to this one. */
126
+ border-style: solid;
127
+ border-width: var(--_edge-width);
128
+ box-shadow:
129
+ 0 0 0 var(--_ring-width) var(--_ring-color),
130
+ var(--_elevation);
131
+ box-sizing: border-box;
132
+ display: inline-flex;
133
+ /* A box that shrinks is not a box: an avatar keeps its size in a flex row. */
134
+ flex: none;
135
+ font-family: var(--slotted-avatar-font-family, var(--slotted-control-font-family, sans-serif));
136
+ font-size: var(--_initials-size);
137
+ font-weight: var(--slotted-avatar-font-weight, var(--slotted-font-weight-medium, 500));
138
+ inline-size: var(--_size);
139
+ justify-content: center;
140
+ line-height: 1;
141
+ padding: 0;
142
+ /**
143
+ * An avatar is one thing, and it paints as one thing.
144
+ *
145
+ * The rim used to be a pseudo-element carrying `z-index: 1`, and a positive
146
+ * `z-index` on a pseudo-element of a box whose own `z-index` is `auto`
147
+ * promotes it into the nearest ancestor stacking context rather than into
148
+ * its own. In a group every member's rim was drawn across the face on top
149
+ * of it, and the row was inconsistent as well as wrong: the one member the
150
+ * pointer had raised did create a stacking context, so it alone behaved.
151
+ * The rim is the element's own border now, and the isolation is what keeps
152
+ * the picture inside the avatar it belongs to.
153
+ */
154
+ isolation: isolate;
155
+ /**
156
+ * The picture and the initials are stacked rather than swapped, so the
157
+ * picture can arrive over the letters instead of replacing them in one
158
+ * frame. This is the containing block for both.
159
+ */
160
+ position: relative;
161
+ /**
162
+ * `render` can make the root an `<a>`, and a rule the browser writes on
163
+ * every anchor would put a line under two initials.
164
+ */
165
+ text-decoration: none;
166
+ /**
167
+ * The edge changes colour at the moment the picture arrives — the tone's
168
+ * own rim gives way to the neutral one a photograph wants — so it crosses
169
+ * on the same clock as the picture. Without it the rim snapped from red to
170
+ * grey a frame before the face had finished appearing, which was the one
171
+ * thing left moving on its own in the loading sequence.
172
+ *
173
+ * The ground crosses on the same clock, because hover and press answer on
174
+ * it; the ring and the transform are what a group raises a member with, and
175
+ * they are listed here so the group states only the curve and never
176
+ * replaces this list — it did, and the edge snapped inside a stack.
177
+ */
178
+ transition:
179
+ border-color var(--slotted-duration-short) var(--slotted-easing-standard),
180
+ background-color var(--slotted-duration-short) var(--slotted-easing-standard),
181
+ box-shadow var(--slotted-duration-short) var(--slotted-easing-standard),
182
+ transform var(--slotted-duration-short) var(--slotted-easing-standard);
183
+ /* Initials are a label, not a selection: dragging across a row of faces
184
+ should not highlight a column of letters. */
185
+ user-select: none;
186
+ /* Written beside a name, an avatar sits on the text's centre. */
187
+ vertical-align: middle;
188
+ }
189
+
190
+ /* ── Tone: which of the five semantic colours the name resolves to ─────── */
191
+
192
+ [data-ui='avatar'][data-part='root'][data-tone='accent'] {
193
+ --_solid: var(--slotted-tone-accent-solid);
194
+ --_solid-edge: var(--slotted-tone-accent-solid);
195
+ --_on-solid: var(--slotted-tone-accent-on-solid);
196
+ --_surface: var(--slotted-tone-accent-surface);
197
+ --_border: var(--slotted-tone-accent-border);
198
+ --_text: var(--slotted-tone-accent-text);
199
+ }
200
+
201
+ [data-ui='avatar'][data-part='root'][data-tone='success'] {
202
+ --_solid: var(--slotted-tone-success-solid);
203
+ --_solid-edge: var(--slotted-tone-success-solid);
204
+ --_on-solid: var(--slotted-tone-success-on-solid);
205
+ --_surface: var(--slotted-tone-success-surface);
206
+ --_border: var(--slotted-tone-success-border);
207
+ --_text: var(--slotted-tone-success-text);
208
+ }
209
+
210
+ [data-ui='avatar'][data-part='root'][data-tone='warning'] {
211
+ --_solid: var(--slotted-tone-warning-solid);
212
+ --_solid-edge: var(--slotted-tone-warning-solid);
213
+ --_on-solid: var(--slotted-tone-warning-on-solid);
214
+ --_surface: var(--slotted-tone-warning-surface);
215
+ --_border: var(--slotted-tone-warning-border);
216
+ --_text: var(--slotted-tone-warning-text);
217
+ }
218
+
219
+ [data-ui='avatar'][data-part='root'][data-tone='danger'] {
220
+ --_solid: var(--slotted-tone-danger-solid);
221
+ --_solid-edge: var(--slotted-tone-danger-solid);
222
+ --_on-solid: var(--slotted-tone-danger-on-solid);
223
+ --_surface: var(--slotted-tone-danger-surface);
224
+ --_border: var(--slotted-tone-danger-border);
225
+ --_text: var(--slotted-tone-danger-text);
226
+ }
227
+
228
+ /**
229
+ * ── Variant: two paintings ─────────────────────────────────────────────
230
+ *
231
+ * `soft` tints and keeps the tone's own edge, which is what holds the disc
232
+ * together in the `high-contrast` scheme where every tone surface collapses
233
+ * to the page's own white. `solid` fills and draws no rim, because its edge
234
+ * is its own fill.
235
+ *
236
+ * There is no `outline`: an avatar drawn as a ring around nothing is not an
237
+ * avatar, and every one of the fourteen surveyed implementations fills.
238
+ */
239
+ [data-ui='avatar'][data-part='root'][data-variant='soft'] {
240
+ --_ground: var(--_surface, ButtonFace);
241
+
242
+ background-color: var(--_ground);
243
+ color: var(--_text, ButtonText);
244
+ }
245
+
246
+ [data-ui='avatar'][data-part='root'][data-variant='solid'] {
247
+ --_edge: var(--_solid-edge);
248
+ --_ground: var(--_solid, ButtonFace);
249
+
250
+ background-color: var(--_ground);
251
+ color: var(--_on-solid, ButtonText);
252
+ }
253
+
254
+ /* ── Shape ────────────────────────────────────────────────────────────── */
255
+
256
+ /**
257
+ * Primer's rule, taken with its meaning: a circle is a person and a square is
258
+ * a team, an organisation or a bot.
259
+ *
260
+ * The corner is a sixth of the box rather than a step of the radius scale.
261
+ * `radius-md` is six pixels, which is a gentle round on a 64-pixel square and
262
+ * a quarter-circle on a 20-pixel one; a proportion is the same shape at every
263
+ * step. A theme that disagrees states the token.
264
+ *
265
+ * Rounded to the pixel: a sixth of 64 is 10.67 and of 28 is 4.67, and a
266
+ * fractional radius spreads the curve over partial pixels at every corner.
267
+ */
268
+ [data-ui='avatar'][data-part='root'][data-shape='square'] {
269
+ --_radius: var(--slotted-avatar-radius, round(nearest, calc(var(--_size) / 6), 1px));
270
+ }
271
+
272
+ /* ── Size ─────────────────────────────────────────────────────────────── */
273
+
274
+ [data-ui='avatar'][data-part='root'][data-size='xs'] {
275
+ --slotted-avatar-size: var(--slotted-avatar-size-xs, 24px);
276
+ }
277
+
278
+ [data-ui='avatar'][data-part='root'][data-size='sm'] {
279
+ --slotted-avatar-size: var(--slotted-avatar-size-sm, 28px);
280
+ }
281
+
282
+ [data-ui='avatar'][data-part='root'][data-size='md'] {
283
+ --slotted-avatar-size: var(--slotted-avatar-size-md, 36px);
284
+ }
285
+
286
+ [data-ui='avatar'][data-part='root'][data-size='lg'] {
287
+ --slotted-avatar-size: var(--slotted-avatar-size-lg, 48px);
288
+ }
289
+
290
+ [data-ui='avatar'][data-part='root'][data-size='xl'] {
291
+ --slotted-avatar-size: var(--slotted-avatar-size-xl, 64px);
292
+ }
293
+
294
+ /**
295
+ * ── The picture ──────────────────────────────────────────────────────── */
296
+
297
+ /**
298
+ * Always rendered, never in the flow. It lies over the initials at opacity
299
+ * nought and rises to one when the load succeeds, so a photograph that
300
+ * arrives four hundred milliseconds after the page fades in over the letters
301
+ * instead of replacing them between two frames. A list of twenty avatars
302
+ * without the transition snaps letter-by-photograph as each request returns.
303
+ *
304
+ * `prefers-reduced-motion` collapses the duration in the theme, and nothing
305
+ * here loops.
306
+ */
307
+ [data-ui='avatar'] [data-part='image'] {
308
+ block-size: 100%;
309
+ border-radius: inherit;
310
+ inline-size: 100%;
311
+ inset: 0;
312
+ object-fit: cover;
313
+ opacity: 0;
314
+ position: absolute;
315
+ transition: opacity var(--slotted-duration-short) var(--slotted-easing-exit);
316
+ }
317
+
318
+ /**
319
+ * A transition runs on the curve of the state it arrives at: the picture
320
+ * enters on the entering curve and, when a later request fails, leaves on the
321
+ * exiting one. The fallback below is the mirror of it.
322
+ */
323
+ [data-ui='avatar'][data-status='loaded'] [data-part='image'] {
324
+ opacity: 1;
325
+ transition-timing-function: var(--slotted-easing-enter);
326
+ }
327
+
328
+ /* ── What stands in for it ────────────────────────────────────────────── */
329
+
330
+ [data-ui='avatar'] [data-part='fallback'] {
331
+ /**
332
+ * The last step of the fallback order, and the only artwork this family
333
+ * owns. It is reached when there is no picture and no name to take
334
+ * initials from — an unassigned ticket, an anonymous visitor — and the
335
+ * binding draws it as the `person` glyph in an `Icon` rather than as a
336
+ * character, which no font can be relied on to carry.
337
+ *
338
+ * The size is set on the slot rather than on the icon, so an icon the
339
+ * consumer projects without a size of its own is drawn at the same step,
340
+ * and one that states a size keeps it.
341
+ */
342
+ --slotted-icon-size: var(--_glyph-size);
343
+
344
+ align-items: center;
345
+ block-size: 100%;
346
+ border-radius: inherit;
347
+ display: flex;
348
+ inline-size: 100%;
349
+ justify-content: center;
350
+ /**
351
+ * Initials are two graphemes, and a consumer who writes a word into the
352
+ * slot has written a word into a circle. Clipping is visual only:
353
+ * `overflow` removes nothing from the document or from the accessibility
354
+ * tree, which is what the briefing's rule about truncation protects.
355
+ */
356
+ overflow: hidden;
357
+ transition: opacity var(--slotted-duration-short) var(--slotted-easing-enter);
358
+ }
359
+
360
+ [data-ui='avatar'][data-status='loaded'] [data-part='fallback'] {
361
+ /**
362
+ * It leaves as the picture arrives rather than the instant the load
363
+ * reports, so the two cross rather than flicker. It is still painted
364
+ * underneath, which is what a photograph with transparency is drawn on.
365
+ */
366
+ opacity: 0;
367
+ transition-timing-function: var(--slotted-easing-exit);
368
+ }
369
+
370
+ /**
371
+ * A flex container centres the line box, and a line box is not symmetric
372
+ * around the letters inside it: the ascender reaches further above the cap
373
+ * than the descender reaches below the baseline, and two initials have no
374
+ * descender. Measured with the Geist face at `compact`, `AL` sat 0.56 pixels
375
+ * high in the 32-pixel disc and 0.68 high in the 26-pixel one; at
376
+ * `comfortable`, 1.13 high in the 64-pixel one. `text-box` trims the line to
377
+ * the cap and the baseline, so what is centred is the ink rather than the
378
+ * font's metrics — the rule `badge`, `kbd`, `tag` and `button` each carry.
379
+ *
380
+ * Those four have a label part to carry it. The initials are written straight
381
+ * into the fallback, and `text-box-trim` needs a block container — a flex
382
+ * container's text lives in an anonymous item no selector can reach — so the
383
+ * fallback itself becomes the block and centres its one line with
384
+ * `align-content`, which block containers have taken since Chrome 123 and
385
+ * Safari 17.4, both earlier than the trim. The box keeps its full size, so
386
+ * the disc still clips a long word and no descender is beheaded: the clip is
387
+ * the whole fallback, not the trimmed line, which is the room `badge` had to
388
+ * buy with padding.
389
+ *
390
+ * Only a fallback holding nothing but text is trimmed. A projected logo or
391
+ * icon is not a line of type: it sits on the baseline of a line whose trim is
392
+ * measured from the font, and trimming that line would move the artwork
393
+ * rather than centre it. `:has(> *)` is the whole test, and it excludes the
394
+ * family's own glyph as well, which is an icon and needs no line at all.
395
+ */
396
+ @supports (text-box: trim-both cap alphabetic) {
397
+ [data-ui='avatar'] [data-part='fallback']:not(:has(> *)) {
398
+ align-content: center;
399
+ display: block;
400
+ text-align: center;
401
+ text-box: trim-both cap alphabetic;
402
+ }
403
+ }
404
+
405
+ /* ── The picture that arrived, and the one that did not ───────────────── */
406
+
407
+ /**
408
+ * A photograph does not want a coloured rim, and a photograph of a white wall
409
+ * on a white surface with no rim at all has no boundary. `border-subtle` is
410
+ * the token that separates one surface from another everywhere else.
411
+ *
412
+ * Written after the variants and before `disabled`, so the three states
413
+ * resolve in the order `STATE_PRECEDENCE` declares with no selector weight
414
+ * standing in for the order.
415
+ */
416
+ [data-ui='avatar'][data-part='root'][data-status='loaded'] {
417
+ --_edge: var(--slotted-border-subtle, ButtonBorder);
418
+ }
419
+
420
+ /* ── Disabled ─────────────────────────────────────────────────────────── */
421
+
422
+ /**
423
+ * A deactivated account, or an avatar inside a control the page has turned
424
+ * off. It stays legible rather than merely faded: the letters keep the
425
+ * disabled foreground, which the theme holds against the disabled ground, and
426
+ * the photograph loses its colour rather than its contrast.
427
+ */
428
+ [data-ui='avatar'][data-part='root'][data-disabled] {
429
+ --_solid: var(--slotted-disabled-background, Canvas);
430
+ --_on-solid: var(--slotted-disabled-foreground, GrayText);
431
+ --_surface: var(--slotted-disabled-background, Canvas);
432
+ --_text: var(--slotted-disabled-foreground, GrayText);
433
+ --_edge: var(--slotted-disabled-border, GrayText);
434
+ }
435
+
436
+ /**
437
+ * Colour alone would not be enough and colour alone is not what changes: the
438
+ * photograph loses its hue and part of its opacity, so the disabled ground
439
+ * shows through and a switched-off face reads as switched off beside one that
440
+ * has no photograph at all. Grey alone kept every luminance, which made a
441
+ * deactivated portrait the strongest thing in a row of disabled avatars.
442
+ */
443
+ [data-ui='avatar'][data-part='root'][data-disabled] [data-part='image'] {
444
+ filter: var(--slotted-avatar-disabled-image-filter, grayscale(1) opacity(0.5));
445
+ }
446
+
447
+ /* ── Interaction ──────────────────────────────────────────────────────── */
448
+
449
+ /**
450
+ * An avatar is a picture, not a command, and paints nothing at rest. These
451
+ * three selectors are the ones the browser itself treats as interactive, and
452
+ * they are written apart rather than gathered into `:is()` because
453
+ * `packages/styles/src/states.test.mjs` reads one comma-separated selector at
454
+ * a time and a guard inside a list would be invisible to it.
455
+ *
456
+ * A weak state held back for the stronger one: a disabled avatar answers no
457
+ * hover, which is the rule that test exists to keep.
458
+ */
459
+ [data-ui='avatar'][data-part='root']:any-link,
460
+ button[data-ui='avatar'][data-part='root'],
461
+ [data-ui='avatar'][data-part='root'][tabindex] {
462
+ cursor: pointer;
463
+ /**
464
+ * The target below hangs outside the box on purpose. A box whose content
465
+ * can leave it is a different statement, and it is the one an avatar never
466
+ * makes: a face is cropped by its own edge, and the only thing standing
467
+ * outside the disc is a hit area that draws nothing. `clip` says so, and
468
+ * saying so is what separates the two — `tools/harness/audit.mjs` read the
469
+ * hit area as content leaving the box nine times per density at `dense`
470
+ * (AV1), which is the reading `visible` invites.
471
+ *
472
+ * The margin keeps the target painted and pressed: the clip shape is the
473
+ * padding box grown by this length, corner radii grown with it, so a
474
+ * circular avatar is clipped to a larger circle rather than to a square,
475
+ * and the corners of the 24-pixel square still answer a press. One target's
476
+ * length is more than any centred target can reach past a box — half is the
477
+ * exact figure — and the property takes a plain length: Chrome rejects
478
+ * `calc()` here, and a rejected value computes to nought, which clips the
479
+ * target to the face.
480
+ */
481
+ overflow: clip;
482
+ overflow-clip-margin: var(--slotted-target-min, 24px);
483
+ }
484
+
485
+ /**
486
+ * Hover and press answer on the ground, the way `tag` answers on its own: the
487
+ * letters' colour laid over the fill at eight and fourteen per cent. The rim
488
+ * keeps the tone. It used to turn `border-strong` on hover, which took the
489
+ * colour out of the one thing the pointer was on, and in `high-contrast` the
490
+ * tone's rim and `border-strong` are both black, so the hover changed nothing.
491
+ *
492
+ * Press exists because `render` makes the root a link or a button, and a
493
+ * command that does not answer the press reads as one that did not register.
494
+ */
495
+ [data-ui='avatar'][data-part='root']:any-link:not([data-disabled]):hover,
496
+ button[data-ui='avatar'][data-part='root']:not([data-disabled]):hover,
497
+ [data-ui='avatar'][data-part='root'][tabindex]:not([data-disabled]):hover {
498
+ background-color: color-mix(
499
+ in oklab,
500
+ var(--_ground),
501
+ currentColor var(--slotted-avatar-state-layer-hover, 8%)
502
+ );
503
+ }
504
+
505
+ [data-ui='avatar'][data-part='root']:any-link:not([data-disabled]):active,
506
+ button[data-ui='avatar'][data-part='root']:not([data-disabled]):active,
507
+ [data-ui='avatar'][data-part='root'][tabindex]:not([data-disabled]):active {
508
+ background-color: color-mix(
509
+ in oklab,
510
+ var(--_ground),
511
+ currentColor var(--slotted-avatar-state-layer-active, 14%)
512
+ );
513
+ }
514
+
515
+ /**
516
+ * A photograph covers the ground, so a face answers on its frame instead: the
517
+ * rim steps to `border-strong` and a ring of the same colour grows outside it,
518
+ * one pixel under the pointer and two under the press. The ring is what keeps
519
+ * the hover visible in `high-contrast`, where the rim is black before and
520
+ * after.
521
+ *
522
+ * One attribute fewer than the group's raise on purpose. Inside a stack the
523
+ * ring belongs to the group — the surface-coloured gap between a raised face
524
+ * and the one it covers — and the lower weight is what lets the group's
525
+ * declaration of the same token win without either file knowing the order
526
+ * the other is loaded in.
527
+ */
528
+ [data-ui='avatar'][data-status='loaded']:any-link:not([data-disabled]):hover,
529
+ button[data-ui='avatar'][data-status='loaded']:not([data-disabled]):hover,
530
+ [data-ui='avatar'][data-status='loaded'][tabindex]:not([data-disabled]):hover {
531
+ --_edge: var(--slotted-border-strong, CanvasText);
532
+ --slotted-avatar-ring-width: var(--slotted-border-width-thin, 1px);
533
+ --slotted-avatar-ring-color: var(--slotted-border-strong, CanvasText);
534
+ }
535
+
536
+ [data-ui='avatar'][data-status='loaded']:any-link:not([data-disabled]):active,
537
+ button[data-ui='avatar'][data-status='loaded']:not([data-disabled]):active,
538
+ [data-ui='avatar'][data-status='loaded'][tabindex]:not([data-disabled]):active {
539
+ --slotted-avatar-ring-width: var(--slotted-border-width-thick, 2px);
540
+ }
541
+
542
+ /**
543
+ * Painted whatever the element turned out to be. An avatar is not focusable
544
+ * on its own, and a ring that appears only where a family expects focus is a
545
+ * ring that goes missing the first time a consumer makes one a link.
546
+ */
547
+ [data-ui='avatar'][data-part='root']:focus-visible {
548
+ outline: var(--slotted-focus-ring-width, 2px) solid var(--slotted-focus-ring-color, Highlight);
549
+ outline-offset: var(--slotted-focus-ring-offset, 2px);
550
+ }
551
+
552
+ /**
553
+ * WCAG 2.2 2.5.8: an interactive target is at least 24 by 24 CSS pixels, and
554
+ * `--slotted-avatar-size-xs` is twenty at the two tighter densities. The
555
+ * overlay grows the hit area without moving anything: it is out of flow, and
556
+ * it is never larger than the box it belongs to when the box already passes.
557
+ *
558
+ * One negative inset on all four sides, and not a size with `margin: auto`.
559
+ * Auto margins centre a box that fits; a box wider than the space it is
560
+ * placed in is over-constrained, and the rule the engine applies then is to
561
+ * keep the start margin and put the whole difference on the end one. So the
562
+ * 24-pixel target sat against the leading edge of a 20-pixel avatar and hung
563
+ * four pixels off the trailing one: half the target was outside the face on
564
+ * one side and the other half was missing on the other, which is what AV1
565
+ * measured as four pixels of overflow. `inset` states the same target from
566
+ * both edges at once, so it cannot be resolved to one side, and `min(0px, …)`
567
+ * makes it inert on every box that already reaches the minimum. It is the
568
+ * idiom `tag` and `breadcrumb` use for the same requirement.
569
+ *
570
+ * Fluent ships a 16-pixel avatar and Atlassian a 16- and a 20-pixel one, and
571
+ * both accept a click on it as it stands.
572
+ */
573
+ [data-ui='avatar'][data-part='root']:any-link::after,
574
+ button[data-ui='avatar'][data-part='root']::after,
575
+ [data-ui='avatar'][data-part='root'][tabindex]::after {
576
+ content: '';
577
+ inset: min(0px, calc((100% - var(--slotted-target-min, 24px)) / 2));
578
+ position: absolute;
579
+ }
580
+
581
+ /**
582
+ * ── Forced colours ─────────────────────────────────────────────────────
583
+ *
584
+ * The theme maps every tone ground to `ButtonFace` or `Canvas`, so a tinted
585
+ * avatar and a filled one become the same disc and an avatar whose edge
586
+ * happened to match its fill has no edge at all. `currentColor` is the only
587
+ * paint left that still separates a face from the page.
588
+ *
589
+ * The picture is exempt: `forced-color-adjust` is not applied to images by
590
+ * the user agent, and a photograph reduced to two system colours is not a
591
+ * photograph.
592
+ */
593
+ @media (forced-colors: active) {
594
+ [data-ui='avatar'][data-part='root'] {
595
+ --_edge: currentColor;
596
+ }
597
+ }
598
+ }