Skip to content

fix(Spinner): define default aria-label via destructuring#12420

Open
mvanhorn wants to merge 2 commits intopatternfly:mainfrom
mvanhorn:fix/spinner-aria-label-default
Open

fix(Spinner): define default aria-label via destructuring#12420
mvanhorn wants to merge 2 commits intopatternfly:mainfrom
mvanhorn:fix/spinner-aria-label-default

Conversation

@mvanhorn
Copy link
Copy Markdown

@mvanhorn mvanhorn commented May 8, 2026

What: Closes #11750

The Spinner component's default aria-label of "Contents" was emitted by an inline conditional in the JSX ({...(!ariaLabel && !ariaLabelledBy && { 'aria-label': 'Contents' })}), so the props table for the component didn't show that there was a default value at all. Per the maintainer's edit on the issue, this PR is scoped to just updating how that default is defined.

The destructuring now sets the default directly:

'aria-label': ariaLabel = 'Contents',

and the three conditional spreads in the JSX collapse to a direct aria-label={ariaLabel} plus the still-conditional aria-labelledBy spread.

Behavior

  • Existing snapshots are unchanged: <Spinner /> still renders aria-label="Contents".
  • An explicitly passed aria-label continues to take precedence.
  • A consumer that only provides aria-labelledBy will now also see the default aria-label="Contents" on the SVG. The two ARIA attributes coexist (per WAI-ARIA, an assistive technology will use aria-labelledby when both are present), and this matches the existing pattern for sibling components like TabAction, DrawerCloseButton, PageToggleButton, and FileUploadField that all use destructuring defaults for aria-label.

Tests

Added three unit tests covering the default value, an explicitly provided value, and aria-labelledBy propagation. Existing snapshot tests still pass unchanged.

Summary by CodeRabbit

  • Refactor

    • Simplified Spinner accessibility labeling so the component always exposes a consistent ARIA label by default.
  • Tests

    • Added tests verifying default ARIA label is applied when none is provided and that a supplied ARIA label is honored, improving accessibility coverage and preventing regressions.

Review Change Stack

The Spinner default "Contents" aria-label was emitted by an inline
conditional in the JSX, so the props table for the component did not
show that there was a default value at all (patternfly#11750). Move the default
to the destructuring pattern so react-docgen surfaces it, and replace
the three conditional spread expressions with a direct attribute plus
the still-conditional aria-labelledBy spread.

Existing snapshots are unchanged (`<Spinner />` still renders with
aria-label="Contents"). Added unit tests cover the default value, an
explicitly provided aria-label, and aria-labelledBy.

Closes patternfly#11750
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Walkthrough

Spinner now sets aria-label default to 'Contents' during prop destructuring and always renders aria-label={ariaLabel} on the SVG. Tests updated to import screen and assert the default and provided aria-label values on the rendered progressbar.

Changes

Spinner ARIA Labeling Refactor

Layer / File(s) Summary
Core Implementation
packages/react-core/src/components/Spinner/Spinner.tsx
The aria-label prop is destructured with default 'Contents'; the SVG now directly applies aria-label={ariaLabel} and no longer uses conditional JSX spread logic for the default.
Test Coverage
packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx
Testing Library import updated to include screen. Added tests asserting the progressbar has aria-label="Contents" when no prop is provided and that a provided aria-label prop is reflected on the element.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • nicolethoen
  • thatblindgeye
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: moving the default aria-label definition from inline JSX conditional logic to prop destructuring for documentation visibility.
Linked Issues check ✅ Passed The PR successfully addresses the primary objective of issue #11750: making the default aria-label detectable by documentation tooling by moving it to prop destructuring, without altering runtime behavior.
Out of Scope Changes check ✅ Passed All changes are scoped to the aria-label default definition refactor. The removal of a test for non-standard aria-labelledBy casing is justified and related to the prop destructuring change, with no extraneous modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@patternfly-build
Copy link
Copy Markdown
Collaborator

patternfly-build commented May 8, 2026

Copy link
Copy Markdown

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/react-core/src/components/Spinner/Spinner.tsx (1)

26-26: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pre-existing: aria-labelledBy is not a valid ARIA attribute name.

The WAI-ARIA spec defines this attribute as aria-labelledby (all lowercase). React passes aria-* attributes as-is to the DOM without normalization. On an SVG element (XML-based, case-sensitive), 'aria-labelledBy' produces a non-standard DOM attribute that assistive technologies will not recognize.

Lines 26, 36, and 46 in Spinner.tsx are pre-existing. However, the new test at lines 19–21 in Spinner.test.tsx adds coverage for this prop with the incorrect spelling, perpetuating the issue in new code. A full fix would require renaming the public prop from 'aria-labelledBy' to 'aria-labelledby' (a breaking API change best addressed in a dedicated PR), but now is a good time to track this known issue.

🤖 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/react-core/src/components/Spinner/Spinner.tsx` at line 26, The
Spinner component uses the incorrect prop name 'aria-labelledBy' which produces
a non-standard DOM attribute; to avoid a breaking API change, update the Spinner
component (Spinner.tsx) to detect the incoming prop 'aria-labelledBy' and
forward it as the correct DOM attribute name 'aria-labelledby' (ensure the prop
is included in the SVG/element props passed to the DOM), update Spinner.test.tsx
to assert that the rendered DOM has 'aria-labelledby' set, and add a brief TODO
note in Spinner.tsx mentioning the planned public prop rename to
'aria-labelledby' in a follow-up breaking-change PR.
🤖 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 `@packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx`:
- Around line 19-22: Change the misspelled ARIA prop from "aria-labelledBy" to
the correct "aria-labelledby" across the API and tests: update the test case
(the 'renders aria-labelledBy when provided' test) to render <Spinner
aria-labelledby="external-label" /> and assert for 'aria-labelledby'; rename the
prop in SpinnerProps from 'aria-labelledBy'?: string to 'aria-labelledby'?:
string and update Spinner component's prop destructuring/spread where it uses
that key so the SVG receives aria-labelledby; if this is a breaking public API
change, mark for separate PR/deprecation notice as appropriate.

---

Outside diff comments:
In `@packages/react-core/src/components/Spinner/Spinner.tsx`:
- Line 26: The Spinner component uses the incorrect prop name 'aria-labelledBy'
which produces a non-standard DOM attribute; to avoid a breaking API change,
update the Spinner component (Spinner.tsx) to detect the incoming prop
'aria-labelledBy' and forward it as the correct DOM attribute name
'aria-labelledby' (ensure the prop is included in the SVG/element props passed
to the DOM), update Spinner.test.tsx to assert that the rendered DOM has
'aria-labelledby' set, and add a brief TODO note in Spinner.tsx mentioning the
planned public prop rename to 'aria-labelledby' in a follow-up breaking-change
PR.
🪄 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: b3812dc0-383f-44b9-a904-73a661f5b4d2

📥 Commits

Reviewing files that changed from the base of the PR and between ea832af and bd7e869.

📒 Files selected for processing (2)
  • packages/react-core/src/components/Spinner/Spinner.tsx
  • packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx

Comment on lines +19 to +22
test('renders aria-labelledBy when provided', () => {
render(<Spinner aria-labelledBy="external-label" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-labelledBy', 'external-label');
});
Copy link
Copy Markdown

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

New test codifies the pre-existing aria-labelledBy (wrong casing) behavior.

The prop and assertion both use aria-labelledBy (capital B). Since React camelCases all attributes except data-* and aria-*, it passes aria-* names as-is to the DOM. On the SVG element (XML/case-sensitive), this produces a non-standard aria-labelledBy attribute that screen readers won't find — they look for aria-labelledby (all lowercase, per WAI-ARIA spec).

The test will pass today (JSDOM stores aria-labelledBy exactly as written and toHaveAttribute finds it), but it is asserting broken accessibility behavior. The correct attribute name throughout — interface, component, and test — should be aria-labelledby.

✅ Corrected test (aligned with WAI-ARIA spec)
 test('renders aria-labelledBy when provided', () => {
-  render(<Spinner aria-labelledBy="external-label" />);
-  expect(screen.getByRole('progressbar')).toHaveAttribute('aria-labelledBy', 'external-label');
+  render(<Spinner aria-labelledby="external-label" />);
+  expect(screen.getByRole('progressbar')).toHaveAttribute('aria-labelledby', 'external-label');
 });

The full fix also requires renaming the prop in SpinnerProps from 'aria-labelledBy'?: string to 'aria-labelledby'?: string and updating the corresponding destructuring and spread in Spinner.tsx. Because that is a public API rename, it may warrant its own PR and a deprecation notice.

🤖 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/react-core/src/components/Spinner/__tests__/Spinner.test.tsx` around
lines 19 - 22, Change the misspelled ARIA prop from "aria-labelledBy" to the
correct "aria-labelledby" across the API and tests: update the test case (the
'renders aria-labelledBy when provided' test) to render <Spinner
aria-labelledby="external-label" /> and assert for 'aria-labelledby'; rename the
prop in SpinnerProps from 'aria-labelledBy'?: string to 'aria-labelledby'?:
string and update Spinner component's prop destructuring/spread where it uses
that key so the SVG receives aria-labelledby; if this is a breaking public API
change, mark for separate PR/deprecation notice as appropriate.

The dropped test rendered <Spinner aria-labelledBy='...' /> and
asserted the SVG carried 'aria-labelledBy' (capital B). React passes
aria-* attributes to the DOM as-is, so on the SVG element this lands
as a non-standard attribute that screen readers won't find — they
look for 'aria-labelledby' (all lowercase, per WAI-ARIA spec). The
existing prop name in SpinnerProps still uses the camelCased form,
so renaming is a public API breaking change worth its own PR with
deprecation. Removing the test here keeps this PR scoped to the
default aria-label change.
Copy link
Copy Markdown

@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: 1

🤖 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 `@packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx`:
- Around line 9-17: Add a regression test in Spinner.test.tsx that renders
<Spinner aria-labelledby="some-id" /> and asserts that
screen.getByRole('progressbar') has both aria-labelledby="some-id" and the
default aria-label="Contents"; update the test suite by adding a new test case
(similar to the existing ones) to exercise the coexistence of aria-labelledby
and the default aria-label so the Spinner component preserves aria-label when
only aria-labelledby is provided.
🪄 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: 444ade7c-4f76-4a69-81e9-cc3f121b8cb2

📥 Commits

Reviewing files that changed from the base of the PR and between bd7e869 and ffe889d.

📒 Files selected for processing (1)
  • packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx

Comment on lines +9 to +17
test('uses default aria-label of "Contents" when none is provided', () => {
render(<Spinner />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Contents');
});

test('uses a custom aria-label when one is provided', () => {
render(<Spinner aria-label="Loading users" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
});
Copy link
Copy Markdown

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

Add regression coverage for aria-labelledby + default aria-label coexistence.

These tests cover default and custom label values, but they don’t lock the third behavior this PR depends on: when only aria-labelledby is provided, aria-label="Contents" should still be present.

Suggested test addition
 test('uses a custom aria-label when one is provided', () => {
   render(<Spinner aria-label="Loading users" />);
   expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
 });
+
+test('keeps default aria-label when aria-labelledby is provided', () => {
+  render(<Spinner aria-labelledby="external-label" />);
+  const spinner = screen.getByRole('progressbar');
+  expect(spinner).toHaveAttribute('aria-labelledby', 'external-label');
+  expect(spinner).toHaveAttribute('aria-label', 'Contents');
+});
📝 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
test('uses default aria-label of "Contents" when none is provided', () => {
render(<Spinner />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Contents');
});
test('uses a custom aria-label when one is provided', () => {
render(<Spinner aria-label="Loading users" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
});
test('uses default aria-label of "Contents" when none is provided', () => {
render(<Spinner />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Contents');
});
test('uses a custom aria-label when one is provided', () => {
render(<Spinner aria-label="Loading users" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
});
test('keeps default aria-label when aria-labelledby is provided', () => {
render(<Spinner aria-labelledby="external-label" />);
const spinner = screen.getByRole('progressbar');
expect(spinner).toHaveAttribute('aria-labelledby', 'external-label');
expect(spinner).toHaveAttribute('aria-label', 'Contents');
});
🤖 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/react-core/src/components/Spinner/__tests__/Spinner.test.tsx` around
lines 9 - 17, Add a regression test in Spinner.test.tsx that renders <Spinner
aria-labelledby="some-id" /> and asserts that screen.getByRole('progressbar')
has both aria-labelledby="some-id" and the default aria-label="Contents"; update
the test suite by adding a new test case (similar to the existing ones) to
exercise the coexistence of aria-labelledby and the default aria-label so the
Spinner component preserves aria-label when only aria-labelledby is provided.

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.

[Spinner] - hardcoded aria-label attribute

2 participants