Skip to content

[Component Audit] DropdownMenu: keep the focused row visible in forced colors - #7517

Open
cixzhang wants to merge 5 commits into
mainfrom
night-watch-component-audit/DropdownMenu/2026-10-14
Open

cixzhang wants to merge 5 commits into
mainfrom
night-watch-component-audit/DropdownMenu/2026-10-14

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Primary intent

Whole-component audit of core/DropdownMenu under the Component Audit Rubric 1.16.6 (nightly mode), with only the objective remediations its findings settle. spec:AST-029/FR7 lets those fixes travel with the audit evidence; each keeps its own red-before/green-after proof below.

  • Package: @astryxdesign/core — DropdownMenu, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup/DropdownMenuRadioItem, DropdownMenuGroup, DropdownMenuDivider, DropdownMenuSubMenu
  • Audited base: 5f9be6c18deb78e6e27493c7a2932a0f86308273 (pre-fix score recorded at 58c4f241e92954e1b0c6058201bc5537a888f95e; the only commit between them is a Grid docs change)
  • Component contract: component:DropdownMenu (authority: draft) and module:DropdownMenu/useMenuHover (draft) at the base; module:DropdownMenu/useMenuPress is current. Draft records were read for context only and govern no finding.

User or maintainer impact

  • Keyboard users in forced-colors modes (Windows High Contrast and similar) could not see which menu row was focused. Rows show focus with a translucent overlay; forced colors replace it with the page color, so the focused row looked like every other row and Enter's target was invisible. The focused row now paints the system Highlight/HighlightText pair, as native menus do. Action, destructive, checkbox, radio, and sub-menu rows; the same rows render in ContextMenu and breadcrumb menus. Nothing changes outside forced colors.
  • Theme authors writing presentation:popover rules for the dropdown-menu target — a visual prop the docs list — got nothing: only the bottom sheet reflected its presentation. The anchored menu and its flyouts now reflect data-presentation="popover".
  • Developers and code assistants reading the docs were told button is "mutually exclusive with trigger" (no such prop exists; it is renderTrigger, and the development warning said the same), that the private menu radius defaults to --radius-element (it is --radius-container), and that disabled checkbox and radio rows "stay focusable" (they take no focus; arrows, typeahead, and the pointer highlight skip them).

Problem and solution fit

  1. Forced-colors focus. Why invisible? Focus is paint-only (backgroundColor on :focus, outline: none). Why does that fail? Forced colors force background-color to the system Canvas color, keeping the overlay's alpha. Why does it matter? Keyboard operation depends on knowing where focus is (WCAG 2.4.7). Fix: add @media (forced-colors: active) arms — Highlight background and HighlightText text — to the existing :focus (and pressed) arms of each row. :focus matches the menu's one-highlight model, where pointer hover moves focus. The destructive variant's own color keeps the same arm, because a later flat color would otherwise replace it.
  2. Presentation reflection. The popover branch called themeProps('dropdown-menu') without the documented presentation capability. Fix: pass {presentation: 'popover'} on the anchored menu and on sub-menu flyouts, the other pointer surface carrying the same target.
  3. Docs. Prose was false against the types, the source default, and existing tests (skips aria-disabled menuitemradio and menuitemcheckbox items during arrow navigation). Fix: correct the prose, the TriggerProps JSDoc, and the warning text. Behavior does not change.

Compiled CSS against the base differs by exactly three rules, all inside @media (forced-colors: active). Every non-forced-colors frame therefore paints identically to the base.

Authority

Finding (rubric id, severity) Governing authority
Focused row invisible under forced colors (§1 A14, FIX on an existing component) WCAG 2.2 SC 2.4.7 Focus Visible (AA) through spec:AST-020/FR1 (WCAG 2.2 A/AA baseline) and FR10 (forced colors is a component outcome to review)
Popover branch omits documented presentation (§2 INV6 / X17, FIX) architecture:component-theming-surface/INV6: variant state is reflected on the owning target through themeProps; the dropdown-menu target documents presentation
Docs name a nonexistent trigger prop (§8 X5a, BLOCK) architecture:public-component-api consumer-doc projection: consumer docs change when they "would otherwise become false"; docs naming a nonexistent prop is a listed failure. renderTrigger/button exclusivity follows spec:AST-055/FR7
Var default and disabled-row doc drift (§8, FIX) Same consumer-doc rule; shipped source and existing tests define the observed fact

Global review routing: node scripts/review-global-baselines.mjs --authority-commit 58c4f241e92954e1b0c6058201bc5537a888f95e --review-head <head> --triggers accessibility,theming,public-api,behavior matched current claims in architecture:component-theming-surface, architecture:public-component-api, architecture:theme-application, architecture:theme-authoring-contract, architecture:theme-compilation, architecture:container-padding, spec:AST-002, AST-009, AST-012, AST-020, AST-055, AST-056, AST-057, AST-058, and AST-059. Only the claims cited in the table govern a change here. AST-056/057/058 do not apply to DropdownMenu. AST-009 is not triggered: no AT-specific focus-order or announcement claim changes (FR7 proportionate evidence).

Component audit score

Score Grade Open BLOCKs
Before (recorded before any edit) 81.9 C (capped by one BLOCK) 1 — X5a phantom trigger prop
After 87.5 B 0

Rubric 1.16.6. §5b (rendered judgment) is not measured in both rows and its weight is redistributed, as the rubric requires. The managed Mac used for this audit cannot launch a browser, so rendered evidence comes from exact-head Linux CI (below). What moved: §1 4 → 4.5, §2 4 → 4.5, §8 3 → 4.5. §11 is not run (existing core component).

Section scorecard
Section W Before After Notes
§1 Accessibility 16 4 4.5 A14 fixed. APG pattern not named in consumer docs is an authority gap (no current owner adopts the Menu Button pattern for this component yet)
§2 Theming 14 4 4.5 Presentation reflected; see Needs Review for the target-ownership question
§3 Public API 14 4.5 4.5 Conventions hold
§4 Behavior 12 4 4 Rich jsdom and Chromium coverage; rendered states await exact-head frames
§5a Objective design 6 5 5 Role tokens, concentric inner radius, alpha overlays, opacity-only motion
§5b Rendered design 4 not measured not measured Frames pending exact-head CI
§6 Testing 8 4.5 4.5 + forced-colors and theme-reflection regressions, + evidence spec
§7 Code health 8 4 4 NITs only
§8 Docs 8 3 4.5 X5a and two doc-drift FIXes fixed
§9 i18n & RTL 5 4 4 Curated RTL target exists; pr-rtl supplies the run
§10 Responsive & touch 5 4 4 Narrow and coarse frames pending exact-head CI

Retained NITs: the focus-on-open Effect schedules an animation frame without cancelling it (benign: the callback is a no-op on a closed or detached menu; a naive cancel would drop focus after a re-render, so it is left alone); useMenuOverflow creates a Resize/MutationObserver per open menu (one per open menu, not a repeated-row path, so architecture:react-component-runtime/INV7 holds); the renderTrigger JSDoc @example carries // comment lines inside its bare fence (wiki guidance only; no current repository rule).

FR5 evidence inventory (closed)

Rows come from the public exports in DropdownMenu/index.ts, documented concepts, states reachable through the public API, and branches reachable from them. "CI frame" names a frame of the exact-head evidence spec.

Inventory
# Concept / state Source Existing tests Consumer docs Rendered evidence Authority Gap
1 Built-in trigger (button, hasChevron, default i18n label, size/variant, icon-only, open paint, tooltip hidden while open) DropdownMenu.tsx DropdownMenu.test.tsx DropdownMenu.doc.mjs CI trigger-rest, trigger-rest-dark Button delegation, component-theming-surface/INV5 —
2 renderTrigger (ARIA wiring, press model, keyboard opens, mutual exclusion and warning) DropdownMenu.tsx custom-trigger suite; new warning test doc (corrected) existing CustomTrigger story, not captured AST-055/FR1–FR3, FR7 No frame (same wiring as row 1)
3 Open state: uncontrolled, isMenuOpen/onOpenChange, mounted open, onClick DropdownMenu.tsx controlled and mounted-open suites doc CI keyboard-open, pointer-open architecture:layer-runtime —
4 Keyboard: Enter/Space/ArrowDown/ArrowUp open, wrap, Home/End, PageUp/PageDown, typeahead, Tab, Escape DropdownMenu.tsx, useListFocus, useTypeahead keyboard suites doc CI keyboard-open, disabled-row (End) WCAG 2.1.1 via AST-020/FR1; component-local otherwise APG adoption is a draft decision (Needs Review)
5 Focus on open and close by modality DropdownMenu.tsx closing suites; DropdownMenuFocusReturn.a11y.browser.spec.ts — CI keyboard-open vs pointer-open architecture:interaction-modality/INV1 —
6 Press model (mouse press-open drag-release, touch hold) useMenuPress press suites; MenuPress.a11y.chromium.spec.ts doc CI pressed-row module:DropdownMenu/useMenuPress FR1–FR7 (current) —
7 presentation: popover, bottom-sheet, adaptive DropdownMenu.tsx, useAdaptivePresentation presentation suites; new reflection tests doc CI bottom-sheet(-dark), adaptive-narrow-coarse interaction-modality, component-theming-surface/INV6 presentation:adaptive is never reflected (Needs Review)
8 placement/alignment, logical in RTL DropdownMenu.tsx placement suites doc CI keyboard-open-rtl; curated RTL target core-dropdownmenu--alignment-end layer-runtime —
9 menuWidth, menuMaxHeight, viewport caps, overflow scroll DropdownMenu.tsx, menuWidth.ts, useMenuOverflow.ts width and height suites doc CI overflow-scroll, popover-narrow AST-059/FR2, FR7 —
10 header/footer regions DropdownMenu.tsx region suite; MenuHeaderFooter.a11y.chromium.spec.ts doc CI header-footer component-local Overlaps open #7515 (semantics change proposed there)
11 Data items: action fields (icon, description, endContent, disabled, destructive, hasCloseOnSelect, href/target/rel, id), divider, section renderDropdownItems.tsx, DropdownMenuItem.tsx data-mode, href, section suites doc CI keyboard-open, destructive-row(-dark), disabled-row(-dark) family:navigation-destinations (href rows) —
12 DropdownMenuItem compound DropdownMenuItem.tsx compound suites; new forced-colors tests DropdownMenuItem.doc.mjs CI submenu-open (compound rows) as row 11 —
13 DropdownMenuDivider DropdownMenuDivider.tsx divider suite doc CI destructive-row (story includes a divider) component-theming-surface/INV5 —
14 DropdownMenuGroup (named role="group") DropdownMenuGroup.tsx group suite doc existing CompoundWithGroups story, not captured component-local No frame
15 DropdownMenuCheckboxItem (aria-checked, disabled, close-on-select) DropdownMenuCheckboxItem.tsx DropdownMenuSelectable.test.tsx; new forced-colors test doc (corrected) CI checkbox-rows(-dark) WCAG 4.1.2 via AST-020/FR1 —
16 DropdownMenuRadioGroup/RadioItem DropdownMenuRadio*.tsx selectable suite; new forced-colors test doc (corrected) CI radio-rows as row 15 —
17 DropdownMenuSubMenu: flyout, drill-in, adaptive, async spinner DropdownMenuSubMenu.tsx, useMenuDrillIn.tsx DropdownMenuSubMenu.test.tsx; DropdownMenuDrillIn.a11y.chromium.spec.ts doc CI submenu-open(-dark, -rtl) interaction-modality Drill-in and spinner frames not captured here
18 DropdownMenuContext/useDropdownMenuContext (custom items) DropdownMenuContext.tsx exercised through items doc non-visual plumbing public-component-api/INV12 —
19 Theming: six targets, private vars all row files themingTargets.test.ts, derivedVarRegistry.test.ts doc (var default corrected) CI keyboard-open-probe, keyboard-open-butter(-dark) component-theming-surface Target ownership (Needs Review)
20 Forced-colors focus four row files new DropdownMenuForcedColors.test.tsx changeset CI forced-colors-focus(-dark), forced-colors-focus-before-control WCAG 2.4.7 via AST-020/FR1, FR10 —
21 i18n: default label, Back, Back to DropdownMenu.tsx, DropdownMenuSubMenu.tsx catalog check — — check:i18n-catalog —
22 Coarse pointer: marker swap, adaptive sheet Checkbox/Radio rows, useAdaptivePresentation selectable and presentation suites doc CI adaptive-narrow-coarse interaction-modality —

Change and evidence

TDD, red before green, for each production change (logs kept with the run):

  • DropdownMenuForcedColors.test.tsx — 6 cases. Red on the base row files (no forced-colors :focus rule on any row class); green at the head.
  • DropdownMenuThemeReflection.test.tsx — root popover, data-mode popover, and sub-menu flyout reflect data-presentation="popover"; the warning names renderTrigger. Red before each change (flyout case red separately); green at the head.

Screenshots and evidence obligations

Real-browser frames come from exact-head CI, not from this description. This PR adds packages/core/src/DropdownMenu/__tests__/DropdownMenuAudit.a11y.chromium.spec.ts, run by a pr-a11y step gated on this PR title. The step runs accessibility-audit.js --components DropdownMenu --fail-on-new and the spec, then uploads dropdown-menu-audit-evidence-<sha7>. The artifact holds:

  • 31 receipted frames, captured as four bounded serial tests of at most eight frames each (one frame takes about half a minute on the CI runner), followed by a final test that requires all 31 states and runs the cross-frame checks: closed, keyboard open (neutral light and dark, butter light and dark, probe, RTL), pointer open, hovered row (light and dark), pressed row, disabled and destructive rows (light and dark), checkbox and radio rows, open sub-menu (light, dark, RTL), overflow scroll, bottom sheet (light and dark), adaptive at 320px with a coarse pointer, popover at 320px, header and footer, and forced-colors focus (light and dark) with a helper-isolated before-control arm;
  • one sensor receipt per frame: build SHA bound to the served Storybook stamp, story id, theme, mode, direction, viewport, DPR, media, focused element, geometry and settle delta, page errors;
  • a manifest.json with the state-visual matrix and a rendered contrast-pair matrix.

The spec fails closed when the environment is not what the frame claims (build, theme, mode, viewport, pointer, forced colors, settled geometry, page errors) and when the claimed state is not observed. Every frame has its own state assertion, and a frame without one fails:

  • closed: no menu open, aria-expanded="false";
  • keyboard open (all themes, RTL, 320px, forced colors): anchored popover, first enabled row focused; at 320px the menu box stays inside the viewport;
  • pointer open and header/footer: the menu element itself focused, no row focused (root rows exclude rows nested in a flyout); header/footer frame also finds the filter field and the Create button in the popover outside the menu element;
  • hovered and pressed: the second row focused;
  • disabled: a disabled row exists, End lands on the last enabled row, and the trailing disabled row is skipped;
  • destructive: the focused row is the destructive variant;
  • checkbox: two menuitemcheckbox rows with aria-checked false/true; radio: three menuitemradio rows with only "Newest" checked;
  • open sub-menu: more than one menu, and the focused menuitem is "Folder A" in the second visible menu (the flyout), not a root row;
  • overflow: the menu scrolls internally (scrollHeight > clientHeight, overflow-y auto);
  • bottom sheet and adaptive at 320px with a coarse pointer: resolve to bottom-sheet inside a dialog;
  • forced colors: the focused row's computed background is Highlight and more than half its interior pixels match; the before-control arm matches under 5%.

The 320px frames must not scroll horizontally.

Before/after: no frame outside forced colors can change, because the compiled CSS delta is confined to @media (forced-colors: active). The forced-colors pair uses a helper-isolated control arm that restores the overlay focus paint on the head build. It is labelled as such and is not a base build.

Expected visual-gate delta: the probe theme ships a presentation:popover rule for dropdown-menu that never matched before. Probe-theme DropdownMenu captures will now show it. That change is the fix working, not drift.

These frames, the axe report, and pr-rtl are this head's open evidence obligations. §5b stays not measured until they are read.

Needs Review (deliberately unresolved)

  1. Draft component contract damage. DropdownMenu.spec.md (draft) has concept rows inside the behavior table, DEC references that do not match the decision log (FR7 cites DEC-3 and FR9 cites DEC-2, but those bodies sit under DEC-7 and DEC-3), a verification map whose FR numbers are shifted, duplicated "Open questions"/"Content boundary" sections, and trigger where the prop is renderTrigger. Three open pull requests reflow this file, so the observational repair is left to its owner rather than reflowed here.
  2. presentation:adaptive never matches. The target reflects the resolved presentation (popover or bottom-sheet), so the documented adaptive value can never select a theme rule. Reflecting the requested value or narrowing the documented values is a theming-contract decision.
  3. dropdown-menu target placement. On the anchored menu, Popover's popover target paints the surface (background, shadow, outer radius). dropdown-menu sits on the inner menu element (padding, inner radius), so a theme radius on dropdown-menu can disagree with the outer corner. Who owns the anchored surface (component-theming-surface/INV4–INV5) is an ownership question.
  4. APG adoption. The trigger already behaves as a Menu Button (draft DEC-9; contract test in open test(a11y): Menu Button contract and DropdownMenu/MoreMenu trigger binding #7469). Until a current record adopts the pattern, keyboard findings rest on WCAG 2.1.1 and component-local behavior.
  5. Pressed feedback on selectable rows. Checkbox and radio rows paint no pressed state, while action rows do. Whether they should is a visual decision.
  6. Open-branch marker under forced colors. A sub-menu row keeps an overlay while its flyout is open and focus has moved into the flyout. Under forced colors that marker disappears. It is not a focus indicator, so it is left as a visual decision.

No public API, default, or compatibility promise changes here. The data-presentation attribute was already documented on this target.

FR10 eligibility report

{
  "version": 1,
  "component": "core/DropdownMenu",
  "mode": "nightly",
  "rubricVersion": "1.16.6",
  "repositoryBase": "5f9be6c18deb78e6e27493c7a2932a0f86308273",
  "componentContract": {"id": "component:DropdownMenu", "authority": "draft", "backfillInThisPr": false},
  "inventory": {"rows": 22, "closed": true, "rowsWithoutRenderedFrame": [2, 14, 17, 18, 21]},
  "remediations": [
    {"id": "forced-colors-focus", "kind": "bug", "evidence": ["DropdownMenuForcedColors.test.tsx red->green", "CI forced-colors frames + before-control arm"]},
    {"id": "presentation-reflection", "kind": "bug", "evidence": ["DropdownMenuThemeReflection.test.tsx red->green", "CI probe-theme frame"]},
    {"id": "consumer-doc-drift", "kind": "doc-drift", "evidence": ["warning test red->green", "types/source/tests"]}
  ],
  "unresolved": {"objective": [], "manual": ["spec-damage", "adaptive-reflection", "target-ownership", "apg-adoption", "selectable-pressed", "open-branch-forced-colors"]},
  "approvals": {"specOwner": "not requested", "reviewLoop": "pending"},
  "checks": "pending exact head",
  "verdict": "ineligible",
  "reasons": ["spec:AST-029 is phase: accepted (FR11 auto-merge inactive)", "manual decisions recorded", "exact-head evidence pending"]
}

Scope

  • This PR has one primary intent: the DropdownMenu audit and its objective remediations (spec:AST-029/FR7).
  • Independently removable deltas were removed or split: the draft-spec repair and every judgement item are left out (Needs Review).
  • Public text and artifacts contain no internal Meta context.

Testing

Local, managed Mac (no browser available):

  • pnpm vitest run packages/core/src/DropdownMenu packages/core/src/ContextMenu packages/core/src/Breadcrumbs packages/core/src/MoreMenu packages/core/src/hooks/useMenuPress.test.tsx packages/core/src/hooks/useMenuHover.test.tsx packages/core/src/theme packages/core/src/docPropReferences.test.ts packages/core/src/docPropLiterals.test.ts packages/core/src/pressableCoverage.test.ts .github/scripts — 102 files, 3063 tests pass
  • pnpm vitest run packages/core/src/DropdownMenu packages/core/src/ContextMenu packages/core/src/Breadcrumbs packages/core/src/theme/themingTargets.test.ts packages/core/src/docPropReferences.test.ts at the final head — 772 pass
  • ASTRYX_STRICT_LINT=1 eslint packages/core/src/DropdownMenu apps/storybook/stories/DropdownMenu.stories.tsx — clean
  • pnpm -F @astryxdesign/core typecheck, pnpm -F @astryxdesign/core typecheck:docs, pnpm -F @astryxdesign/storybook typecheck
  • pnpm build, node scripts/verify-exports.mjs, pnpm sync:exports:check
  • pnpm -F @astryxdesign/docsite generate && pnpm -F @astryxdesign/docsite test
  • pnpm storybook:build
  • pnpm check:repo, pnpm check:changesets, pnpm check:knowledge, pnpm check:use-client, pnpm check:i18n-catalog
  • Compiled CSS diff of packages/core/dist/astryx.css, base vs head: three added rules, all under @media (forced-colors: active)

Exact-head CI: pr-a11y (including the new audit evidence step), pr-rtl, visual gate, and the repository-wide test suite.

…olors

- Paint a focused menu row with the system Highlight/HighlightText pair under
  forced colors, where the translucent focus overlay is replaced by the page
  color (WCAG 2.4.7). Action, destructive, checkbox, radio, and sub-menu rows.
- Reflect the documented presentation capability on the anchored menu and its
  flyouts (data-presentation="popover"), so presentation:popover theme rules
  apply.
- Correct consumer docs: there is no trigger prop (renderTrigger), the private
  menu radius defaults to --radius-container, and disabled checkbox/radio rows
  take no focus.
- Add jsdom regressions and an exact-head Chromium evidence spec with a CI step
  gated on the component-audit PR title.
@vercel

vercel Bot commented Oct 14, 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 14, 2026 11:43am UTC

Request Review

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

astracat-bot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Oct 14, 2026, 12:11 PM UTC

@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 14, 2026
…state

- Correct the forced-colors changeset: the focused row paints the
  Highlight/HighlightText pair, not an outline.
- Reclassify the warning and docs changeset as [fix]: it changes the
  development warning string.
- Give every Chromium evidence frame its own claimed-state assertion
  (focused row, semantics and checked state, overflow, resolved
  presentation, header and footer regions) and fail any frame without one.
…ut row

The sub-menu frames compared two root-menu indexes that are both -1 when
focus is in the flyout, so the assertion could pass without proving focus
moved. Record the focused row's role, label and its menu's position among
the visible menus, and require menuitem "Folder A" in the second menu.
Root rows now exclude rows that belong to a nested flyout.
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

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

44 of 456 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,081
Chat Full AI Chat probe dark 237,048
Markdown Default probe light 159,625
Markdown Default probe dark 158,986
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

and 24 more.


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

… chunks

The 31-frame browser test hit its single 10-minute timeout after 19 frames
(about 31 seconds a frame on the CI runner). Capture the frames as four
serial tests of at most eight frames each, sharing one results map and one
manifest, then run a final test that requires every state and the
cross-frame presentation and probe-theme checks. Per-frame fail-closed
assertions and context cleanup are unchanged.
…er sees it

The sub-menu frames waited for two role="menu" elements matching
:popover-open, but :popover-open belongs to the flyout's layer host, not to
its inner role="menu" element, so the wait could never succeed and the
chunk hung until its timeout. Wait up to five seconds for a second visible
menu with focus on a menuitem inside it. The exact "Folder A" in the
second menu assertion is unchanged.

@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: the forced-colors focus fix, the presentation reflection and the doc corrections are all in scope, but the PR's own forced-colors evidence fails and the forced-colors styling hides some row content in Chromium.

  • The forced-colors-focus frame fails in pr-a11y because highlightFill() compares pixels to Highlight's RGB while Chromium reports it with 0.8 alpha, so the composited fill never matches; account for the alpha and rerun so the forced-colors frames exist.
  • With forced colors on, text that inherits the row's HighlightText (such as a plain-string endContent shortcut) disappears on the focused row behind Chromium's Canvas backplate and icons drop to about 2:1, so mark focus with a system-color outline or make the focused row forced-color-adjust: none with explicit HighlightText colors.
  • Then cover a destructive or icon row and an end-content row in the forced-colors frames and update the changeset to match what renders.

[Automated review]

document.body.append(probe);
const highlight = getComputedStyle(probe).color;
probe.remove();
const [hr, hg, hb] = (highlight.match(/[\d.]+/g) ?? []).map(Number);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Chromium's emulated forced-colors palette reports Highlight as rgba(5, 0, 73, 0.8), so this drops the alpha and compares the rendered fill (rgb(55, 51, 109) once composited over Canvas) with rgb(5, 0, 73). Nearly every pixel misses the 30-unit tolerance, which is why forced-colors-focus fails in pr-a11y with a ratio of 0.0005. Composite the parsed color over the row's backdrop before comparing, or sample a reference element painted with background-color: Highlight in the same page.

default: colorVars['--color-text-primary'],
':focus': {
default: null,
'@media (forced-colors: active)': 'HighlightText',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In Chromium's forced colors this HighlightText only reaches text that inherits from the row, and Chromium paints a Canvas backplate behind that text, so a plain-string endContent (a shortcut hint) turns white-on-white and disappears while the row is focused. The label and description spans set their own colors, so they are forced to CanvasText on a Canvas chip, and icons render at about 2:1 on the fill. The same arm is in the checkbox, radio and sub-menu rows. Either mark focus with a system-color outline and leave the row's colors alone, or make the focused row forced-color-adjust: none and set label, description, end-content and icon colors to HighlightText explicitly.

This branch was successfully deployed

1 active deployment
Preview — abd3adf0 Deployed Oct 14, 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. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant