Repository navigation
test(a11y): Menu Button contract and DropdownMenu/MoreMenu trigger binding - #7469
Conversation
…nding MENU_BUTTON_PATTERN encodes the APG Menu Button parts component:DropdownMenu AR4 and component:MoreMenu AR1 adopt for the anchored trigger: button role and name, aria-haspopup menu, aria-expanded, aria-controls to the role=menu element, Enter and Space opening with focus on the first enabled item, the pointer round trip and aborted press, and — as the records' named exception — ArrowDown opening on the first and ArrowUp on the last enabled item. Fixtures give every expectation a violating twin; jsdom and Chromium self-tests, the binding (default Button trigger closed/open/disabled, a renderTrigger control, MoreMenu), stories, a CI step, and the README row. Test-only: no runtime or docs change.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewModified ComponentsAlertDialog (@astryxdesign/core) · View in Storybook
Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Visual Regression38 of 474 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 18 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: the Menu Button adoption these tests gate — the APG Menu Button parts plus the ArrowDown/ArrowUp exception on the DropdownMenu and MoreMenu triggers — is not yet in a current record on main (it lives in the open #7458 and #7459, and #7459 keeps both component records in draft), so a maintainer needs to identify or land the governing record. Separately from that decision, these need fixing:
- The
pr-a11yjob fails here becausemenu-button.control.survives-an-aborted-pressfails fordropdown-button-closedandmore-menu-closed(the menu stays open after a press released away from the trigger), and no known-failure record covers it. - The ArrowDown/ArrowUp expectations are
requiredthrough a WCAG 2.1.1 citation although the keys are APG-optional and not yet adopted, so under AST-020 FR9 they should stay advisory until a current record adopts them. - The only disabled state uses an unfocusable trigger, while a disabled MoreMenu stays focusable and opens on ArrowDown, so that state needs its own binding state and an expectation that an unavailable trigger stays closed (AST-021 FR4).
[Automated review]
| const cdp = await page.context().newCDPSession(page); | ||
| const result = await runState(page, cdp, state); | ||
| const report = summarize(MENU_BUTTON_PATTERN, [result]); | ||
| expect(formatFailures(blockingResults([result]))).toBe(''); |
There was a problem hiding this comment.
This assertion fails in this head's CI run for dropdown-button-closed and more-menu-closed: menu-button.control.survives-an-aborted-press reports “releasing the press away from the control left aria-expanded true instead of false”, because the trigger opens on mouse press-down and an unsettled release keeps the menu open. Under AST-021 FR8/FR10 this needs exact known-failure records for both states (noting that useMenuPress FR8/DEC-4 is still proposed), or a separate fix first, before the step can gate; the any-non-pass assertion just below would also fail on a known-failure row, so gate on blockingResults alone, as the ToggleButton binding does.
| id: 'menu-button.open.arrow-down-focuses-first-item', | ||
| outcome: | ||
| 'With focus on the control, ArrowDown opens the menu and places focus on its first enabled item.', | ||
| sources: [WCAG_2_1_1, APG_DOWN], |
There was a problem hiding this comment.
WCAG 2.1.1 does not require ArrowDown (Enter and Space already make the menu keyboard-operable), so leading with it makes this APG-optional key required without the current adoption AST-020 FR9 asks for. Until a current record adopts the arrow-key exception, make this and arrow-up-focuses-last-item (L378) APG-primary with a wcagOutcome, enforcement: 'advisory' and an advisoryBecause, as spinbutton.ts does, and keep advisory rows report-only in the Chromium binding.
| label: 'Actions', | ||
| firstItem: 'Rename', | ||
| lastItem: 'Delete', | ||
| facts: facts({operable: false, focusable: false}), |
There was a problem hiding this comment.
This covers only the natively disabled, unfocusable trigger. A disabled MoreMenu (it always passes tooltip: label) or a disabled DropdownMenu trigger with a tooltip renders aria-disabled and stays focusable, and ArrowDown then opens its menu (aria-expanded becomes true). AST-021 FR4 asks the binding to cover the focusable-disabled state, together with an expectation that an unavailable trigger stays closed.
| */ | ||
|
|
||
| import type {Meta, StoryObj} from '@storybook/react'; | ||
| import {DropdownMenu} from '@astryxdesign/core/DropdownMenu'; |
There was a problem hiding this comment.
MenuButton.a11y.renders.tsx says the story renders through it, but these stories re-declare each state and the item list. Building them from MENU_BUTTON_BINDING_STATES and renderMenuButtonState, as the sibling a11y stories do, keeps the jsdom and Chromium lanes on one tree.
…tional arrows advisory, and gates an unavailable trigger closed - opensOnPress fact: a press-model trigger (DropdownMenu opens on pointer press-down) is not judged by the aborted-press check; its module owns what a release elsewhere does. Recorded as a binding exclusion. - ArrowDown/ArrowUp opens stay advisory until the component records adopt them as current (AST-020 FR9). - menu-button.control.unavailable-stays-closed: a focusable aria-disabled trigger keeps its menu closed on Enter, Space, ArrowDown, ArrowUp and a press; conforming and violating fixtures. The disabled MoreMenu state records the shipped gap as an exact known failure (ArrowDown opens it) for its own spec-backed fix.
…on the press model excludes
There was a problem hiding this comment.
This needs a maintainer decision before it can be approved: the required aria-haspopup and first-item focus checks on the built-in DropdownMenu and MoreMenu triggers, and the opensOnPress fact that switches off the WCAG 2.5.2 aborted-press check for every state, rest on records that are not yet current on main (the Menu Button adoption in #7458 and #7459, and the press-to-open rule that useMenuPress still lists as proposed). A maintainer needs to either land those records or direct this PR to keep those outcomes advisory and record the aborted-press failures as exact known failures. Separately, these still need fixing:
- The Chromium binding's any-non-pass assertion makes the advisory ArrowDown/ArrowUp rows fail CI, so gate on
blockingResultsalone, as the ToggleButton and Link bindings do. - The arrow-key rows still cite WCAG 2.1.1 as their primary source; make them APG-primary with a
wcagOutcome, asspinbutton.tsdoes. - The stories re-declare each state instead of rendering
renderMenuButtonState, so the jsdom and Chromium lanes can drift apart. - The existing
aria-haspopup/aria-expandedassertions inDropdownMenu.test.tsxandMoreMenu.test.tsxnow duplicate the binding, so name their removal blocker in the description or narrow them (AST-021 FR6).
[Automated review]
| expect( | ||
| result.results.filter( | ||
| row => | ||
| row.status !== 'pass' && | ||
| row.status !== 'not-applicable' && | ||
| row.status !== 'known-failure', | ||
| ), | ||
| ).toEqual([]); |
There was a problem hiding this comment.
This filter fails the test on any fail row, an advisory one included: an advisory failure is reported as fail, and only blockingResults leaves it out. A regression in the ArrowDown/ArrowUp rows would therefore turn the pr-a11y step red although they stay advisory until a current record adopts them (AST-020 FR9: “Advisory expectations report only”). Keeping just the blockingResults and unrunLayers assertions, as ToggleButton.a11y.chromium.spec.ts and Link.a11y.chromium.spec.ts do, keeps advisory rows report-only.
| id: 'menu-button.open.arrow-down-focuses-first-item', | ||
| outcome: | ||
| 'With focus on the control, ArrowDown opens the menu and places focus on its first enabled item.', | ||
| sources: [WCAG_2_1_1, APG_DOWN], |
There was a problem hiding this comment.
This row is advisory now, but its primary source is still WCAG 2.1.1, so its test name and failures read “[WCAG 2.2 2.1.1 Keyboard (A)]” for a key the APG marks optional and 2.1.1 does not require (and definePattern reads a WCAG-primary source as directly applicable). AST-020 FR1 asks an APG-sourced expectation to “identify the WCAG 2.2 user outcome it supports without inventing a success-criterion mapping”: making this row and arrow-up-focuses-last-item (L380) APG-primary with a wcagOutcome, as spinbutton.ts does for its arrow keys, fixes both.
| focusable: true, | ||
| controls: true, | ||
| arrowKeysOpen: true, | ||
| opensOnPress: true, |
There was a problem hiding this comment.
Pending a maintainer decision: this default keeps the required WCAG 2.5.2 row from running in every state. With it set to false, menu-button.control.survives-an-aborted-press fails on dropdown-button-closed, dropdown-render-trigger-closed and more-menu-closed (“releasing the press away from the control left aria-expanded true instead of false”), while the press-to-open rule the exclusion relies on is still proposed in useMenuPress (FR8, DEC-4, OQ1) and MenuPress.a11y.chromium.spec.ts has no aborted-press case. If the maintainer directs conformance before that rule is current (AST-020 FR2), these become exact known failures per state (AST-021 FR8, FR10).
| id: 'menu-button.control.haspopup-menu', | ||
| outcome: | ||
| 'The control says it opens a menu, so a user knows a press reveals choices rather than acting at once.', | ||
| sources: [WCAG_4_1_2, APG_HASPOPUP], |
There was a problem hiding this comment.
Pending a maintainer decision: aria-haspopup is an APG Menu Button mechanic, and the only current Astryx adoption of it is spec:AST-055 AR1, for a caller-rendered trigger; for the built-in Button and MoreMenu triggers the adopting records are still open in #7459. Listing WCAG 4.1.2 first is what makes this row required under the contract's FR9 check; the first-enabled-item landing in the Enter and Space rows (L320, L338) is in the same position.
…rrow rows APG-primary with a wcagOutcome; stories render through renderMenuButtonState
There was a problem hiding this comment.
Approval needs a maintainer to provide the governing spec for one addition: the opensOnPress exclusion keeps the required WCAG 2.5.2 aborted-press check from running on any DropdownMenu or MoreMenu state, but the press-to-open model it relies on is still proposed in module:DropdownMenu/useMenuPress (FR8, DEC-4, OQ1), so that record has to become current on main, including how a press-opened trigger meets 2.5.2. Two fixes are needed regardless:
- The binding builds story ids as
a11y-menu-button--…, but the stories'A11y/MenuButtontitle producesa11y-menubutton--…, so every Chromium binding test times out and the Menu Button step is red. - The new CI step scopes with a two-dot
git difffrom the base tip, whichmainreplaced with.github/scripts/pr-scope-touches.shin #7470; after rebasing, reuse that helper so branches cut before this lands do not run spec files they lack.
[Automated review]
| }; | ||
| } | ||
|
|
||
| const story = (id: string) => `a11y-menu-button--${id}`; |
There was a problem hiding this comment.
This prefix no longer matches the stories: MenuButtonA11y.stories.tsx now uses the title A11y/MenuButton, which Storybook indexes as a11y-menubutton--… (this head's CI Storybook build has a11y-menubutton--dropdown-button-closed and no a11y-menu-button--… entry). Every Chromium binding test therefore times out waiting for the trigger, and the Menu Button step is red. With a11y-menubutton--${id} (or the previous A11y/Menu Button title), every applicable row passes in all six states and the known failure matches exactly.
| focusable: true, | ||
| controls: true, | ||
| arrowKeysOpen: true, | ||
| opensOnPress: true, |
There was a problem hiding this comment.
Pending a maintainer decision: this default switches the required WCAG 2.5.2 row off in every state, and with it set to false the row fails on dropdown-button-closed, dropdown-render-trigger-closed and more-menu-closed (“releasing the press away from the control left aria-expanded true instead of false”). The press-to-open model it rests on is still proposed in module:DropdownMenu/useMenuPress (FR8, DEC-4, OQ1), and MenuPress.a11y.chromium.spec.ts has no aborted trigger-press case, so the exclusion needs that record to become current (AST-020 FR2).
| if git fetch --quiet --depth=1 origin "$BASE_SHA"; then | ||
| if git diff --name-only "$BASE_SHA" HEAD \ | ||
| | grep -qE '^(internal/a11y-spec/src/patterns/menu-button|packages/core/src/DropdownMenu/|packages/core/src/MoreMenu/|apps/storybook/stories/MenuButtonA11y)'; then | ||
| should_run=true | ||
| fi | ||
| else | ||
| should_run=true | ||
| fi |
There was a problem hiding this comment.
main replaced this two-dot scope check in every PR evidence step with .github/scripts/pr-scope-touches.sh, loaded from BASE_SHA (#7470): on a branch cut before the base gained a file, the base-tip diff lists that file as changed. Once this lands, every older branch would run this step against spec files its head lacks and fail pr-a11y with “No tests found” until rebased. After rebasing, reuse the Link step's helper block with this pattern.
… step decides scope through pr-scope-touches.sh
There was a problem hiding this comment.
Approval needs a maintainer to identify the governing spec for one addition: the opensOnPress exclusion keeps the required WCAG 2.5.2 aborted-press check from running on every DropdownMenu and MoreMenu state, and with it lifted the check fails on three of them. The press-to-open trigger it relies on is still proposed in module:DropdownMenu/useMenuPress (FR8, DEC-4, OQ1) and in component:DropdownMenu FR8, and both component records are still draft on main after #7459, so a current record of that model, including how a press-opened trigger meets 2.5.2, has to be in place first. The story ids and the CI scope check are fixed, and the Menu Button step now passes. Separately, please refresh the description, which still reports five binding states and says the binding stays advisory until #7459 merges.
[Automated review]
…ract # Conflicts: # .github/workflows/ci.yml # internal/a11y-spec/README.md
…d record its exact failures The press-to-open model the opensOnPress exclusion relied on is still proposed (useMenuPress FR8/DEC-4, OQ1), so the required WCAG 2.5.2 row now applies to every operable closed trigger and the three press-opened states carry exact known-failure records. The disabled-MoreMenu known failure is gone: main now keeps a disabled trigger closed. The adopted required parts cite spec:AST-020 FR15.
There was a problem hiding this comment.
Changes requested for two small corrections; the Menu Button contract and its DropdownMenu/MoreMenu binding otherwise match the current accessibility specs, and the new CI step passes on this head.
- Record the header/footer popover, whose trigger says
aria-haspopup="dialog", as an ownership exclusion beside the bottom sheet, and point the bottom-sheet exclusion at the clause that records it instead of DropdownMenu FR13, which covers the flyout safe triangle. - Refresh the description's source-line references, list the ArrowDown/ArrowUp rows' source as APG with the WCAG outcome they support, and mark those rows advisory in the README table.
[Automated review]
| }, | ||
| { | ||
| id: 'bottom-sheet-presentation', | ||
| owner: 'component:DropdownMenu FR13 and the modal-dialog pattern', |
There was a problem hiding this comment.
component:DropdownMenu FR13 is the flyout safe-triangle requirement, not the bottom sheet; AR4 is the clause that places the bottom sheet outside the Menu Button pattern. The same AR4 sentence also places a pointer menu with header or footer outside it, and that trigger says aria-haspopup="dialog" (DropdownMenu.tsx L1576–1578), so it needs its own exclusion row here and in the inventory test: AST-021 FR4 asks the inventory to record why omitted states cannot change the shared outcome.
| | `listbox` | [WAI-ARIA 1.2 Listbox](https://www.w3.org/TR/wai-aria-1.2/#listbox) and WCAG 2.2 semantics | Selector and MultiSelector popup listbox, group, and option parts | | ||
| | `spinbutton` | WCAG 2.2 semantics and [APG Spinbutton](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) | NumberInput | | ||
| | `disclosure` | [APG Disclosure](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/) | Standalone Collapsible triggers and their controlled content | | ||
| | `menu-button` | [APG Menu Button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) required parts plus the optional ArrowDown/ArrowUp opens Astryx ships | Anchored DropdownMenu and MoreMenu triggers: button role and name, `aria-haspopup="menu"`, `aria-expanded`, `aria-controls`, Enter/Space/ArrowDown open on the first enabled item, ArrowUp on the last | |
There was a problem hiding this comment.
ArrowDown/ArrowUp opening is advisory in the contract until component:DropdownMenu AR4 and component:MoreMenu AR1 are current; the accordion row below names its advisory rows, so this row should mark the arrow opens advisory too.
…ow opens advisory The bottom-sheet exclusion points at DropdownMenu AR4, which places it outside the pattern; a pointer menu with a header or footer (AR4/AR5, its trigger says aria-haspopup="dialog") gets its own exclusion row. The README row marks ArrowDown/ArrowUp opening advisory, as the contract does.
There was a problem hiding this comment.
Changes requested for one small ledger fix: the Menu Button contract, its DropdownMenu/MoreMenu binding and the three exact WCAG 2.5.2 known failures otherwise match the current accessibility specs, and the new Chromium step passes on this head. Two local tests now duplicate required rows the binding proves, without the named removal blocker AST-021 FR6 asks for: DropdownMenu's “keyboard open via Enter focuses the first enabled item” and MoreMenu's “keeps the menu closed from every opening key and press while disabled”. Please add both to the description's duplicate-assertions note with their blocker (DropdownMenu AR4's verification row already cites the first), or remove or narrow them.
[Automated review]
There was a problem hiding this comment.
No further changes are needed: the Menu Button contract, its DropdownMenu and MoreMenu trigger binding, and the three exact pointer-cancellation known failures match the current accessibility specs, and the new Chromium step passes on this head. The description's duplicate-assertions note now names a removal blocker for every local test the binding duplicates, which was the last open item. The press-release failures stay recorded as visible debt until the press-to-open model is decided, so nothing else is needed in this pull request.
[Automated review]
What this proves
component:DropdownMenuAR4 andcomponent:MoreMenuAR1 (the Menu Button adoption recorded in #7459 underspec:AST-020FR15) now have an executable contract and a binding. A keyboard or screen-reader user at an anchored menu trigger gets: a named button that says it opens a menu, reports whether the menu is displayed, and names the menu; Enter, Space, and ArrowDown opening it with focus on the first enabled item and ArrowUp on the last; a pointer press that opens and closes; an aborted press that changes nothing.Test-only: no runtime, docs, or Changeset. Enforcement follows
spec:AST-020FR9: the APG Menu Button's required parts arerequired; the APG-recommended arrow opens areadvisory. #7459 is merged;component:DropdownMenuandcomponent:MoreMenucarryauthority: draftonmain, so the records' currency is tracked by the maintainers, not by this PR.Expectations (AST-020 FR13)
menu-button.control.role-and-namemenu-button.control.haspopup-menumenu-button.control.expanded-statemenu-button.control.controls-menumenu-button.menu.matches-statemenu-button.open.enter-focuses-first-itemmenu-button.open.space-focuses-first-itemmenu-button.open.arrow-down-focuses-first-itemmenu-button.open.arrow-up-focuses-last-itemmenu-button.open.pointer-round-tripmenu-button.control.unavailable-stays-closedmenu-button.control.survives-an-aborted-pressExemptions:
1.1.1-non-text-content→ the binding component;1.3.1-info-and-relationships→ the binding component and composing page (remainder only);1.3.2-meaningful-sequence→ the binding component and composing page;1.3.5-identify-input-purpose→ the composing form and caller content;1.4.1-use-of-color→ the binding component and theme;1.4.3-contrast-minimum→ the binding component and theme;1.4.11-non-text-contrast→ the binding component and theme;2.1.1-keyboard→ the button pattern and composing page (remainder only);2.1.2-no-keyboard-trap→ the button pattern and composing page;2.4.2-page-titled→ the page;2.4.3-focus-order→ the menu pattern and the composing page (remainder only);2.4.4-link-purpose→ the link pattern and caller content;2.4.6-headings-and-labels→ the button pattern and caller content;2.4.7-focus-visible→ the interaction-modality architecture record, binding component, and theme;2.4.11-focus-not-obscured→ the composing page and overlay system;2.5.3-label-in-name→ the button pattern and caller content;2.5.8-target-size→ the binding component and composing page;3.1.1-language-of-page→ the page;3.2.2-on-input→ the menu pattern and the composing page;3.2.4-consistent-identification→ the design system and caller content;3.3.1-error-identification→ the binding component and composing form;3.3.2-labels-or-instructions→ the composing form and caller content;4.1.2-name-role-value→ the button pattern and binding component (remainder only);4.1.3-status-messages→ the caller and the assistive-technology verification record, spec:AST-009;apg-interaction→ the button pattern (remainder only);forced-colors→ the binding component and theme;reduced-motion→ the binding component and theme;at-facing-strings→ the binding component and caller contentCurrent state (head after the merge of
main)opensOnPressfact and thepress-model-pointer-cancellationexclusion are gone: the press-to-open model they relied on is still proposed inmodule:DropdownMenu/useMenuPress(FR8, DEC-4, OQ1). The required row now fails ondropdown-button-closed,dropdown-render-trigger-closedandmore-menu-closed("releasing the press away from the control left aria-expanded true instead of false"), and each failure is an exact known-failure record (AST-021 FR8/FR10) inMenuButton.a11y.known-failures.ts. The owner's answer to that record's OQ1 either makes the press model conformant or brings the fix; this PR decides neither.mainnow keeps a disabled trigger closed (DropdownMenu/MoreMenu disabled-trigger fix), so themore-menu-disabledknown failure is removed andmenu-button.control.unavailable-stays-closedmust pass in that state.aria-haspopup,aria-expanded,aria-controls, Enter and Space opening on the first enabled item) citespec:AST-020FR15, the current rule that a control already playing the menu button role adopts the pattern's required parts. The optional ArrowDown/ArrowUp opens stayadvisory(AST-020 FR9) and are recorded as a named exception bycomponent:DropdownMenuAR4.main(conflicts inci.ymland the a11y-spec README only: the Menu Button step and README row now sit beside the patternsmainadded). The evidence upload keeps the same retention as its sibling steps.blockingResultsalone (required rows block; advisory ArrowDown/ArrowUp rows and the exact known failure report), as the ToggleButton and Link bindings do.MenuButtonA11y.stories.tsxrenders every state throughrenderMenuButtonState, so the jsdom and Chromium lanes cannot drift.DropdownMenu.test.tsx"has aria-haspopup and aria-expanded attributes" andMoreMenu.test.tsx"has aria-haspopup and aria-expanded attributes" overlapmenu-button.control.haspopup-menu/expanded-state;DropdownMenu.test.tsx"keyboard open via Enter focuses the first enabled item" overlapsmenu-button.open.enter-focuses-first-item. Blocker:component:DropdownMenuAR4 andcomponent:MoreMenuAR1 cite those exact titles as their verification evidence while the binding is "pending" there. Once this binding is onmain, one follow-up narrows those verification rows to the contract and deletes the three assertions together.MoreMenu.test.tsx"keeps the menu closed from every opening key and press while disabled" overlapsmenu-button.control.unavailable-stays-closedinmore-menu-disabled. Blocker: it is the regression test of the disabled-trigger fix, and the path it guards runs through Button'saria-disabledkey handling, which is outside this contract step's CI scope (DropdownMenu, MoreMenu, the stories, the contract); the jsdom test runs on every PR. It goes when the contract step's scope covers that path or the release-time contract run is the accepted guard.Shape
internal/a11y-spec/src/patterns/menu-button.ts—MENU_BUTTON_PATTERN: 12 expectations with WCAG, APG, andspec:AST-020FR15 sources (APG Menu Button wording verified against the published pattern on 2026-10-13: role button;aria-haspopupmenu or true;aria-expandedtrue/false; optionalaria-controlsto themenu; Enter/Space to the first item; optional Down/Up Arrow to the first/last item).arrowKeysOpenis a binding fact, so a binding that does not ship the optional keys is not held to them. Every other checklist dimension is exempted to a named owner — the menu surface (items, arrows inside, Escape, focus return) to the menu pattern.menu-button.fixtures.ts— a plain HTML menu button in conforming and violating forms; every expectation has at least one violating twin.menu-button.jsdom.test.ts(32 cases) andmenu-button.chromium.spec.ts.packages/core/src/DropdownMenu/__tests__/MenuButton.a11y.{states,renders,test}.tsxandMenuButton.a11y.chromium.spec.ts— six states: default Button trigger closed, open (isMenuOpen), arenderTriggercontrol spreading the props onto its own<button>, a disabled (unfocusable) Button trigger, MoreMenu, and a disabled (focusable) MoreMenu. All onpresentation="popover": the bottom-sheet presentation is a dialog (aria-haspopup="dialog", DropdownMenu.tsx L823/L851) and outside the pattern bycomponent:DropdownMenuAR4.apps/storybook/stories/MenuButtonA11y.stories.tsx— oneno-visualstory per state..github/workflows/ci.yml— "Run Menu Button accessibility contract" step; runs when DropdownMenu, MoreMenu, the stories, or the contract change.Source lines behind the binding facts
At the reviewed head:
aria-haspopup,aria-expanded,aria-controlson the anchored trigger (DropdownMenu.tsxL1561–1578,triggerProps; arenderTriggercontrol receives the same object, L1584);aria-haspopup="dialog"with a header or footer (L1576–1578, L1626); ArrowUp opens on the last row (focusLastOnOpenRef, L1039 and L1463–1471); bottom-sheet trigger (L823–825, L851); MoreMenu default label andaria-haspopup="menu"(MoreMenu.test.tsxL74–89).Evidence
Ownership exclusions
MENU_BUTTON_EXCLUSIONS: menu-surface semantics (menu pattern, queued); bottom-sheet presentation (component:DropdownMenuAR4 / modal dialog); header/footer popover, whose trigger saysaria-haspopup="dialog"(component:DropdownMenuAR4 and AR5 / modal dialog).