Skip to content

fix: a11y baseline pass across components#805

Open
paanSinghCoder wants to merge 8 commits into
mainfrom
fix/a11y-baseline-issue-673
Open

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

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented May 13, 2026

Summary

Related issue #673

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 15, 2026 4:29am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

📝 Walkthrough

Walkthrough

This pull request establishes an accessibility baseline across the component library by systematically clarifying semantic HTML rendering, standardizing ARIA attribute patterns, adding new accessible configuration props, and updating tests. Changes span documentation updates explaining accessible behaviors, new optional props like aria-level, decorative, closeLabel, and scope, conditional landmark role application based on labeling presence, wrapping of decorative icons with aria-hidden, and refinement of ARIA state semantics (role, aria-busy, aria-live, aria-invalid, aria-required). Component tests are updated to verify these new behaviors, and a comprehensive accessibility baseline section is added to the migration guide.

Possibly related issues

  • Issue #673: Cross-component accessibility improvements directly implement the same ARIA/role/semantic element fixes described in this foundational accessibility work.
  • Issue #596: AnnouncementBar component refactoring to render action as semantic button is directly related to accessibility enhancements in that component.
  • Issue #638: Skeleton component accessibility gaps (aria-hidden, reduced-motion) are addressed in this PR.
  • Issue #631: ScrollArea conditional role="region" and aria-label/aria-labelledby forwarding directly address missing accessibility attributes.
  • Issue #601: Button component accessibility improvements (aria-busy, aria-hidden on spinner, test updates) directly implement the improvements in this PR.

Suggested reviewers

  • rsbh
  • rohanchkrabrty
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: a11y baseline pass across components' accurately summarizes the main objective of this PR, which is addressing cross-component accessibility gaps. It is clear, concise, and directly reflects the primary change (accessibility fixes across the component library).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description clearly outlines accessibility improvements across 21 components with a summary table, specific behavioral changes, test results, and downstream migration guidance directly related to the substantial changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

Document the PR #805 accessibility changes in V1-migration.md under
Cross-Cutting Changes so consumers upgrading to v1.0 see the full set
of test / CSS / i18n migration patterns alongside the per-component
notes.
Apsara components consistently expose the native `aria-label` attribute
rather than a camelCase `ariaLabel` prop (see Chip, IconButton, Tooltip,
Drawer, ScrollArea, etc.). Spinner was the lone exception in the a11y
baseline pass — drop the alias and consume `aria-label` directly with
"Loading" as the default. Docs and migration notes updated.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/raystack/components/side-panel/side-panel.tsx (1)

23-29: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The <aside> landmark should be labeled by the heading ID.

While the header now generates an ID for the <h2> title (Line 56), the <aside> element rendered by SidePanelRoot doesn't reference it via aria-labelledby. According to WCAG, landmark regions should have accessible names to help users navigate.

The SidePanelRoot and SidePanelHeader are separate components, so you'll need to:

  1. Pass the heading ID from Header up to Root (via context or composition), or
  2. Accept aria-labelledby as a prop on SidePanelRoot and require consumers to pass the heading ID

Without this connection, screen reader users cannot distinguish between multiple side panels on the same page.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/side-panel/side-panel.tsx` around lines 23 - 29,
SidePanelRoot currently renders the <aside> without an accessible name; update
SidePanelProps to accept an optional ariaLabelledBy (string) prop and set it on
the aside as aria-labelledby={ariaLabelledBy} inside SidePanelRoot, and ensure
consumers pass the heading ID generated by SidePanelHeader (or lift the heading
ID via context if preferred); reference the SidePanelRoot and SidePanelHeader
symbols and ensure the header exposes its generated id so it can be forwarded to
SidePanelRoot via the new ariaLabelledBy prop (or via shared context).
🧹 Nitpick comments (4)
packages/raystack/components/label/label.tsx (1)

43-45: ⚡ Quick win

Consider renaming or using a more generic CSS class.

The requiredText indicator reuses styles.optional, which is semantically mismatched. While this may be intentional for consistent styling, consider either:

  • Renaming the CSS class to something generic like styles.indicator or styles.labelSuffix
  • Creating a separate styles.required class if the styling should differ in the future
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/label/label.tsx` around lines 43 - 45, The JSX
uses styles.optional for the requiredText span which is semantically incorrect;
update the component (in label.tsx around the span that renders requiredText) to
use a generic or semantically appropriate class name—either rename
styles.optional to styles.indicator or styles.labelSuffix (and update the CSS
module accordingly) or add a new styles.required class in the CSS module and use
that on the span so the class name matches the intent and future styling changes
are clearer.
apps/www/src/content/docs/components/label/index.mdx (1)

57-57: 💤 Low value

Consider hyphenating compound modifier.

The phrase "WCAG compliant color contrast" should use a hyphen: "WCAG-compliant color contrast" per standard English grammar for compound modifiers.

📝 Suggested grammar fix
-- Maintains WCAG compliant color contrast ratios.
+- Maintains WCAG-compliant color contrast ratios.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/www/src/content/docs/components/label/index.mdx` at line 57, Update the
phrase "Maintains WCAG compliant color contrast ratios." to hyphenate the
compound modifier so it reads "Maintains WCAG-compliant color contrast ratios."
Locate and edit the exact string "Maintains WCAG compliant color contrast
ratios." in the label docs (the line containing that sentence) and replace it
with the hyphenated version to follow standard English grammar for compound
modifiers.
packages/raystack/components/spinner/__tests__/spinner.test.tsx (1)

10-10: ⚡ Quick win

Remove { hidden: true } from role queries for consistency.

The Spinner component no longer sets aria-hidden="true" by default (confirmed by the updated accessibility test on lines 50-55). All getByRole('status') queries should work without the { hidden: true } option for consistency with the new default behavior.

♻️ Proposed fix to update query pattern
-    const spinner = screen.getByRole('status', { hidden: true });
+    const spinner = screen.getByRole('status');

Apply this change to lines 10, 21, 27, 34, 41, 77, and 89.

Also applies to: 21-21, 27-27, 34-34, 41-41, 77-77, 89-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/spinner/__tests__/spinner.test.tsx` at line 10,
Tests in spinner.test.tsx still use getByRole('status', { hidden: true });
update all role queries to remove the hidden option so they read
getByRole('status') for consistency with the Spinner's new default (apply to the
occurrences currently using { hidden: true } - e.g., the spinner variable
assignments and other getByRole('status', { hidden: true }) calls found in this
test file).
packages/raystack/components/drawer/drawer-content.tsx (1)

41-41: ⚡ Quick win

Add closeLabel to the exported props interface for API consistency.

closeLabel is part of the component API but not part of DrawerContentProps, which can cause typing drift for consumers reusing the exported type.

Suggested fix
 export interface DrawerContentProps
   extends Omit<DrawerPrimitive.Popup.Props, 'children'>,
     VariantProps<typeof drawerPopup> {
   showCloseButton?: boolean;
   overlayProps?: DrawerPrimitive.Backdrop.Props;
   children?: ReactNode;
+  closeLabel?: string;
 }
@@
-}: DrawerContentProps & { closeLabel?: string }) {
+}: DrawerContentProps) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/drawer/drawer-content.tsx` at line 41, The
DrawerContent component accepts a closeLabel prop but the exported type
DrawerContentProps doesn't include it; update the exported DrawerContentProps
interface to add closeLabel?: string so consumers typing against
DrawerContentProps see the prop, and ensure any references to DrawerContentProps
(and the component signature where closeLabel is destructured) remain compatible
after the addition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/V1-migration.md`:
- Around line 274-289: The document contains contradictory guidance for the
Separator component: the summary table claims Separator has a new decorative
prop while the detailed “### Separator” section later says decorative was
removed—pick the correct API behavior and make both sections consistent. If the
prop was added: update the “### Separator” section to document the new
decorative prop on the Separator component, show usage examples, and state that
decorative -> role="presentation" + aria-hidden are applied; if the prop was
removed: update the summary table to remove the decorative entry and adjust any
examples. Ensure all references to Separator, decorative, role="presentation",
and aria-hidden are aligned and update the migration notes and examples
accordingly.

In `@packages/raystack/components/announcement-bar/announcement-bar.module.css`:
- Line 56: Change the CSS keyword value to lowercase to satisfy the stylelint
value-keyword-case rule: in announcement-bar.module.css replace the outline
property value "currentColor" with "currentcolor" (the rule applies where
outline: 2px solid currentColor; is defined) so the keyword is all lowercase and
the lint violation is resolved.

In `@packages/raystack/components/announcement-bar/announcement-bar.tsx`:
- Around line 58-69: The action button currently renders when actionIcon exists
even if actionLabel is missing, creating an unlabeled button; update the render
condition in the AnnouncementBar component so the button is only rendered when
actionLabel is present (require actionLabel truthy) and remove/avoid rendering
an icon-only button; locate the JSX that checks {actionLabel || actionIcon ?
(...) : null} and change it to require actionLabel (use actionLabel) for
rendering the <button> (keeping onActionClick, styles['action-btn'], actionIcon
handling inside) so the button always has an accessible label.

In `@packages/raystack/components/separator/separator.tsx`:
- Around line 44-56: The decorative semantics can be overridden because props
are spread after decorativeProps; in the Separator component ensure
decorativeProps win by applying them after consumer props (or explicitly set
role and aria-hidden on the final props sent to SeparatorPrimitive). Update the
render so SeparatorPrimitive receives {...props} first and then decorativeProps
(or assign role:'presentation' and aria-hidden:true after spreading props) so
that when decorative is true the role/aria-orientation/aria-hidden cannot be
replaced by incoming props.

In `@packages/raystack/components/spinner/spinner.tsx`:
- Around line 48-55: The component's computed accessibility attributes (role,
aria-label, aria-live, aria-hidden) are being overridden by user-supplied props
because {...props} is spread last; to make decorative mode authoritative, spread
{...props} before the computed attributes in the Spinner component (the JSX
block that builds the <div className={spinner({ size, color, className })}
...>), or explicitly derive final attributes by taking props but forcing
role/aria-label/aria-live/aria-hidden from the computed values when isDecorative
is true so passed props cannot override them.

---

Outside diff comments:
In `@packages/raystack/components/side-panel/side-panel.tsx`:
- Around line 23-29: SidePanelRoot currently renders the <aside> without an
accessible name; update SidePanelProps to accept an optional ariaLabelledBy
(string) prop and set it on the aside as aria-labelledby={ariaLabelledBy} inside
SidePanelRoot, and ensure consumers pass the heading ID generated by
SidePanelHeader (or lift the heading ID via context if preferred); reference the
SidePanelRoot and SidePanelHeader symbols and ensure the header exposes its
generated id so it can be forwarded to SidePanelRoot via the new ariaLabelledBy
prop (or via shared context).

---

Nitpick comments:
In `@apps/www/src/content/docs/components/label/index.mdx`:
- Line 57: Update the phrase "Maintains WCAG compliant color contrast ratios."
to hyphenate the compound modifier so it reads "Maintains WCAG-compliant color
contrast ratios." Locate and edit the exact string "Maintains WCAG compliant
color contrast ratios." in the label docs (the line containing that sentence)
and replace it with the hyphenated version to follow standard English grammar
for compound modifiers.

In `@packages/raystack/components/drawer/drawer-content.tsx`:
- Line 41: The DrawerContent component accepts a closeLabel prop but the
exported type DrawerContentProps doesn't include it; update the exported
DrawerContentProps interface to add closeLabel?: string so consumers typing
against DrawerContentProps see the prop, and ensure any references to
DrawerContentProps (and the component signature where closeLabel is
destructured) remain compatible after the addition.

In `@packages/raystack/components/label/label.tsx`:
- Around line 43-45: The JSX uses styles.optional for the requiredText span
which is semantically incorrect; update the component (in label.tsx around the
span that renders requiredText) to use a generic or semantically appropriate
class name—either rename styles.optional to styles.indicator or
styles.labelSuffix (and update the CSS module accordingly) or add a new
styles.required class in the CSS module and use that on the span so the class
name matches the intent and future styling changes are clearer.

In `@packages/raystack/components/spinner/__tests__/spinner.test.tsx`:
- Line 10: Tests in spinner.test.tsx still use getByRole('status', { hidden:
true }); update all role queries to remove the hidden option so they read
getByRole('status') for consistency with the Spinner's new default (apply to the
occurrences currently using { hidden: true } - e.g., the spinner variable
assignments and other getByRole('status', { hidden: true }) calls found in this
test file).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c941fa5c-3756-4e3e-9aa9-75fde3e687e1

📥 Commits

Reviewing files that changed from the base of the PR and between 8ccfc9a and 5288593.

📒 Files selected for processing (73)
  • apps/www/src/content/docs/components/announcement-bar/index.mdx
  • apps/www/src/content/docs/components/button/index.mdx
  • apps/www/src/content/docs/components/container/index.mdx
  • apps/www/src/content/docs/components/drawer/index.mdx
  • apps/www/src/content/docs/components/drawer/props.ts
  • apps/www/src/content/docs/components/image/index.mdx
  • apps/www/src/content/docs/components/label/index.mdx
  • apps/www/src/content/docs/components/label/props.ts
  • apps/www/src/content/docs/components/link/index.mdx
  • apps/www/src/content/docs/components/list/index.mdx
  • apps/www/src/content/docs/components/list/props.ts
  • apps/www/src/content/docs/components/scroll-area/index.mdx
  • apps/www/src/content/docs/components/separator/index.mdx
  • apps/www/src/content/docs/components/separator/props.ts
  • apps/www/src/content/docs/components/sidepanel/index.mdx
  • apps/www/src/content/docs/components/sidepanel/props.ts
  • apps/www/src/content/docs/components/skeleton/index.mdx
  • apps/www/src/content/docs/components/spinner/index.mdx
  • apps/www/src/content/docs/components/spinner/props.ts
  • apps/www/src/content/docs/components/table/index.mdx
  • apps/www/src/content/docs/components/table/props.ts
  • apps/www/src/content/docs/components/tooltip/index.mdx
  • apps/www/src/content/docs/components/tooltip/props.ts
  • docs/V1-migration.md
  • packages/raystack/components/announcement-bar/__tests__/announcement-bar.test.tsx
  • packages/raystack/components/announcement-bar/announcement-bar.module.css
  • packages/raystack/components/announcement-bar/announcement-bar.tsx
  • packages/raystack/components/announcement-bar/index.tsx
  • packages/raystack/components/button/__tests__/button.test.tsx
  • packages/raystack/components/button/button.module.css
  • packages/raystack/components/button/button.tsx
  • packages/raystack/components/button/index.tsx
  • packages/raystack/components/container/__tests__/container.test.tsx
  • packages/raystack/components/container/container.module.css
  • packages/raystack/components/container/container.tsx
  • packages/raystack/components/container/index.tsx
  • packages/raystack/components/drawer/__tests__/drawer.test.tsx
  • packages/raystack/components/drawer/drawer-content.tsx
  • packages/raystack/components/icon-button/__tests__/icon-button.test.tsx
  • packages/raystack/components/icon-button/icon-button.module.css
  • packages/raystack/components/icon-button/icon-button.tsx
  • packages/raystack/components/icon-button/index.tsx
  • packages/raystack/components/image/__tests__/image.test.tsx
  • packages/raystack/components/image/image.module.css
  • packages/raystack/components/image/image.tsx
  • packages/raystack/components/image/index.tsx
  • packages/raystack/components/input/input.tsx
  • packages/raystack/components/label/label.tsx
  • packages/raystack/components/link/__tests__/link.test.tsx
  • packages/raystack/components/link/index.tsx
  • packages/raystack/components/link/link.tsx
  • packages/raystack/components/list/__tests__/list.test.tsx
  • packages/raystack/components/list/index.tsx
  • packages/raystack/components/list/list.tsx
  • packages/raystack/components/scroll-area/scroll-area.tsx
  • packages/raystack/components/select/index.ts
  • packages/raystack/components/select/select-content.tsx
  • packages/raystack/components/separator/index.tsx
  • packages/raystack/components/separator/separator.module.css
  • packages/raystack/components/separator/separator.tsx
  • packages/raystack/components/side-panel/index.tsx
  • packages/raystack/components/side-panel/side-panel.tsx
  • packages/raystack/components/skeleton/index.ts
  • packages/raystack/components/skeleton/skeleton.module.css
  • packages/raystack/components/skeleton/skeleton.tsx
  • packages/raystack/components/spinner/__tests__/spinner.test.tsx
  • packages/raystack/components/spinner/index.tsx
  • packages/raystack/components/spinner/spinner.tsx
  • packages/raystack/components/table/index.ts
  • packages/raystack/components/table/table.tsx
  • packages/raystack/components/text-area/text-area.tsx
  • packages/raystack/components/tooltip/tooltip-content.tsx
  • packages/raystack/components/tooltip/tooltip.module.css
💤 Files with no reviewable changes (1)
  • packages/raystack/components/image/image.tsx

Comment thread docs/V1-migration.md Outdated
}

.action-btn:focus-visible {
outline: 2px solid currentColor;
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix Stylelint keyword-case violation on Line 56.

currentColor should be currentcolor to satisfy the configured value-keyword-case rule.

Proposed fix
-.action-btn:focus-visible {
-  outline: 2px solid currentColor;
+.action-btn:focus-visible {
+  outline: 2px solid currentcolor;
   outline-offset: 2px;
   border-radius: var(--rs-radius-1);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
outline: 2px solid currentColor;
.action-btn:focus-visible {
outline: 2px solid currentcolor;
outline-offset: 2px;
border-radius: var(--rs-radius-1);
}
🧰 Tools
🪛 Stylelint (17.11.0)

[error] 56-56: Expected "currentColor" to be "currentcolor" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/announcement-bar/announcement-bar.module.css` at
line 56, Change the CSS keyword value to lowercase to satisfy the stylelint
value-keyword-case rule: in announcement-bar.module.css replace the outline
property value "currentColor" with "currentcolor" (the rule applies where
outline: 2px solid currentColor; is defined) so the keyword is all lowercase and
the lint violation is resolved.

Comment on lines 58 to 69
{actionLabel || actionIcon ? (
<Text
<button
type='button'
className={styles['action-btn']}
size='small'
weight='medium'
onClick={onActionClick}
>
{actionLabel}
{actionIcon}
</Text>
<Text size='small' weight='medium'>
{actionLabel}
</Text>
{actionIcon && <span aria-hidden='true'>{actionIcon}</span>}
</button>
) : null}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent rendering an unlabeled action button.

The current condition allows icon-only actions, but the icon is hidden from AT and no accessible name is provided for the <button>.

Suggested fix (require label for button render)
-      {actionLabel || actionIcon ? (
+      {actionLabel ? (
         <button
           type='button'
           className={styles['action-btn']}
           onClick={onActionClick}
         >
           <Text size='small' weight='medium'>
             {actionLabel}
           </Text>
           {actionIcon && <span aria-hidden='true'>{actionIcon}</span>}
         </button>
       ) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/announcement-bar/announcement-bar.tsx` around
lines 58 - 69, The action button currently renders when actionIcon exists even
if actionLabel is missing, creating an unlabeled button; update the render
condition in the AnnouncementBar component so the button is only rendered when
actionLabel is present (require actionLabel truthy) and remove/avoid rendering
an icon-only button; locate the JSX that checks {actionLabel || actionIcon ?
(...) : null} and change it to require actionLabel (use actionLabel) for
rendering the <button> (keeping onActionClick, styles['action-btn'], actionIcon
handling inside) so the button always has an accessible label.

Comment on lines +44 to 56
const decorativeProps = decorative
? {
role: 'presentation',
'aria-hidden': true,
'aria-orientation': undefined
}
: {};
return (
<SeparatorPrimitive
orientation={orientation}
className={separator({ size, color, className })}
{...decorativeProps}
{...props}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ensure decorative semantics cannot be overridden by passthrough ARIA props.

When decorative is true, spreading ...props after decorativeProps lets consumers reintroduce conflicting role/aria-* values.

Suggested fix
     <SeparatorPrimitive
       orientation={orientation}
       className={separator({ size, color, className })}
-      {...decorativeProps}
       {...props}
+      {...decorativeProps}
     />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const decorativeProps = decorative
? {
role: 'presentation',
'aria-hidden': true,
'aria-orientation': undefined
}
: {};
return (
<SeparatorPrimitive
orientation={orientation}
className={separator({ size, color, className })}
{...decorativeProps}
{...props}
return (
<SeparatorPrimitive
orientation={orientation}
className={separator({ size, color, className })}
{...props}
{...decorativeProps}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/separator/separator.tsx` around lines 44 - 56,
The decorative semantics can be overridden because props are spread after
decorativeProps; in the Separator component ensure decorativeProps win by
applying them after consumer props (or explicitly set role and aria-hidden on
the final props sent to SeparatorPrimitive). Update the render so
SeparatorPrimitive receives {...props} first and then decorativeProps (or assign
role:'presentation' and aria-hidden:true after spreading props) so that when
decorative is true the role/aria-orientation/aria-hidden cannot be replaced by
incoming props.

Comment on lines 48 to 55
<div
className={spinner({ size, color, className })}
role='status'
aria-hidden='true'
role={isDecorative ? undefined : 'status'}
aria-label={isDecorative ? undefined : ariaLabel}
aria-live={isDecorative ? undefined : 'polite'}
aria-hidden={isDecorative || undefined}
{...props}
>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep decorative mode authoritative when aria-hidden is set.

...props currently comes after computed accessibility props, so a passed role/aria-live can override decorative behavior.

Suggested fix
     <div
+      {...props}
       className={spinner({ size, color, className })}
       role={isDecorative ? undefined : 'status'}
       aria-label={isDecorative ? undefined : ariaLabel}
       aria-live={isDecorative ? undefined : 'polite'}
       aria-hidden={isDecorative || undefined}
-      {...props}
     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
className={spinner({ size, color, className })}
role='status'
aria-hidden='true'
role={isDecorative ? undefined : 'status'}
aria-label={isDecorative ? undefined : ariaLabel}
aria-live={isDecorative ? undefined : 'polite'}
aria-hidden={isDecorative || undefined}
{...props}
>
<div
{...props}
className={spinner({ size, color, className })}
role={isDecorative ? undefined : 'status'}
aria-label={isDecorative ? undefined : ariaLabel}
aria-live={isDecorative ? undefined : 'polite'}
aria-hidden={isDecorative || undefined}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/spinner/spinner.tsx` around lines 48 - 55, The
component's computed accessibility attributes (role, aria-label, aria-live,
aria-hidden) are being overridden by user-supplied props because {...props} is
spread last; to make decorative mode authoritative, spread {...props} before the
computed attributes in the Spinner component (the JSX block that builds the <div
className={spinner({ size, color, className })} ...>), or explicitly derive
final attributes by taking props but forcing
role/aria-label/aria-live/aria-hidden from the computed values when isDecorative
is true so passed props cannot override them.

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