Skip to content

fix(action-card): render a button for proper accessibility#2625

Open
spike-rabbit wants to merge 1 commit into
siemens:mainfrom
spike-rabbit:fix/action-card/render-as-button
Open

fix(action-card): render a button for proper accessibility#2625
spike-rabbit wants to merge 1 commit into
siemens:mainfrom
spike-rabbit:fix/action-card/render-as-button

Conversation

@spike-rabbit

@spike-rabbit spike-rabbit commented Jun 29, 2026

Copy link
Copy Markdown
Member

Description

Wraps the inner ix-card of ix-action-card in a button element so the card is keyboard-focusable and exposed as a button to assistive technologies.

User-facing impact

ix-action-card is now properly accessible: it is focusable and announced as a button.

Affected packages

  • @siemens/ix (patch)

Validation

  • Changeset added (@siemens/ix patch)

Release impact

Patch — accessibility bug fix, no breaking change.

Summary by CodeRabbit

  • Bug Fixes
    • Improved accessibility for ix-action-card by rendering the card as a native <button> with focus-visible styling.
    • Ensured the entire card area is the interactive target and added appropriate button semantics (aria-label/aria-labelledby).
    • When passive is set, the card’s button is disabled.
  • Tests
    • Added Playwright regression coverage and Axe accessibility scans for both default and passive variants.
  • Chores
    • Published a patch release note for ix-action-card accessibility improvements.

@netlify

netlify Bot commented Jun 29, 2026

Copy link
Copy Markdown

Deploy Preview for ix-storybook ready!

Name Link
🔨 Latest commit 8ea02ce
🔍 Latest deploy log https://app.netlify.com/projects/ix-storybook/deploys/6a423b91d579bc0008673a03
😎 Deploy Preview https://deploy-preview-2625--ix-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@spike-rabbit, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 08ecdee5-94fc-4982-80cd-419448da2a36

📥 Commits

Reviewing files that changed from the base of the PR and between 8e5afe0 and 8ea02ce.

📒 Files selected for processing (4)
  • .changeset/pink-sloths-drop.md
  • packages/core/src/components/action-card/action-card.scss
  • packages/core/src/components/action-card/action-card.tsx
  • packages/core/src/components/action-card/test/action-card.ct.ts
📝 Walkthrough

Walkthrough

ix-action-card now renders inside a native <button> with matching reset and focus styles. The patch note records the accessibility change, and new regression and Axe tests cover default and passive states.

Changes

Action Card Accessibility Fix

Layer / File(s) Summary
Button wrapper render and styles
packages/core/src/components/action-card/action-card.tsx, packages/core/src/components/action-card/action-card.scss, .changeset/pink-sloths-drop.md
render() wraps ix-card in a native <button>, removes the conditional role prop, updates pointer class handling, and SCSS adds button reset styling plus a focus-visible outline. The changeset records a patch release note for the accessibility fix.
Regression and accessibility tests
packages/core/src/components/action-card/test/action-card.ct.ts
New Playwright tests assert hydration, visible button rendering, disabled passive behavior, and zero Axe violations in default and passive states.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • danielleroux

Poem

🐇 A card now hops in button guise,
With focus rings that meet the eyes.
Passive paws can still sit still,
And Axe says “clean” — a lovely thrill!

🚥 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 clearly matches the main change: making action-card render a button for accessibility.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request improves the accessibility of the ix-action-card component by wrapping its content in a button element to make it keyboard-focusable. The feedback highlights critical improvements to this implementation: explicitly setting type='button' to prevent accidental form submissions, conditionally rendering the button wrapper only when the card is active (not passive), and resetting default browser styles on the button to avoid layout issues. Additionally, in accordance with the repository style guide, the reviewer requests adding an axe-based component test to verify the accessibility changes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/core/src/components/action-card/action-card.tsx
Comment thread packages/core/src/components/action-card/action-card.scss
Comment thread packages/core/src/components/action-card/action-card.tsx

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 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/core/src/components/action-card/action-card.scss`:
- Around line 25-35: The `button` wrapper in `action-card.scss` is now the
containing box for `ix-card`, but it does not inherit the host size, so the card
can shrink to content instead of filling the available space. Update the
`button` rule in `action-card.scss` to size the wrapper itself to the full
container dimensions, keeping the existing `button` and `ix-card` structure
intact and preserving the current focus styles.

In `@packages/core/src/components/action-card/action-card.tsx`:
- Around line 78-121: The new native button wrapper in ActionCard still renders
an unrestricted slot through ix-card-content, which can place arbitrary consumer
controls inside a button and create invalid nested interactive content. Update
ActionCard’s render structure so the slot is either constrained to
non-interactive content or moved outside the native button, and verify the
aria-label/aria-labelledby behavior in action-card.tsx still works with ix-card,
ix-icon, and ix-typography.
- Around line 78-85: The accessible name is currently applied to the inner
ix-card instead of the focusable button in action-card, which leaves the
interactive control unnamed. Move aria-label and/or aria-labelledby from ix-card
onto the wrapping button in action-card.tsx, and keep ix-card purely
presentational so ariaLabelCard still names the actual button.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 231c4dac-615f-4705-aee5-02d23323b42e

📥 Commits

Reviewing files that changed from the base of the PR and between d9fe735 and 589663b.

📒 Files selected for processing (3)
  • .changeset/pink-sloths-drop.md
  • packages/core/src/components/action-card/action-card.scss
  • packages/core/src/components/action-card/action-card.tsx

Comment thread packages/core/src/components/action-card/action-card.tsx Outdated
Comment thread packages/core/src/components/action-card/action-card.tsx Outdated
@spike-rabbit spike-rabbit force-pushed the fix/action-card/render-as-button branch from 589663b to 3dcbc1d Compare June 29, 2026 09:04
@changeset-bot

changeset-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8ea02ce

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@siemens/ix Patch
@siemens/ix-angular Patch
@siemens/ix-docs Patch
@siemens/ix-react Patch
@siemens/ix-vue Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/core/src/components/action-card/action-card.tsx (1)

90-120: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The native <button> still wraps unrestricted slotted content.

<slot> can project links, buttons, or inputs into this subtree, which creates invalid interactive descendants inside a native button and breaks keyboard/assistive-technology behavior. This needs a slot contract change or a different DOM structure before shipping.

As per path instructions, prioritize accessibility regressions in packages/core/** changes.

🤖 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/core/src/components/action-card/action-card.tsx` around lines 90 -
120, The ActionCard content currently renders unrestricted slotted children
inside the native button structure in action-card.tsx, which can create invalid
interactive descendants. Update the ActionCard DOM/slot contract so the slot no
longer lives inside the clickable <button> subtree, or otherwise restrict the
slot to non-interactive content; use the ActionCard render path and its
heading/subheading/content structure to relocate or refactor the slot while
preserving accessibility.

Source: Path instructions

🤖 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/core/src/components/action-card/action-card.scss`:
- Around line 25-39: The ActionCard still shows a pointer cursor on
passive/disabled states because the host-level cursor styling is not overridden
by the new button rule. Update the action-card styling so the disabled/passive
state explicitly resets the cursor on the button (or host) when the card is
non-interactive, using the existing ActionCard/`:host`/`button` selectors and
the `[disabled]` state to ensure it no longer advertises clickability.

---

Duplicate comments:
In `@packages/core/src/components/action-card/action-card.tsx`:
- Around line 90-120: The ActionCard content currently renders unrestricted
slotted children inside the native button structure in action-card.tsx, which
can create invalid interactive descendants. Update the ActionCard DOM/slot
contract so the slot no longer lives inside the clickable <button> subtree, or
otherwise restrict the slot to non-interactive content; use the ActionCard
render path and its heading/subheading/content structure to relocate or refactor
the slot while preserving accessibility.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 76ba6c5b-887c-43c0-925a-ef9c4d453aec

📥 Commits

Reviewing files that changed from the base of the PR and between 589663b and 3dcbc1d.

📒 Files selected for processing (4)
  • .changeset/pink-sloths-drop.md
  • packages/core/src/components/action-card/action-card.scss
  • packages/core/src/components/action-card/action-card.tsx
  • packages/core/src/components/action-card/test/action-card.ct.ts

Comment thread packages/core/src/components/action-card/action-card.scss
@spike-rabbit spike-rabbit force-pushed the fix/action-card/render-as-button branch 2 times, most recently from 8e5afe0 to e0d7c2a Compare June 29, 2026 09:13

@coderabbitai coderabbitai 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.

♻️ Duplicate comments (2)
packages/core/src/components/action-card/action-card.tsx (1)

81-127: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The native button still accepts arbitrary slotted interactive content.

The new note documents the restriction, but this render path still allows consumers to project links, buttons, or inputs into the <slot>, which creates invalid descendants of a native <button> and can break keyboard/assistive-technology behavior. Please either move the slot outside the button or enforce a non-interactive slot contract before shipping this as a patch. As per path instructions, "Prioritize correctness, regressions, accessibility, release impact, and missing validation."

🤖 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/core/src/components/action-card/action-card.tsx` around lines 81 -
127, The action-card render path still places the default <slot> inside a native
<button>, so arbitrary slotted interactive descendants remain allowed and can
create invalid nested controls. Update the ActionCardComponent render logic to
either move the <slot> outside the button or otherwise prevent interactive
projected content from being rendered inside the button, while keeping the
existing ix-card, ix-typography, and icon structure intact.

Source: Path instructions

packages/core/src/components/action-card/action-card.scss (1)

25-39: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Passive cards still advertise clickability.

Line 18 keeps cursor: pointer, and the new button rule never overrides it for [disabled], so passive cards still show a pointer cursor.

Suggested fix
   button {
     width: 100%;
     height: 100%;
     display: block;
     background: transparent;
     padding: 0;
     border: 0;
     border-radius: var(--theme-btn--border-radius);
     box-shadow: initial;
 
+    &:disabled {
+      cursor: default;
+    }
+
     &:focus-visible {
       outline: 1px solid var(--theme-color-focus-bdr);
       outline-offset: var(--theme-btn--focus--outline-offset);
     }
   }
🤖 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/core/src/components/action-card/action-card.scss` around lines 25 -
39, The `action-card` styles keep the pointer cursor on the wrapper even when
the inner `button` is disabled, so passive cards still look clickable. Update
the `button` rule in `action-card.scss` to override the inherited cursor for the
disabled state, ideally targeting the disabled button state within the same
`button` selector so the card no longer advertises clickability when it cannot
be interacted with.
🤖 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.

Duplicate comments:
In `@packages/core/src/components/action-card/action-card.scss`:
- Around line 25-39: The `action-card` styles keep the pointer cursor on the
wrapper even when the inner `button` is disabled, so passive cards still look
clickable. Update the `button` rule in `action-card.scss` to override the
inherited cursor for the disabled state, ideally targeting the disabled button
state within the same `button` selector so the card no longer advertises
clickability when it cannot be interacted with.

In `@packages/core/src/components/action-card/action-card.tsx`:
- Around line 81-127: The action-card render path still places the default
<slot> inside a native <button>, so arbitrary slotted interactive descendants
remain allowed and can create invalid nested controls. Update the
ActionCardComponent render logic to either move the <slot> outside the button or
otherwise prevent interactive projected content from being rendered inside the
button, while keeping the existing ix-card, ix-typography, and icon structure
intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f9944493-a756-4c34-b3b2-a10c175ec654

📥 Commits

Reviewing files that changed from the base of the PR and between 3dcbc1d and 8e5afe0.

📒 Files selected for processing (4)
  • .changeset/pink-sloths-drop.md
  • packages/core/src/components/action-card/action-card.scss
  • packages/core/src/components/action-card/action-card.tsx
  • packages/core/src/components/action-card/test/action-card.ct.ts

Wraps the inner `ix-card` of the `ix-action-card` in a `button`.
This is aligned with the element approach.
@spike-rabbit spike-rabbit force-pushed the fix/action-card/render-as-button branch from e0d7c2a to 8ea02ce Compare June 29, 2026 09:31
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant