Skip to content

fix: a11y baseline pass across components#805

Draft
paanSinghCoder wants to merge 1 commit into
mainfrom
fix/a11y-baseline-issue-673
Draft

fix: a11y baseline pass across components#805
paanSinghCoder wants to merge 1 commit into
mainfrom
fix/a11y-baseline-issue-673

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

Summary

Address the cross-component accessibility gaps tracked in #673 in one PR. Each fix is targeted at the specific WCAG / ARIA issue from the child issues (#596, #600 already fixed, #601, #605 already fixed, #610, #620, #621, #623, #624, #625, #626, #631, #633, #634, #635, #636, #637, #638, #639 already fixed, #640, #642, #643 already fixed, #645, #647 already fixed, #648) — no broader refactors.

Component changes (21 source files):

Component Change
Tooltip aria-label / aria-labelledby flow to Popup so ReactNode messages get an accessible name
TextArea aria-invalid / aria-required propagated from Field context
Table Table.Head defaults scope=\"col\"; SectionHeader uses scope=\"colgroup\"; new Table.Caption
Spinner Drop conflicting aria-hidden + role=\"status\"; add ariaLabel (default \"Loading\"); aria-hidden override demotes to decorative cleanly
Skeleton aria-hidden=\"true\" on decorative placeholder container
Sidebar Remove orphan role=\"listitem\"
SidePanel Title renders as <h2> with generated id; new titleId prop for aria-labelledby
Drawer aria-label / aria-labelledby customisable; new closeLabel prop (default \"Close\")
Separator New decorative prop → role=\"presentation\" + aria-hidden
Select Remove conflicting aria-multiselectable on Combobox list (data-multiselectable retained for styling)
ScrollArea aria-label / aria-labelledby apply role=\"region\" to viewport
List Keep explicit role=\"list\" (Safari drops the implicit role under list-style: none); drop redundant role=\"listitem\"; remove generic default aria-label=\"List\"; new level prop on List.Header
Link Drop redundant role=\"link\"; use children for aria-label only when it's a string
Label New requiredText + showRequiredIndicator props to balance the existing (optional) indicator
InputField aria-invalid / aria-required from Field context; leading / trailing icon wrappers aria-hidden
Image Drop redundant role=\"img\" and aria-label={alt} (native alt is the accessible name)
IconButton Drop redundant aria-disabled; strengthen aria-label guidance in JSDoc
Container No default role=\"region\"; role applied only when an aria-label / aria-labelledby is supplied
Button aria-busy when loading; internal spinner marked aria-hidden so the button speaks for itself
AnnouncementBar Action rendered as a real <button> with focus styles (was <Text onClick>); decorative icons aria-hidden

Tests + docs:

  • Component tests updated to assert the corrected a11y contract — 1735 passing (was 1707 on main).
  • apps/www props.ts updated for every new prop.
  • apps/www Accessibility sections rewritten where the previous text claimed roles / attributes that are no longer emitted.

Behavioural changes downstream callers should know about

Bumping to this version is mostly a drop-in, but a few queries / assertions need updating:

  • getByRole('listitem') no longer finds Sidebar or List items — they rely on native element semantics now.
  • getByRole('img') does not find decorative images (alt=\"\").
  • getByRole('region') no longer finds a Container unless an aria-label / aria-labelledby is supplied.
  • getByLabelText('Close Drawer') is now getByLabelText('Close') (override via the closeLabel prop if the old copy is needed).
  • Tests doing getByRole('status') to detect a Button's loading state should switch to expect(button).toHaveAttribute('aria-busy', 'true').

Test plan

  • pnpm test:apsara — 1735 / 1735 passing locally
  • pnpm --filter @raystack/apsara build — clean
  • Visual check of SidePanel.Header (now <h2> — confirm default heading margins don't shift layout)
  • Visual check of AnnouncementBar action (now <button> with reset CSS + focus ring — confirm rendering is unchanged)
  • Smoke test multi-select Combobox with a screen reader to confirm the dropped aria-multiselectable doesn't regress AT announcements

Closes #673.

Address the cross-component a11y gaps tracked in #673 with one PR. Each
change is targeted at the specific WCAG / ARIA issue called out in the
child issues — no broader refactors.

Component-level fixes
  - Tooltip:        aria-label / aria-labelledby flow to Popup for
                    ReactNode messages
  - TextArea:       aria-invalid / aria-required propagated from Field
                    context
  - Table:          Table.Head defaults scope="col"; SectionHeader uses
                    scope="colgroup"; new Table.Caption sub-component
  - Spinner:        drop conflicting aria-hidden + status combo; add
                    ariaLabel (default "Loading"); aria-hidden override
                    cleanly demotes to decorative
  - Skeleton:       aria-hidden="true" on decorative placeholder container
  - Sidebar:        remove orphan role="listitem" (items rely on native
                    element semantics)
  - SidePanel:      title renders as <h2> with generated id, new titleId
                    prop for aria-labelledby wiring
  - Drawer:         aria-label / aria-labelledby customisable; new
                    closeLabel prop (default "Close")
  - Separator:      new `decorative` prop -> role="presentation" +
                    aria-hidden
  - Select:         remove conflicting aria-multiselectable on Combobox
                    list (data-multiselectable retained for styling)
  - ScrollArea:     aria-label / aria-labelledby apply role="region" to
                    viewport
  - List:           keep explicit role="list" (Safari drops implicit role
                    when list-style:none); drop redundant role="listitem";
                    remove generic default aria-label="List"; new `level`
                    prop on List.Header (default 3, was hardcoded)
  - Link:           drop redundant role="link"; use children for aria-label
                    only when string (no more "[object Object]")
  - Label:          new requiredText + showRequiredIndicator props to
                    balance the existing (optional) indicator
  - Input:          aria-invalid / aria-required from Field context;
                    leading / trailing icon wrappers marked aria-hidden
  - Image:          drop redundant role="img" and aria-label={alt} (native
                    alt is the accessible name)
  - IconButton:     drop redundant aria-disabled; strengthen aria-label
                    guidance in JSDoc
  - Container:      no default role="region"; role applied only when an
                    aria-label / aria-labelledby is supplied
  - Button:         aria-busy when loading; internal spinner marked
                    aria-hidden so the button speaks for itself
  - AnnouncementBar: action rendered as a real <button> with focus styles
                    (was <Text onClick>); decorative icons aria-hidden

Tests + docs
  - Existing component tests updated to assert the corrected a11y
    contract (1735 passing, +28 vs. main).
  - props.ts updated for every new prop (Spinner.ariaLabel,
    Drawer.closeLabel, Separator.decorative, List.Header.level,
    Label.requiredText / showRequiredIndicator, SidePanel.Header.titleId,
    Table.Head.scope + Table.Caption, Tooltip.Content aria-label /
    aria-labelledby).
  - Accessibility sections in index.mdx rewritten where the previous
    text claimed roles / attributes that are no longer emitted.

Behavioural notes for downstream consumers
  - getByRole('listitem') will not find Sidebar items or List items.
  - getByRole('img') will not find decorative (alt="") images.
  - getByRole('region') will not find a Container without a label.
  - getByLabelText('Close Drawer') is now getByLabelText('Close')
    (override via closeLabel for older copy).
  - Test suites that depended on these strings will need a small update.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

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

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment May 13, 2026 6:10am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5fef0122-34f3-40dc-a1f6-480719fc9475

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accessibility baseline: aria attributes, roles, and semantic HTML

1 participant