Skip to content

fix(a11y): name a scrolling LayoutContent, LayoutPanel or StackItem - #9985

Merged
cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/c28-scroll-region-name
Nov 4, 2026
Merged

cixzhang merged 1 commit into
meta:mainfrom
vjeux:uikit/c28-scroll-region-name

Conversation

@vjeux

@vjeux vjeux commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Replaces #9095, which first explored this.

User impact

A screen reader reaching a scrolling LayoutContent, LayoutPanel or StackItem announces nothing about it. Chromium makes it a Tab stop when it holds nothing focusable.

Problem and solution fit

  • Why it occurs: the region is a plain div with no role or name unless the caller adds them, and most callers don't.
  • Why it harms the task: a nameless stop gives no context about what would scroll.
  • Why it matters: it usually holds the page's main content.

While the region scrolls, it is now a group named by the caller's label (StackItem: aria-label), or by a translated "Content". A region that fits, or cannot scroll, is unchanged.

Expected behavior and authority

The spec owner decided this on Nov 4: name the scrolling region only. The records carry her line verbatim: LayoutContent AR3, LayoutPanel AR3, Stack AR2.

Flagged: I added one sentence to each: a caller's role takes only the caller's label. spec:AST-025 FR12 lets AppShell's single main rely on its role, so it stays unnamed.

Smallest restoration

  • useScrollRegionProps (internal): the region's ref, and its role and name while it overflows.
  • The regions reuse AppShell's overflow observer, built for content held directly.
  • Only a scrollable div or span StackItem renders a client island; other elements keep their role.
  • One catalog key, @astryx.scrollRegion.label. No new prop and no tab index.

Evidence

  • Reproduction before the change: at 179dd61 (not for landing: main plus the new Chromium spec), the 4 scrolling cases fail.
  • Result after the change: the spec passes ×3, with group "Content", group "Messages", a panel and a StackItem.
  • Representative unchanged path: AppShell's unnamed-main check stays green, as do a fitting region, a static or ul StackItem and a main region.
  • Regression test: useScrollRegionProps.test.tsx, 12 cases.

Scope

  • One defect is restored.
  • Separable contradictory or unsettled tagalongs were removed or split.
  • No new public API, visual representation, default, or interaction model is hidden under the bug-fix label.
  • Public text and artifacts contain no internal Meta context.

Testing

Gates at this head, on main a0c48b8: typecheck, lint, 2,182 unit tests and the i18n, use-client and knowledge checks pass. In Chromium the new spec passes ×3, and the Layout, AppShell and Stack accessibility specs pass (44).

Writing rubric results

  • R1 narration: AR3 (Layout) and AR2 (Stack) keep the owner's words, including "(owner decision, 2026-11-04)". The other edits are present tense.
  • R2 research: none.
  • R3 evidence: the new verification rows name tests.
  • R4 rejected alternatives: none.
  • R5 change references: none.
  • R6 open questions: none added.

@vercel

vercel Bot commented Nov 4, 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 Nov 4, 2026 6:08pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 4, 2026
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 175b388

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
LayoutFooter 5.9 kB (0) · 1.6 kB CSS (0) — — · ✓ · ✗
LayoutHeader 6.0 kB (0) · 1.6 kB CSS (0) — — · ✓ · ✗
StackItem 🔴 16.1 kB (+11.5 kB) · 219 B CSS (0) — — · ✓ · ✓
13 more components
Component Size LoC Visual · A11y · RTL
LayoutPanel 🔴 19.1 kB (+11.4 kB) · 4.9 kB CSS (0) — — · ✓ · ✓
LayoutContent 🔴 17.4 kB (+11.4 kB) · 1.7 kB CSS (0) — — · ✓ · ✓
CommandPalette 54.7 kB (+1.2 kB) · 12.3 kB CSS (0) — — · — · —
richtext/RichTextEditorToolbar 229.8 kB (+1.1 kB) · 454 B CSS (0) — — · — · —
AlertDialog 87.8 kB (+1.0 kB) · 13.1 kB CSS (0) — — · — · —
lab/CommentThread 260.7 kB (+796 B) · 2.4 kB CSS (0) — — · — · —
lab/Comments 266.0 kB (+763 B) · 2.5 kB CSS (0) — — · — · —
lab/CommentComposer 105.6 kB (+717 B) · 1.9 kB CSS (0) — — · — · —
PowerSearch 244.8 kB (+21 B) · 22.2 kB CSS (0) — — · — · —
Layout 6.8 kB (0) · 2.3 kB CSS (0) — — · ✓ · ✓
Stack 5.7 kB (0) · 1.6 kB CSS (0) — — · ✓ · ✓
Tokenizer 70.0 kB (0) · 17.1 kB CSS (0) — — · — · —
AppShell · Storybook 73.2 kB (−125 B) · 12.2 kB CSS (0) 1,096 (0) — · ✓ · ✓

Package size: @astryxdesign/core 6.43 MB packed (+3.9 kB); no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 8 rows, core 2 rows; run pnpm api:report.

Touched components checked; add ci:run-visual, ci:run-a11y or ci:run-rtl to run the full suites.


Generated by PR Enrichment workflow | View full report

@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 18c7636

@astracat-bot

astracat-bot Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 4, 2026, 6:20 PM UTC

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: the scroll-region naming works as the approved records describe for LayoutContent, LayoutPanel and a default StackItem, but the StackItem path and the records need fixes first.

  • A scrollable StackItem rendered as nav, main or ul gets role="group" while it overflows (StackItemScrollRegion.tsx:46), so screen readers lose its landmark or list role, which AST-025 FR12 rules out ("group unless the viewport is itself a landmark").
  • A scrollable StackItem rendered from a Server Component no longer accepts a server-component as (StackItem.tsx:121), which narrows existing usage in a patch [fix] against AST-017 FR53.
  • The LayoutContent, LayoutPanel and Stack records still say in their compatibility and at-a-glance rows that the DOM output does not change, so they should name the new role and aria-label output and its patch [fix] class under AST-017 FR53.
  • Putting the 'use client' child in Stack/ makes serverSafeComponents.test.ts skip the ./Stack entry, so a client import added to StackItem.tsx no longer fails it; moving the child beside the hook and listing it in CLIENT_ISLANDS restores the guard.

Taking the client path only when as is a plain div or span would fix the first two together. The record edits need a fresh spec approval after the new commit.

[Automated review]

}: StackItemScrollRegionProps) {
const region = useScrollRegionProps({
isScrollable: true,
role: elementProps.role,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only an explicit role reaches the hook here, so an as with its own role is re-roled while it overflows: in Chromium a scrolling as="nav" goes from navigation to group, as="main" from main to group, and as="ul" from list to group. AST-025 FR12 says "The role is group unless the viewport is itself a landmark". Treat an as other than div or span like a caller role: no default role or name, and no observation.

Comment thread packages/core/src/Stack/StackItem.tsx Outdated
...props,
};

if (isScrollable) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From a Server Component, as now has to cross into the 'use client' StackItemScrollRegion, so a server-component as fails, as the JSDoc above notes. AST-017 FR53 does not let a patch [fix] narrow existing usage. Taking this path only for a div or span string keeps the static render for every other as (and fixes the role override too).

- **AR2 — Keyboard scrolling.** A scrolling region is a browser scroll
container, so its overflow scrolls with the keyboard once focus is inside
it or on it.
- **AR3 — A scrolling region is named.** While the region scrolls, its

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AR3 adds role and aria-label output, but this record's at-a-glance rows (Builder impact, Compatibility/readiness) and its Compatibility and migration list still say the DOM output does not change. Name the change and its patch [fix] class under AST-017 FR53 there, as LayoutPanel already does for its AR2. LayoutPanel.spec.md (for AR3) and Stack.spec.md need the same edit.

@@ -0,0 +1,60 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

'use client';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A 'use client' file in Stack/ makes serverSafeComponents.test.ts skip ./Stack in its directive-free check, so a client import added to StackItem.tsx now passes it (the same import fails on the base branch). Moving this file beside the hook and adding it to CLIENT_ISLANDS keeps the guard that Stack's AR1/PR1 verification row relies on.

@vjeux
vjeux force-pushed the uikit/c28-scroll-region-name branch from 18c7636 to 71941b4 Compare November 4, 2026 17:05
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026
@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec 71941b4

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026
@vjeux
vjeux force-pushed the uikit/c28-scroll-region-name branch from 71941b4 to e1722e4 Compare November 4, 2026 17:21
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026
@cixzhang

cixzhang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

/approve-spec e1722e4

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 4, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested: the scroll-region naming matches the LayoutContent, LayoutPanel and Stack records an engineering owner approved for this head, and the earlier findings are fixed, but the changeset needs two corrections.

  • Regression: the changeset says "a scrollable StackItem" is named, but only a div or span item is (StackItem.tsx:120), so it should say "a scrollable div or span StackItem".
  • Miss: the changeset names neither the old behavior nor the accessibility target that AST-017 FR53 asks an accessibility [fix] changeset to name, so it needs a "Before, …" sentence and the target (the records' AR3 and AR2).

A new commit voids the current /approve-spec, so after the fix an engineering owner, the author included, comments /approve-spec with the new head SHA.

[Automated review]

Comment thread .changeset/scroll-region-name.md Outdated
'@astryxdesign/core': patch
---

[fix] LayoutContent, LayoutPanel and a scrollable StackItem name their region while it scrolls. It is then a group named by the caller's label, or by a translated "Content", so a screen reader that reaches it announces it. A caller's role stays with only the caller's name, and Tab behavior stays the browser's.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two fixes here. Only a div or span StackItem takes the naming path (StackItem.tsx:120), so "a scrollable StackItem" overstates it: say "a scrollable div or span StackItem". AST-017 FR53 also asks an accessibility [fix] changeset to name the old behavior and the accessibility target, for example: "Before, a scrolling region was a nameless element that Chromium could make a Tab stop" and the records' AR3 and AR2.

Comment on lines +10 to +14
* @position Internal hook for LayoutContent, LayoutPanel and StackItem.
* A browser can make a scrolling region a Tab stop of its own, so a screen
* reader can reach it. While it scrolls, the region is a named group, so
* the screen reader announces it. A caller's role, such as a landmark,
* stays with the caller's name. Tab behavior stays the browser's.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking (INV29): this @position prose repeats the hook's JSDoc below. "Internal hook for LayoutContent, LayoutPanel and StackItem." is enough here.

render(
<StackItem
aria-label="Results"
as="section"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: with as="section" this StackItem never reaches StackItemScrollRegion (only a div or span does), so the caller-role pass-through that Stack AR2's verification row cites is not exercised. Dropping as="section" (and the tag assertion) covers it.

While the region scrolls, it is a group named by the caller's label, or
by a translated "Content". A screen reader that reaches it, including as
a browser-made Tab stop, announces it. A caller's role stays, with only
the caller's name, and Tab behavior stays the browser's.

The three regions reuse AppShell's overflow observer, which measures a
region that holds its content directly. Only a scrollable div or span
StackItem measures, through a client island; any other element keeps
its own role, and StackItem otherwise stays a static render.

Replaces meta#9095.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved pending CI: CI has not run on this head yet.

The scroll-region naming for LayoutContent, LayoutPanel and a scrollable div or span StackItem matches the LayoutContent, LayoutPanel and Stack records an engineering owner approved, and Chromium's accessibility tree shows each overflowing region as a named group. The two changeset corrections from the last review are in: it now names the div or span scope, the old behavior and the AR3/AR2 target.

Next step: mark the pull request ready for review, and a maintainer adds the ci:run label so CI runs, since the branch lives in a fork.

[Automated review]

@vjeux
vjeux marked this pull request as ready for review November 4, 2026 18:28
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 4, 2026
@cixzhang
cixzhang marked this pull request as draft November 4, 2026 18:47
@cixzhang
cixzhang marked this pull request as ready for review November 4, 2026 18:47
@cixzhang
cixzhang merged commit cb2a34a into meta:main Nov 4, 2026
159 of 164 checks passed
@vjeux
vjeux deleted the uikit/c28-scroll-region-name branch November 4, 2026 21:36

This branch was successfully deployed

1 active deployment
Preview — 175b3889 Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) 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.

2 participants