@astryxdesign/core 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +41 -3
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
  4. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
  9. package/dist/Center/Center.d.ts +23 -16
  10. package/dist/Center/Center.d.ts.map +1 -1
  11. package/dist/Center/Center.js +7 -5
  12. package/dist/CodeBlock/CodeBlock.js +2 -2
  13. package/dist/DateInput/DateInput.d.ts.map +1 -1
  14. package/dist/DateInput/DateInput.js +12 -2
  15. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  16. package/dist/DateTimeInput/DateTimeInput.js +12 -2
  17. package/dist/Field/Field.d.ts.map +1 -1
  18. package/dist/Field/Field.js +1 -0
  19. package/dist/Field/InputClearButton.d.ts +2 -2
  20. package/dist/Field/InputClearButton.d.ts.map +1 -1
  21. package/dist/Field/InputClearButton.js +5 -1
  22. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  23. package/dist/Field/PanelSearchInput.js +16 -4
  24. package/dist/FileInput/FileInput.d.ts.map +1 -1
  25. package/dist/FileInput/FileInput.js +12 -1
  26. package/dist/HoverCard/useHoverCard.js +2 -2
  27. package/dist/Indicator/CheckboxIndicator.js +2 -2
  28. package/dist/Indicator/RadioIndicator.js +2 -2
  29. package/dist/Layer/layerStack.d.ts +10 -0
  30. package/dist/Layer/layerStack.d.ts.map +1 -1
  31. package/dist/Layer/layerStack.js +21 -9
  32. package/dist/Layer/useLayerDismissal.d.ts +2 -3
  33. package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
  34. package/dist/Layer/useLayerDismissal.js +2 -3
  35. package/dist/NavIcon/NavIcon.js +2 -2
  36. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  37. package/dist/NumberInput/NumberInput.js +12 -2
  38. package/dist/Popover/usePopover.d.ts +3 -2
  39. package/dist/Popover/usePopover.d.ts.map +1 -1
  40. package/dist/Popover/usePopover.js +4 -2
  41. package/dist/ProgressBar/ProgressBar.js +2 -2
  42. package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
  43. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
  44. package/dist/ScrollableArea/ScrollableArea.js +144 -0
  45. package/dist/ScrollableArea/index.d.ts +11 -0
  46. package/dist/ScrollableArea/index.d.ts.map +1 -0
  47. package/dist/ScrollableArea/index.js +11 -0
  48. package/dist/StatusDot/StatusDot.js +2 -2
  49. package/dist/TextArea/TextArea.js +2 -2
  50. package/dist/TextInput/TextInput.d.ts.map +1 -1
  51. package/dist/TextInput/TextInput.js +19 -4
  52. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  53. package/dist/TimeInput/TimeInput.js +12 -2
  54. package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
  55. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  56. package/dist/Typeahead/BaseTypeahead.js +56 -20
  57. package/dist/astryx.css +15 -0
  58. package/dist/hooks/index.d.ts +2 -0
  59. package/dist/hooks/index.d.ts.map +1 -1
  60. package/dist/hooks/index.js +1 -0
  61. package/dist/hooks/scrollGeometry.d.ts +24 -0
  62. package/dist/hooks/scrollGeometry.d.ts.map +1 -0
  63. package/dist/hooks/scrollGeometry.js +86 -0
  64. package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
  65. package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
  66. package/dist/hooks/scrollOwnerRegistry.js +24 -0
  67. package/dist/hooks/useFocusTrap.d.ts +8 -0
  68. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  69. package/dist/hooks/useFocusTrap.js +22 -11
  70. package/dist/hooks/useScrollableArea.d.ts +51 -0
  71. package/dist/hooks/useScrollableArea.d.ts.map +1 -0
  72. package/dist/hooks/useScrollableArea.js +287 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -0
  76. package/dist/theme/defineTheme.d.ts +2 -6
  77. package/dist/theme/defineTheme.d.ts.map +1 -1
  78. package/dist/theme/defineTheme.js +1 -1
  79. package/dist/theme/derivedVarRegistry.js +1 -1
  80. package/dist/theme/localTokens.d.ts +8 -11
  81. package/dist/theme/localTokens.d.ts.map +1 -1
  82. package/dist/theme/localTokens.js +17 -71
  83. package/dist/theme/themeAdaptations.d.ts.map +1 -1
  84. package/dist/theme/themeAdaptations.js +4 -4
  85. package/dist/utils/themeProps.d.ts +10 -10
  86. package/dist/utils/themeProps.d.ts.map +1 -1
  87. package/dist/utils/themeProps.js +27 -10
  88. package/locales/en.json +16 -0
  89. package/locales/pseudo.json +12 -0
  90. package/package.json +7 -2
  91. package/src/AppShell/AppShell.test.tsx +36 -0
  92. package/src/AppShell/AppShell.tsx +4 -1
  93. package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
  94. package/src/Banner/Banner.test.tsx +3 -1
  95. package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
  96. package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
  97. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
  98. package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
  99. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
  100. package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
  101. package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
  102. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
  103. package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
  104. package/src/Center/Center.doc.mjs +32 -28
  105. package/src/Center/Center.spec.md +225 -0
  106. package/src/Center/Center.test.tsx +42 -4
  107. package/src/Center/Center.tsx +24 -17
  108. package/src/Chat/ChatSystemMessage.test.tsx +2 -9
  109. package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
  110. package/src/CodeBlock/CodeBlock.tsx +2 -2
  111. package/src/DateInput/DateInput.test.tsx +4 -4
  112. package/src/DateInput/DateInput.tsx +15 -4
  113. package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
  114. package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
  115. package/src/DateTimeInput/DateTimeInput.tsx +18 -7
  116. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
  117. package/src/Field/Field.test.tsx +42 -0
  118. package/src/Field/Field.tsx +6 -0
  119. package/src/Field/InputClearButton.test.tsx +35 -1
  120. package/src/Field/InputClearButton.tsx +7 -3
  121. package/src/Field/PanelSearchInput.tsx +21 -8
  122. package/src/FieldStatus/FieldStatus.spec.md +27 -17
  123. package/src/FieldStatus/FieldStatus.test.tsx +7 -5
  124. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
  125. package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
  126. package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
  127. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
  128. package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
  129. package/src/FileInput/FileInput.tsx +10 -1
  130. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
  131. package/src/HoverCard/HoverCard.doc.mjs +4 -4
  132. package/src/HoverCard/useHoverCard.tsx +2 -2
  133. package/src/Indicator/CheckboxIndicator.tsx +2 -2
  134. package/src/Indicator/Indicator.doc.mjs +2 -2
  135. package/src/Indicator/Indicator.test.tsx +1 -1
  136. package/src/Indicator/RadioIndicator.tsx +2 -2
  137. package/src/Layer/layerStack.ts +20 -9
  138. package/src/Layer/useLayerDismissal.ts +2 -3
  139. package/src/MultiSelector/MultiSelector.test.tsx +4 -4
  140. package/src/NavIcon/NavIcon.doc.mjs +4 -4
  141. package/src/NavIcon/NavIcon.tsx +2 -2
  142. package/src/NumberInput/NumberInput.tsx +18 -7
  143. package/src/Popover/Popover.doc.mjs +10 -10
  144. package/src/Popover/Popover.spec.md +55 -65
  145. package/src/Popover/Popover.test.tsx +29 -0
  146. package/src/Popover/usePopover.doc.mjs +4 -4
  147. package/src/Popover/usePopover.tsx +7 -4
  148. package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
  149. package/src/ProgressBar/ProgressBar.test.tsx +1 -31
  150. package/src/ProgressBar/ProgressBar.tsx +2 -2
  151. package/src/RadioList/RadioList.test.tsx +5 -144
  152. package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
  153. package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
  154. package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
  155. package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
  156. package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
  157. package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
  158. package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
  159. package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
  160. package/src/ScrollableArea/ScrollableArea.tsx +259 -0
  161. package/src/ScrollableArea/index.ts +26 -0
  162. package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
  163. package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
  164. package/src/Selector/Selector.test.tsx +4 -4
  165. package/src/Spinner/Spinner.test.tsx +0 -18
  166. package/src/StatusDot/StatusDot.doc.mjs +4 -4
  167. package/src/StatusDot/StatusDot.tsx +2 -2
  168. package/src/TabList/TabList.test.tsx +5 -9
  169. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
  170. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
  171. package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
  172. package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
  173. package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
  174. package/src/Table/Table.doc.mjs +2 -2
  175. package/src/TextArea/TextArea.doc.mjs +4 -4
  176. package/src/TextArea/TextArea.tsx +2 -2
  177. package/src/TextInput/TextInput.doc.mjs +2 -1
  178. package/src/TextInput/TextInput.test.tsx +94 -0
  179. package/src/TextInput/TextInput.tsx +22 -6
  180. package/src/TimeInput/TimeInput.tsx +18 -7
  181. package/src/Toast/ToastViewport.test.tsx +1 -39
  182. package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
  183. package/src/Typeahead/BaseTypeahead.spec.md +269 -0
  184. package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
  185. package/src/Typeahead/BaseTypeahead.tsx +99 -30
  186. package/src/hooks/index.ts +13 -0
  187. package/src/hooks/scrollGeometry.ts +155 -0
  188. package/src/hooks/scrollOwnerRegistry.ts +47 -0
  189. package/src/hooks/useFocusTrap.ts +22 -11
  190. package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
  191. package/src/hooks/useScrollableArea.doc.mjs +108 -0
  192. package/src/hooks/useScrollableArea.test.tsx +437 -0
  193. package/src/hooks/useScrollableArea.ts +469 -0
  194. package/src/index.ts +1 -0
  195. package/src/theme/defineTheme.test.ts +65 -105
  196. package/src/theme/defineTheme.ts +3 -9
  197. package/src/theme/derivedVarRegistry.ts +1 -1
  198. package/src/theme/localTokens.ts +25 -96
  199. package/src/theme/publicThemeHelperContract.test.ts +2 -2
  200. package/src/theme/themeAdaptations.test.ts +16 -42
  201. package/src/theme/themeAdaptations.ts +6 -9
  202. package/src/utils/themeProps.test.ts +29 -10
  203. package/src/utils/themeProps.ts +36 -17
@@ -0,0 +1,225 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 4
4
+ kind: component
5
+ id: component:BreadcrumbItem
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Breadcrumbs/Breadcrumbs.test.tsx,
16
+ apps/storybook/stories/BreadcrumbItem.stories.tsx,
17
+ packages/core/src/theme/themingTargets.test.ts,
18
+ scripts/check-knowledge.mjs,
19
+ ]
20
+ modules: []
21
+ families: [family:navigation-destinations, family:overlay-dismissal]
22
+ design_specs: []
23
+ architecture:
24
+ [
25
+ architecture:component-style-authoring,
26
+ architecture:component-test-sufficiency,
27
+ architecture:component-theming-surface,
28
+ architecture:interaction-modality,
29
+ architecture:layer-runtime,
30
+ architecture:public-component-api,
31
+ architecture:react-component-runtime,
32
+ ]
33
+ contributing: [contributing:api-conventions]
34
+ system_specs: [spec:AST-002, spec:AST-005, spec:AST-029]
35
+ ---
36
+
37
+ # BreadcrumbItem component contract
38
+
39
+ ## Intent
40
+
41
+ BreadcrumbItem presents one destination, action, current location, or sibling-menu
42
+ trigger inside a Breadcrumbs trail. This observational draft records verified
43
+ released behavior and current shared obligations without changing public API,
44
+ defaults, or the component's visual design.
45
+
46
+ ## Compatibility and migration
47
+
48
+ - Released default preserved: `yes`
49
+ - Compatibility class: additive observational documentation plus bug fixes that
50
+ preserve the released public surface
51
+ - Controlled/uncontrolled behavior: not applicable
52
+ - Migration decision: none
53
+
54
+ Consumer migration instructions belong in consumer docs and release notes.
55
+
56
+ ## Ownership boundary
57
+
58
+ **Owns**
59
+
60
+ - The list-item root, the item-content branch selected from current public inputs,
61
+ and forwarding the documented item ref and `BaseProps` surface to that root.
62
+ - The decorative separator container rendered for its position in the trail.
63
+ - The link-styled action or menu trigger and BreadcrumbItem-specific menu surface,
64
+ including their current theme targets.
65
+ - Deriving the menu-item size from the parent Breadcrumbs variant when no explicit
66
+ menu size is supplied.
67
+
68
+ **Does not own / non-goals**
69
+
70
+ - The navigation landmark, ordered list, trail label, separator value, or visual
71
+ variant — owned by `component:Breadcrumbs` through the public parent component.
72
+ - Destination acceptance and custom-router handoff — owned by
73
+ `family:navigation-destinations` and the shared link owner.
74
+ - Generic top-layer hosting, focus containment, positioning, and light dismissal —
75
+ owned by `component:Popover` and `architecture:layer-runtime`.
76
+ - Shared Escape/platform-close ordering — owned by
77
+ `family:overlay-dismissal`.
78
+ - Menu-item data, row semantics, selection, or submenu behavior — delegated to the
79
+ DropdownMenu item pipeline.
80
+ - Caller-provided icon artwork or arbitrary child content.
81
+
82
+ ## Public concepts
83
+
84
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
85
+ | ------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------- | ---------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------ |
86
+ | Item content | required `ReactNode` | Supplies the visible item label and may contain caller-owned content. | Every item branch | Required | `component:BreadcrumbItem` | Released | React renders the supplied node. |
87
+ | Destination | supplied or absent `href` | Selects the shared link path when the item is not explicitly current and has no menu. | Default and supporting variants | Absent | `family:navigation-destinations` | Released | Shared link handling decides accepted destinations. |
88
+ | Action | supplied or absent `onClick` | Selects a native link-styled button when no destination or menu is supplied, or augments the link path when `href` is supplied. | Non-current items | Absent | `component:BreadcrumbItem` | Released | `menu` currently suppresses this input and warns in development. |
89
+ | Current page | `true`, `false`, or omitted | `true` explicitly marks the item current; `false` opts it out; omission makes the item an auto-current candidate. | Every branch, including a menu trigger | Omitted | `component:BreadcrumbItem` | Released observation; intent review pending | When no item is explicitly current, the omitted final item receives `aria-current="page"`. |
90
+ | Link renderer | per-item `as`, provider renderer, or native anchor | Selects the component that receives an accepted destination. | Non-current link path | Provider renderer, then native anchor | `family:navigation-destinations` | Released | Explicitly current and menu paths do not use it. |
91
+ | Start content | supplied or absent `startIcon` | Renders caller content before the item label. | Every item branch | Absent | Caller | Released | Caller content remains caller-owned. |
92
+ | Sibling menu | data array, composed menu content, or absent | Replaces the link/action content branch with a menu button and menu surface. | Current or non-current item | Absent | `component:BreadcrumbItem`; menu rows delegate | Released observation; precedence review pending | Currently suppresses `href` or `onClick` and warns in development. |
93
+ | Menu size | `sm`, `md`, or `lg` | Selects the delegated menu-row size. | Menu branch | `sm` for supporting Breadcrumbs; otherwise `md` | `component:BreadcrumbItem` | Released | TypeScript rejects unsupported values. |
94
+
95
+ ## Behavioral and layout contract
96
+
97
+ Draft requirements identify their basis so observed code is not mistaken for an
98
+ intentional decision.
99
+
100
+ | ID | Candidate invariant | Basis | Draft review state |
101
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
102
+ | FR1 | The item root MUST remain one `<li>` carrying the public ref, item theme target, variant reflection, styling escape hatches, and neutral DOM pass-throughs. | Released types, source, docs, and tests; `architecture:public-component-api` | Verified current behavior; no API change proposed |
103
+ | FR2 | A non-current item with `href` and no menu MUST use the shared link renderer; an item with only `onClick` MUST use a native button; an item with neither MUST render text content. | Released source, docs, and tests | Verified current behavior |
104
+ | FR3 | Explicit `isCurrent=true` MUST expose `aria-current="page"`; explicit `false` MUST opt out; when every item omits the prop, the final item MUST receive the same current-page state. | Released source, docs, and tests | Verified current behavior; whether auto-detection remains the intended long-term default needs owner review |
105
+ | FR4 | Every item MUST render one decorative separator container; the first item hides it. The built-in slash mirrors exactly once in RTL, while caller-provided separators remain caller-owned. | Source, focused tests, and objective bidi behavior | Verified current behavior |
106
+ | FR5 | A menu item MUST expose one named menu button, current expanded state, control relationship, and one named `role="menu"` surface. Click, Enter, Space, and ArrowDown open it and focus its first item; Escape and selection close it. | APG Menu Button pattern, current Popover contract, source, tests, and browser evidence | Settled objective behavior |
107
+ | FR6 | Closing the menu MUST preserve a newly focused outside control. Focus returns to the trigger only when the closing surface would otherwise strand focus. | `component:Popover/AR4` and browser focus behavior | Settled objective behavior |
108
+ | FR7 | The menu branch MUST delegate item rendering, selection, typeahead, row focus, and submenu content to the shared DropdownMenu pipeline. | Released source and focused tests | Verified current behavior; DropdownMenu's draft contract remains context only |
109
+ | FR8 | Omitted `menuSize` MUST resolve from the parent variant before entering DropdownMenu context. | Released source and consumer docs | Verified current behavior |
110
+ | FR9 | The current local targets MUST remain `breadcrumb-item`, `breadcrumb-item-menu-trigger`, and `breadcrumb-menu`, each on its current visible style owner with `variant` reflected where applicable. | Released docs, source, and theming tests | Verified current inventory; no target change proposed |
111
+
112
+ ### Allowed variation
113
+
114
+ - **AV1 — Link renderer.** Native anchors and custom router components may differ in
115
+ DOM implementation while preserving the shared destination contract.
116
+ - **AV2 — Caller content.** Labels and start content may be any renderable React
117
+ content; the item owns surrounding semantics, not caller artwork or markup.
118
+ - **AV3 — Menu content.** Data-driven and composed content may produce actions,
119
+ sections, dividers, selectable items, or submenus through the delegated pipeline.
120
+ - **AV4 — Theme.** Current targets and semantic tokens may change paint while item
121
+ roles, focus ownership, and branch behavior remain stable.
122
+
123
+ ### Representative states
124
+
125
+ | State | Required invariant | Allowed variation |
126
+ | ---------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
127
+ | Destination link | Shared link path, visible keyboard focus, and accepted destination behavior remain available. | Native or custom router renderer; optional start content. |
128
+ | Action button | Native button semantics and supplied click handling remain available. | Default or supporting typography. |
129
+ | Explicit current | Current-page state is exposed on the rendered content owner. | Plain content or menu trigger. |
130
+ | Auto-current | The final omitted item receives current-page state only when no explicit current item exists. | Link or text content remains in its released branch. |
131
+ | Closed menu | Trigger exposes `aria-haspopup="menu"`, controls the menu, and reports collapsed state. | Data-driven or composed rows; current or non-current trigger. |
132
+ | Open menu | Named menu is top-layer reachable and focus enters its first eligible row. | Row kinds, submenu content, and explicit menu size. |
133
+ | Dismissed menu | One dismissal closes the surface; valid outside focus is preserved and stranded focus returns to the trigger. | Escape, selection, Tab, or native light dismiss. |
134
+
135
+ ### Transformation and precedence order
136
+
137
+ - **ORD1 — Content branch.** Explicit current is evaluated first; within either
138
+ current or non-current state, `menu` selects the menu branch. Otherwise `href`
139
+ selects the link branch, then `onClick` selects the action branch, then plain
140
+ content remains.
141
+ - **ORD2 — Link renderer.** Per-item `as` wins over LinkProvider, which wins over
142
+ the native anchor.
143
+ - **ORD3 — Menu size.** Explicit `menuSize` wins; otherwise supporting resolves to
144
+ `sm` and every other parent variant resolves to `md`.
145
+
146
+ ### Performance and resources
147
+
148
+ - **PR1 — Delegated layer resources.** BreadcrumbItem creates no independent global
149
+ listeners or observers; menu lifecycle and focus resources remain delegated to
150
+ Popover and shared menu hooks.
151
+ - **PR2 — Auto-current reconciliation.** Current source reconciles omitted current
152
+ state by inspecting the rendered sibling list after render. This is a known
153
+ runtime-authority gap under `architecture:react-component-runtime`, not an
154
+ approved implementation requirement.
155
+
156
+ ## Accessibility contract
157
+
158
+ - **AR1 — Trail semantics.** The parent Breadcrumbs landmark and ordered list own
159
+ aggregate breadcrumb semantics; each item remains one list item.
160
+ - **AR2 — Current state.** The content owner for a current item exposes
161
+ `aria-current="page"`; the list-item wrapper does not duplicate it.
162
+ - **AR3 — Native interaction.** Links use link semantics, action-only items use a
163
+ native button, and menu items use the APG Menu Button relationship.
164
+ - **AR4 — Menu name.** The menu surface is labelled by its trigger, including when
165
+ the visible item content is a non-string React node.
166
+ - **AR5 — Focus.** Keyboard focus is visible on links and menu/action buttons. Menu
167
+ entry, containment, Escape return, and outside-focus preservation follow FR5–FR6.
168
+ - **AR6 — Separator.** Separator content is hidden from assistive technology while
169
+ remaining directionally correct for sighted readers.
170
+
171
+ ## Design relationships
172
+
173
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
174
+ | ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | ----------------- | ------------------ |
175
+ | Item root | Carries current typography and the `breadcrumb-item` target. | Current source and public docs | Supporting | FR1, FR9 |
176
+ | Link or action content | Uses native semantics and the shared focus indicator without introducing another content target. | Current source; `architecture:interaction-modality` | Supporting | FR2, AR3, AR5 |
177
+ | Current content | Uses the released non-color text emphasis alongside current-page semantics. | Current source and objective non-color communication | Prominent | FR3, AR2 |
178
+ | Separator | Remains decorative to assistive technology and mirrors by contextual bidi role. | Objective bidi behavior | Supporting | FR4, AR6 |
179
+ | Menu trigger | Paints the menu-trigger target on the native focus owner. | Current source and public docs | Supporting | FR5, FR9, AR3–AR5 |
180
+ | Menu surface | Paints the `breadcrumb-menu` refinement inside Popover's shared layer surface. | `component:Popover` plus current BreadcrumbItem target inventory | Prominent | FR5–FR9 |
181
+ | Start content | Renders caller content without claiming its artwork or semantics. | Caller content; `component:Icon` when composed | Context-dependent | AV2 |
182
+
183
+ ## Family and system relationships
184
+
185
+ - `family:navigation-destinations` owns destination inspection and handoff for
186
+ native and custom link paths. BreadcrumbItem composes the shared link owner.
187
+ - `family:overlay-dismissal` owns topmost Escape/platform-close routing.
188
+ BreadcrumbItem participates through Popover.
189
+ - `component:Popover` owns generic menu-surface hosting, focus containment, and
190
+ stranded-focus return; BreadcrumbItem owns its trigger and refinement target.
191
+ - `architecture:component-theming-surface` owns target qualification, placement,
192
+ and state reflection.
193
+ - `architecture:public-component-api` and `spec:AST-002` own released API,
194
+ precedence, invalid-state prevention, and compatibility review.
195
+
196
+ ## Verification map
197
+
198
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
199
+ | ---------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | --------------------------------------------- |
200
+ | FR1–FR3, AR1–AR3 | `Breadcrumbs.test.tsx` branch, ref, pass-through, and current-state suites | Link, action, explicit current, omitted final, explicit false | A branch changes semantic element, loses current state, or moves the public root contract. | `audit:BreadcrumbItem/api-behavior` |
201
+ | FR4, AR6 | `Breadcrumbs.test.tsx`, `BreadcrumbItem.stories.tsx`, and RTL audit | Built-in slash, bidi-mirrored glyph, explicit icon mirror, LTR and RTL | A contextual separator fails to mirror exactly once or becomes exposed to AT. | `audit:BreadcrumbItem/rtl` |
202
+ | FR5–FR8, AR3–AR5 | `Breadcrumbs.test.tsx` plus real-browser story evidence | String and ReactNode labels, click/keyboard open, rows, selection, Escape, outside dismiss | A menu loses its name/state, focus enters the wrong owner, or dismissal steals valid outside focus. | `audit:BreadcrumbItem/accessibility-behavior` |
203
+ | FR9 | `themingTargets.test.ts`, source inspection, and rendered theme evidence | Item, menu trigger, menu surface; default/supporting | A target disappears, moves to non-painting plumbing, or loses variant reflection. | `audit:BreadcrumbItem/theming` |
204
+ | Draft structure | `scripts/check-knowledge.mjs` | Required schema and current relationship links | Missing sections, stale links, or accidental current authority fail validation. | `audit:BreadcrumbItem/knowledge` |
205
+
206
+ ## Decision log
207
+
208
+ None. This draft records observed released behavior and objective shared
209
+ requirements; it makes no component-local API, default, compatibility, ownership,
210
+ or visual-design decision.
211
+
212
+ ## Open questions
213
+
214
+ - **OQ1 — Conflicting interaction props.** Should the released `menu` + `href` or
215
+ `menu` + `onClick` combinations remain warning-based precedence, or should a
216
+ compatibility plan make them unrepresentable? (`human-api`)
217
+ - **OQ2 — Auto-current ownership.** Should omitted `isCurrent` remain an
218
+ auto-detected current candidate, and if so which React-owned mechanism should
219
+ replace post-render DOM reconciliation? (`human-api`)
220
+
221
+ ## Content boundary
222
+
223
+ This file does not duplicate the consumer prop table, menu-item API, audit scores,
224
+ run evidence, screenshots, implementation steps, or shared navigation, layer,
225
+ dismissal, and theming rules. It links to their owners.
@@ -96,8 +96,9 @@ export interface BreadcrumbItemProps extends Omit<
96
96
  onClick?: (e: MouseEvent<HTMLElement>) => void;
97
97
  /**
98
98
  * Marks this item as the current page. Renders as a span with aria-current="page".
99
- * If not set on any item, the last item is auto-detected as current.
100
- * @default false
99
+ * When omitted, the last item is auto-detected as current if no item is
100
+ * explicitly current. Pass `false` to opt this item out of auto-detection.
101
+ * @default undefined
101
102
  */
102
103
  isCurrent?: boolean;
103
104
  /**
@@ -395,8 +396,7 @@ export function BreadcrumbItem({
395
396
  menu={menu}
396
397
  menuSize={resolvedMenuSize}
397
398
  variant={ctx.variant}
398
- isCurrent
399
- label={children}>
399
+ isCurrent>
400
400
  {content}
401
401
  </BreadcrumbMenuTrigger>
402
402
  ) : (
@@ -439,8 +439,7 @@ export function BreadcrumbItem({
439
439
  ref={contentRef}
440
440
  menu={menu}
441
441
  menuSize={resolvedMenuSize}
442
- variant={ctx.variant}
443
- label={children}>
442
+ variant={ctx.variant}>
444
443
  {content}
445
444
  </BreadcrumbMenuTrigger>
446
445
  ) : href != null ? (
@@ -493,8 +492,6 @@ interface BreadcrumbMenuTriggerProps {
493
492
  ref: React.Ref<HTMLElement>;
494
493
  /** The link-styled label content rendered inside the trigger button. */
495
494
  children: ReactNode;
496
- /** Accessible name for the menu surface (the crumb's label). */
497
- label: ReactNode;
498
495
  menu: DropdownMenuOption[] | ReactNode;
499
496
  menuSize: DropdownMenuSize;
500
497
  variant: BreadcrumbsVariant;
@@ -511,20 +508,17 @@ interface BreadcrumbMenuTriggerProps {
511
508
  function BreadcrumbMenuTrigger({
512
509
  ref,
513
510
  children,
514
- label,
515
511
  menu,
516
512
  menuSize,
517
513
  variant,
518
514
  isCurrent = false,
519
515
  }: BreadcrumbMenuTriggerProps) {
520
516
  const menuId = useId();
517
+ const triggerId = useId();
521
518
  const buttonRef = useRef<HTMLButtonElement>(null);
522
519
  const isSupporting = variant === 'supporting';
523
520
 
524
521
  const popover = usePopover({
525
- onHide: useCallback(() => {
526
- buttonRef.current?.focus();
527
- }, []),
528
522
  hasLightDismiss: true,
529
523
  hasCloseButton: false,
530
524
  hasAutoFocus: false,
@@ -637,6 +631,7 @@ function BreadcrumbMenuTrigger({
637
631
  onClick={handleClick}
638
632
  onKeyDown={handleKeyDown}
639
633
  {...popover.triggerProps}
634
+ id={triggerId}
640
635
  aria-haspopup="menu"
641
636
  aria-controls={menuId}
642
637
  aria-current={isCurrent ? 'page' : undefined}
@@ -670,7 +665,7 @@ function BreadcrumbMenuTrigger({
670
665
  ref={listRef}
671
666
  id={menuId}
672
667
  role="menu"
673
- aria-label={typeof label === 'string' ? label : undefined}
668
+ aria-labelledby={triggerId}
674
669
  onKeyDown={listKeyDown}
675
670
  {...mergeProps(
676
671
  themeProps('breadcrumb-menu'),
@@ -117,8 +117,8 @@ export const docs = {
117
117
  {
118
118
  name: 'isCurrent',
119
119
  type: 'boolean',
120
- description: 'Marks this item as the current page, applying aria-current="page".',
121
- default: 'false',
120
+ description:
121
+ 'Marks this item as the current page, applying aria-current="page". When omitted, the last item is auto-detected if no item is explicitly current; pass false to opt out.',
122
122
  },
123
123
  {
124
124
  name: 'startIcon',
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  import {describe, it, expect, vi, beforeEach} from 'vitest';
4
- import {render, screen, waitFor, fireEvent} from '@testing-library/react';
4
+ import {render, screen, waitFor, fireEvent, act} from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import {Breadcrumbs} from './Breadcrumbs';
7
7
  import {BreadcrumbItem} from './BreadcrumbItem';
@@ -427,6 +427,24 @@ describe('BreadcrumbItem menu', () => {
427
427
  expect(trigger).toHaveAttribute('aria-expanded', 'false');
428
428
  });
429
429
 
430
+ it('labels the menu from non-string trigger content', async () => {
431
+ const user = userEvent.setup();
432
+ render(
433
+ <Breadcrumbs>
434
+ <BreadcrumbItem menu={items}>
435
+ <span>Teams</span>
436
+ </BreadcrumbItem>
437
+ <BreadcrumbItem isCurrent>Overview</BreadcrumbItem>
438
+ </Breadcrumbs>,
439
+ );
440
+
441
+ await user.click(screen.getByRole('button', {name: 'Teams'}));
442
+
443
+ expect(screen.getByRole('menu', {hidden: true})).toHaveAccessibleName(
444
+ 'Teams',
445
+ );
446
+ });
447
+
430
448
  it('portability: a DropdownMenuOption[] renders its items on open', async () => {
431
449
  const user = userEvent.setup();
432
450
  render(
@@ -437,7 +455,7 @@ describe('BreadcrumbItem menu', () => {
437
455
  );
438
456
  await user.click(screen.getByRole('button', {name: 'Teams'}));
439
457
  const menu = screen.getByRole('menu', {hidden: true});
440
- expect(menu).toHaveAttribute('aria-label', 'Teams');
458
+ expect(menu).toHaveAccessibleName('Teams');
441
459
  expect(
442
460
  screen.getByRole('menuitem', {name: 'Design', hidden: true}),
443
461
  ).toBeInTheDocument();
@@ -579,6 +597,35 @@ describe('BreadcrumbItem menu', () => {
579
597
  });
580
598
  });
581
599
 
600
+ it('preserves outside focus after a browser light dismiss', async () => {
601
+ const user = userEvent.setup();
602
+ render(
603
+ <>
604
+ <Breadcrumbs>
605
+ <BreadcrumbItem menu={items}>Teams</BreadcrumbItem>
606
+ <BreadcrumbItem isCurrent>Overview</BreadcrumbItem>
607
+ </Breadcrumbs>
608
+ <button type="button">Outside action</button>
609
+ </>,
610
+ );
611
+ const trigger = screen.getByRole('button', {name: 'Teams'});
612
+ await user.click(trigger);
613
+ const menu = screen.getByRole('menu', {hidden: true});
614
+ const outside = screen.getByRole('button', {name: 'Outside action'});
615
+
616
+ outside.focus();
617
+ expect(outside).toHaveFocus();
618
+ const popover = menu.closest('[popover]');
619
+ expect(popover).not.toBeNull();
620
+ await act(async () => {
621
+ (popover as HTMLElement).hidePopover();
622
+ });
623
+
624
+ await waitFor(() => {
625
+ expect(outside).toHaveFocus();
626
+ });
627
+ });
628
+
582
629
  it('roves focus with ArrowDown across items', async () => {
583
630
  render(
584
631
  <Breadcrumbs>
@@ -20,7 +20,8 @@ export const docs = {
20
20
  {
21
21
  name: 'axis',
22
22
  type: "'both' | 'horizontal' | 'vertical'",
23
- description: 'Which direction(s) to center.',
23
+ description:
24
+ 'Which Center mode to use. In horizontal writing, "horizontal" centers the flex main/inline axis and "vertical" centers the cross/block axis. In vertical writing, current single-axis behavior follows those logical flex axes rather than the physical names; "both" still centers both axes.',
24
25
  default: "'both'",
25
26
  },
26
27
  {
@@ -53,37 +54,37 @@ export const docs = {
53
54
  name: 'paddingInline',
54
55
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
55
56
  description:
56
- 'Inline (horizontal) padding, using the spacing scale. Overrides padding on the inline axis when both are set.',
57
+ 'Logical inline-axis padding, using the spacing scale. Overrides padding on the inline axis when both are set.',
57
58
  },
58
59
  {
59
60
  name: 'paddingInlineStart',
60
61
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
61
62
  description:
62
- 'Inline-start padding, using the spacing scale (left in LTR, right in RTL). Overrides paddingInline and padding on that edge only.',
63
+ 'Logical inline-start padding, using the spacing scale. Its resolved physical edge depends on writing mode and direction. Overrides paddingInline and padding on that edge only.',
63
64
  },
64
65
  {
65
66
  name: 'paddingInlineEnd',
66
67
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
67
68
  description:
68
- 'Inline-end padding, using the spacing scale (right in LTR, left in RTL). Overrides paddingInline and padding on that edge only.',
69
+ 'Logical inline-end padding, using the spacing scale. Its resolved physical edge depends on writing mode and direction. Overrides paddingInline and padding on that edge only.',
69
70
  },
70
71
  {
71
72
  name: 'paddingBlock',
72
73
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
73
74
  description:
74
- 'Block (vertical) padding, using the spacing scale. Overrides padding on the block axis when both are set.',
75
+ 'Logical block-axis padding, using the spacing scale. Overrides padding on the block axis when both are set.',
75
76
  },
76
77
  {
77
78
  name: 'paddingBlockStart',
78
79
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
79
80
  description:
80
- 'Block-start (top) padding, using the spacing scale. Overrides paddingBlock and padding on that edge only.',
81
+ 'Logical block-start padding, using the spacing scale. Its resolved physical edge depends on writing mode. Overrides paddingBlock and padding on that edge only.',
81
82
  },
82
83
  {
83
84
  name: 'paddingBlockEnd',
84
85
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
85
86
  description:
86
- 'Block-end (bottom) padding, using the spacing scale. Overrides paddingBlock and padding on that edge only.',
87
+ 'Logical block-end padding, using the spacing scale. Its resolved physical edge depends on writing mode. Overrides paddingBlock and padding on that edge only.',
87
88
  },
88
89
  {
89
90
  name: 'isInline',
@@ -112,9 +113,10 @@ export const docs = {
112
113
  description:
113
114
  'Center aligns content to the middle of its container. Use it for empty states, loading screens, login forms, or any content that should sit in the center of the available space.',
114
115
  bestPractices: [
115
- {guidance: true, description: 'Use axis="horizontal" or axis="vertical" when you only need one direction. Both axes is the default but not always needed.'},
116
- {guidance: true, description: 'Set a height when centering vertically. Center needs a defined height to know what space to center within.'},
116
+ {guidance: true, description: 'Use a single-axis value only in horizontal writing, or after verifying the active writing mode. In vertical writing, the current implementation follows flex main/cross axes rather than the physical prop names.'},
117
+ {guidance: true, description: 'In horizontal writing, give Center height when using axis="vertical"; centering needs available space on the selected flex axis.'},
117
118
  {guidance: true, description: 'Use isInline to center small elements like icons or badges within a line of text without breaking the text flow.'},
119
+ {guidance: true, description: 'Keep semantic structure and accessible names on the content. Center is a layout-only container and does not add a role or label.'},
118
120
  {guidance: false, description: 'Wrap large page sections in Center. Use Layout or AppShell for page-level structure.'},
119
121
  {guidance: false, description: 'Use Center for horizontal lists of items. Use Stack with hAlign="center" instead.'},
120
122
  ],
@@ -133,15 +135,16 @@ export const docsZh = {
133
135
  description:
134
136
  'Center aligns content to the middle of its container. Use it for empty states, loading screens, login forms, or any content that should sit in the center of the available space.',
135
137
  bestPractices: [
136
- {guidance: true, description: 'Use axis="horizontal" or axis="vertical" when you only need one direction. Both axes is the default but not always needed.'},
137
- {guidance: true, description: 'Set a height when centering vertically. Center needs a defined height to know what space to center within.'},
138
+ {guidance: true, description: 'Use a single-axis value only in horizontal writing, or after verifying the active writing mode. In vertical writing, the current implementation follows flex main/cross axes rather than the physical prop names.'},
139
+ {guidance: true, description: 'In horizontal writing, give Center height when using axis="vertical"; centering needs available space on the selected flex axis.'},
138
140
  {guidance: true, description: 'Use isInline to center small elements like icons or badges within a line of text without breaking the text flow.'},
141
+ {guidance: true, description: 'Keep semantic structure and accessible names on the content. Center is a layout-only container and does not add a role or label.'},
139
142
  {guidance: false, description: 'Wrap large page sections in Center. Use Layout or AppShell for page-level structure.'},
140
143
  {guidance: false, description: 'Use Center for horizontal lists of items. Use Stack with hAlign="center" instead.'},
141
144
  ],
142
145
  },
143
146
  props: [
144
- {name: 'axis', type: "'both' | 'horizontal' | 'vertical'", description: '居中的方向。', default: "'both'"},
147
+ {name: 'axis', type: "'both' | 'horizontal' | 'vertical'", description: '选择 Center 模式。横向书写时,horizontal 对应 flex 主轴/行内轴,vertical 对应交叉轴/块轴;纵向书写时,当前单轴行为仍跟随这些逻辑 flex 轴,而不是属性名暗示的物理轴。', default: "'both'"},
145
148
  {name: 'width', type: 'number | string', description: '容器宽度(px 或 CSS 值)。'},
146
149
  {name: 'height', type: 'number | string', description: '容器高度(px 或 CSS 值)。'},
147
150
  {
@@ -153,32 +156,32 @@ export const docsZh = {
153
156
  {
154
157
  name: 'paddingInline',
155
158
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
156
- description: '行内(水平)内边距,使用间距刻度。两者同时设置时在行内轴上覆盖 padding。',
159
+ description: '逻辑行内轴内边距,使用间距刻度。两者同时设置时在行内轴上覆盖 padding。',
157
160
  },
158
161
  {
159
162
  name: 'paddingInlineStart',
160
163
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
161
- description: '行内起始内边距,使用间距刻度(LTR 中为左侧,RTL 中为右侧)。仅在该边上覆盖 paddingInline 和 padding。',
164
+ description: '逻辑行内起始内边距,使用间距刻度。解析后的物理边取决于书写模式和方向。仅在该边上覆盖 paddingInline 和 padding。',
162
165
  },
163
166
  {
164
167
  name: 'paddingInlineEnd',
165
168
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
166
- description: '行内结束内边距,使用间距刻度(LTR 中为右侧,RTL 中为左侧)。仅在该边上覆盖 paddingInline 和 padding。',
169
+ description: '逻辑行内结束内边距,使用间距刻度。解析后的物理边取决于书写模式和方向。仅在该边上覆盖 paddingInline 和 padding。',
167
170
  },
168
171
  {
169
172
  name: 'paddingBlock',
170
173
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
171
- description: '块(垂直)内边距,使用间距刻度。两者同时设置时在块轴上覆盖 padding。',
174
+ description: '逻辑块轴内边距,使用间距刻度。两者同时设置时在块轴上覆盖 padding。',
172
175
  },
173
176
  {
174
177
  name: 'paddingBlockStart',
175
178
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
176
- description: '块起始(顶部)内边距,使用间距刻度。仅在该边上覆盖 paddingBlock 和 padding。',
179
+ description: '逻辑块起始内边距,使用间距刻度。解析后的物理边取决于书写模式。仅在该边上覆盖 paddingBlock 和 padding。',
177
180
  },
178
181
  {
179
182
  name: 'paddingBlockEnd',
180
183
  type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
181
- description: '块结束(底部)内边距,使用间距刻度。仅在该边上覆盖 paddingBlock 和 padding。',
184
+ description: '逻辑块结束内边距,使用间距刻度。解析后的物理边取决于书写模式。仅在该边上覆盖 paddingBlock 和 padding。',
182
185
  },
183
186
  {name: 'isInline', type: 'boolean', description: '使用 inline-flex(适用于文本/图标)。', default: 'false'},
184
187
  {name: 'children', type: 'ReactNode', description: '要居中的内容。'},
@@ -203,30 +206,31 @@ export const docsZh = {
203
206
 
204
207
  /** @type {import('@astryxdesign/cli/authoring').ComponentTranslationDoc} */
205
208
  export const docsDense = {
206
- description: 'centers content horizontally and/or vertically via flexbox',
209
+ description: 'centers content on one or both flex axes; single-axis names match physical axes only in horizontal writing',
207
210
  usage: {
208
211
  description:
209
212
  'Center aligns content to the middle of its container. Use for empty states, loading screens, login forms.',
210
213
  bestPractices: [
211
- {guidance: true, description: 'Use axis="horizontal" or axis="vertical" when you only need one direction. Both axes is the default but not always needed.'},
212
- {guidance: true, description: 'Set a height when centering vertically. Center needs a defined height to know what space to center within.'},
214
+ {guidance: true, description: 'Use a single-axis value only in horizontal writing, or after verifying the active writing mode. In vertical writing, the current implementation follows flex main/cross axes rather than the physical prop names.'},
215
+ {guidance: true, description: 'In horizontal writing, give Center height when using axis="vertical"; centering needs available space on the selected flex axis.'},
213
216
  {guidance: true, description: 'Use isInline to center small elements (icons, badges) within a line of text without breaking text flow.'},
217
+ {guidance: true, description: 'Keep semantic structure and accessible names on the content; Center adds no role or label.'},
214
218
  {guidance: false, description: 'Wrap large page sections in Center. Use Layout or AppShell for page-level structure.'},
215
219
  {guidance: false, description: 'Use Center for horizontal lists of items. Use Stack with hAlign="center" instead.'},
216
220
  ],
217
221
  },
218
222
  propDescriptions: {
219
- axis: 'centering direction(s)',
223
+ axis: 'centering mode; outside horizontal writing, single-axis values follow flex main/cross axes',
220
224
  width: 'container width (px or CSS)',
221
225
  height: 'container height (px or CSS)',
222
226
  padding:
223
227
  'inner padding on all sides (spacing step: 0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)',
224
- paddingInline: 'inline (horizontal) padding; overrides padding on that axis',
225
- paddingInlineStart: 'inline-start padding (left in LTR); overrides paddingInline/padding on that edge',
226
- paddingInlineEnd: 'inline-end padding (right in LTR); overrides paddingInline/padding on that edge',
227
- paddingBlock: 'block (vertical) padding; overrides padding on that axis',
228
- paddingBlockStart: 'block-start (top) padding; overrides paddingBlock/padding on that edge',
229
- paddingBlockEnd: 'block-end (bottom) padding; overrides paddingBlock/padding on that edge',
228
+ paddingInline: 'logical inline-axis padding; overrides padding on that axis',
229
+ paddingInlineStart: 'logical inline-start padding; physical edge depends on writing mode and direction',
230
+ paddingInlineEnd: 'logical inline-end padding; physical edge depends on writing mode and direction',
231
+ paddingBlock: 'logical block-axis padding; overrides padding on that axis',
232
+ paddingBlockStart: 'logical block-start padding; physical edge depends on writing mode',
233
+ paddingBlockEnd: 'logical block-end padding; physical edge depends on writing mode',
230
234
  isInline: 'use inline-flex for text/icons',
231
235
  children: 'content to center',
232
236
  xstyle: 'StyleX styles for layout (margins, positioning, sizing); must be stylex.create() value',