Skip to content

test(a11y): Menu Button contract and DropdownMenu/MoreMenu trigger binding - #7469

Merged
cixzhang merged 9 commits into
mainfrom
a11y-menu-button-contract
Oct 15, 2026
Merged

cixzhang merged 9 commits into
mainfrom
a11y-menu-button-contract

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What this proves

component:DropdownMenu AR4 and component:MoreMenu AR1 (the Menu Button adoption recorded in #7459 under spec:AST-020 FR15) 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-020 FR9: the APG Menu Button's required parts are required; the APG-recommended arrow opens are advisory. #7459 is merged; component:DropdownMenu and component:MoreMenu carry authority: draft on main, so the records' currency is tracked by the maintainers, not by this PR.

Expectations (AST-020 FR13)

Expectation Sources Layer Enforcement
menu-button.control.role-and-name WCAG 4.1.2, APG Menu Button accessibility-tree required
menu-button.control.haspopup-menu WCAG 4.1.2, APG Menu Button dom required
menu-button.control.expanded-state WCAG 4.1.2, APG Menu Button dom required
menu-button.control.controls-menu WCAG 1.3.1, APG Menu Button dom required
menu-button.menu.matches-state WCAG 4.1.2, APG Menu Button real-browser required
menu-button.open.enter-focuses-first-item WCAG 2.1.1, APG Menu Button real-browser required
menu-button.open.space-focuses-first-item WCAG 2.1.1, APG Menu Button real-browser required
menu-button.open.arrow-down-focuses-first-item APG Menu Button (optional Down Arrow), supporting WCAG 2.1.1 real-browser advisory
menu-button.open.arrow-up-focuses-last-item APG Menu Button (optional Up Arrow), supporting WCAG 2.1.1 real-browser advisory
menu-button.open.pointer-round-trip WCAG 4.1.2, APG Menu Button real-browser required
menu-button.control.unavailable-stays-closed WCAG 4.1.2, APG Menu Button real-browser required
menu-button.control.survives-an-aborted-press WCAG 2.5.2 real-browser required

Exemptions: 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 content

Current state (head after the merge of main)

  • Aborted press (WCAG 2.5.2) runs on every operable closed trigger. The opensOnPress fact and the press-model-pointer-cancellation exclusion are gone: the press-to-open model they relied on is still proposed in module:DropdownMenu/useMenuPress (FR8, DEC-4, OQ1). The required row now 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"), and each failure is an exact known-failure record (AST-021 FR8/FR10) in MenuButton.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.
  • Disabled MoreMenu. main now keeps a disabled trigger closed (DropdownMenu/MoreMenu disabled-trigger fix), so the more-menu-disabled known failure is removed and menu-button.control.unavailable-stays-closed must pass in that state.
  • Adoption source. The required parts (aria-haspopup, aria-expanded, aria-controls, Enter and Space opening on the first enabled item) cite spec:AST-020 FR15, the current rule that a control already playing the menu button role adopts the pattern's required parts. The optional ArrowDown/ArrowUp opens stay advisory (AST-020 FR9) and are recorded as a named exception by component:DropdownMenu AR4.
  • Merged main (conflicts in ci.yml and the a11y-spec README only: the Menu Button step and README row now sit beside the patterns main added). The evidence upload keeps the same retention as its sibling steps.
  • The Chromium binding gates on blockingResults alone (required rows block; advisory ArrowDown/ArrowUp rows and the exact known failure report), as the ToggleButton and Link bindings do.
  • MenuButtonA11y.stories.tsx renders every state through renderMenuButtonState, so the jsdom and Chromium lanes cannot drift.
  • Duplicate assertions (AST-021 FR6), each kept with its removal blocker:
    • DropdownMenu.test.tsx "has aria-haspopup and aria-expanded attributes" and MoreMenu.test.tsx "has aria-haspopup and aria-expanded attributes" overlap menu-button.control.haspopup-menu / expanded-state; DropdownMenu.test.tsx "keyboard open via Enter focuses the first enabled item" overlaps menu-button.open.enter-focuses-first-item. Blocker: component:DropdownMenu AR4 and component:MoreMenu AR1 cite those exact titles as their verification evidence while the binding is "pending" there. Once this binding is on main, 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" overlaps menu-button.control.unavailable-stays-closed in more-menu-disabled. Blocker: it is the regression test of the disabled-trigger fix, and the path it guards runs through Button's aria-disabled key 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, and spec:AST-020 FR15 sources (APG Menu Button wording verified against the published pattern on 2026-10-13: role button; aria-haspopup menu or true; aria-expanded true/false; optional aria-controls to the menu; Enter/Space to the first item; optional Down/Up Arrow to the first/last item). arrowKeysOpen is 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) and menu-button.chromium.spec.ts.
  • packages/core/src/DropdownMenu/__tests__/MenuButton.a11y.{states,renders,test}.tsx and MenuButton.a11y.chromium.spec.ts — six states: default Button trigger closed, open (isMenuOpen), a renderTrigger control spreading the props onto its own <button>, a disabled (unfocusable) Button trigger, MoreMenu, and a disabled (focusable) MoreMenu. All on presentation="popover": the bottom-sheet presentation is a dialog (aria-haspopup="dialog", DropdownMenu.tsx L823/L851) and outside the pattern by component:DropdownMenu AR4.
  • apps/storybook/stories/MenuButtonA11y.stories.tsx — one no-visual story per state.
  • .github/workflows/ci.yml — "Run Menu Button accessibility contract" step; runs when DropdownMenu, MoreMenu, the stories, or the contract change.
  • README row and index export.

Source lines behind the binding facts

At the reviewed head: aria-haspopup, aria-expanded, aria-controls on the anchored trigger (DropdownMenu.tsx L1561–1578, triggerProps; a renderTrigger control 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 and aria-haspopup="menu" (MoreMenu.test.tsx L74–89).

Evidence

  • Vitest DropdownMenu + MoreMenu directories: 322/322 (incl. the binding cases); pattern jsdom self-test 35/35; tsc (core, a11y-spec), eslint, Prettier, repository checks clean.
  • Real Chromium: the "Run Menu Button accessibility contract" CI step on this head runs the fixture self-test and the six binding states.

Ownership exclusions

MENU_BUTTON_EXCLUSIONS: menu-surface semantics (menu pattern, queued); bottom-sheet presentation (component:DropdownMenu AR4 / modal dialog); header/footer popover, whose trigger says aria-haspopup="dialog" (component:DropdownMenu AR4 and AR5 / modal dialog).

…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.
@vercel

vercel Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 15, 2026 3:04pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 13, 2026
@astracat-bot

astracat-bot Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 15, 2026, 4:02 PM UTC

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

AlertDialog (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 182 -
Complexity N/A Medium (12) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.9KB 1.2KB

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

38 of 474 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 754,077
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,188
Chat Full AI Chat probe light 238,087
Chat Full AI Chat probe dark 237,060
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
Chat Default neutral dark 33,205
Chat Default neutral light 32,876
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232
ProgressBar With Target Mark probe dark 7,895
ProgressBar With Target Mark probe light 7,895
TableRowExpansion Detail Panel neutral dark 5,150
TableRowExpansion Detail Panel neutral light 4,068
SideNav Resizable in AppShell probe light 3,846
TableGroupedRows Default neutral light 2,133

and 18 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-a11y job fails here because menu-button.control.survives-an-aborted-press fails for dropdown-button-closed and more-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 required through 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('');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 blockingResults alone, 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, as spinbutton.ts does.
  • The stories re-declare each state instead of rendering renderMenuButtonState, so the jsdom and Chromium lanes can drift apart.
  • The existing aria-haspopup/aria-expanded assertions in DropdownMenu.test.tsx and MoreMenu.test.tsx now duplicate the binding, so name their removal blocker in the description or narrow them (AST-021 FR6).

[Automated review]

Comment on lines +138 to +145
expect(
result.results.filter(
row =>
row.status !== 'pass' &&
row.status !== 'not-applicable' &&
row.status !== 'known-failure',
),
).toEqual([]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/MenuButton title produces a11y-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 diff from the base tip, which main replaced with .github/scripts/pr-scope-touches.sh in #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}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread .github/workflows/ci.yml Outdated
Comment on lines +1585 to +1592
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/a11y-spec/README.md Outdated
| `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 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

@cixzhang
cixzhang merged commit 980187a into main Oct 15, 2026
59 checks passed

This branch was successfully deployed

1 active deployment
Preview — 42c924f8 Deployed Oct 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant