Skip to content

fix(action-card): make action card focusable and keyboard activatable#2598

Open
lakshmi-priya-b wants to merge 4 commits into
mainfrom
fix/action-card-keyboard-accessibility
Open

fix(action-card): make action card focusable and keyboard activatable#2598
lakshmi-priya-b wants to merge 4 commits into
mainfrom
fix/action-card-keyboard-accessibility

Conversation

@lakshmi-priya-b

@lakshmi-priya-b lakshmi-priya-b commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

💡 What is the current behavior?

Action cards are clickable using a mouse, but they are not keyboard accessible.

  • Action cards cannot receive focus via the Tab key.
  • Enter and Space do not activate the card.
  • Screen readers do not recognize action cards as interactive elements.

GitHub Issue Number: #

🆕 What is the new behavior?

Action cards now support keyboard interaction and expose interactive semantics.

  • Action cards can receive focus via the Tab key.
  • Enter and Space activate the action card and trigger the click handler.

🏁 Checklist

A pull request can only be merged if all of these conditions are met (where applicable):

  • 🦮 Accessibility (a11y) features were implemented
  • 🗺️ Internationalization (i18n) - no hard coded strings
  • 📲 Responsiveness - components handle viewport changes and content overflow gracefully
  • 📕 Add or update a Storybook story
  • 📄 Documentation was reviewed/updated siemens/ix-docs
  • 🧪 Unit tests were added/updated and pass (pnpm test)
  • 📸 Visual regression tests were added/updated and pass (Guide)
  • 🧐 Static code analysis passes (pnpm lint)
  • 🏗️ Successful compilation (pnpm build, changes pushed)

👨‍💻 Help & support

Summary by CodeRabbit

  • New Features

    • Enhanced keyboard navigation with improved focus-visible indicators using theme-based styling
    • Added passive mode option to prevent interactive behavior when needed
    • Better visual feedback for active state interactions with dynamic styling
  • Bug Fixes

    • Improved keyboard focus visibility and accessibility for all users
  • Tests

    • New accessibility regression tests added for action cards

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The ix-action-card component is refactored to wrap its ix-card content in an explicit <button> element. Accessibility attributes (aria-label, aria-labelledby) and focus control (tabIndex) move to the button; ix-card loses those props. SCSS adds button.card-button rules for layout, focus-visible, passive, and active states. New axe-based accessibility tests cover both default and passive variants.

Changes

action-card button wrapper for accessibility

Layer / File(s) Summary
button wrapper: TSX render and SCSS styling
packages/core/src/components/action-card/action-card.tsx, packages/core/src/components/action-card/action-card.scss
render() introduces a <button class="card-button"> that receives aria-label, aria-labelledby, and tabIndex (set to -1 when passive); ix-card loses those accessibility props and its conditional role. SCSS adds a button.card-button block that unsets native button styles, sets width/height: 100%, applies focus-visible outline with theme variables, disables pointer events under :host(.passive), and colors the nested ix-card background on :active.
Axe accessibility regression tests
packages/core/src/components/action-card/test/action-card.ct.ts
New Playwright test file mounts ix-action-card in default and passive configurations, runs makeAxeBuilder().analyze(), and asserts zero accessibility violations for each case.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A button now wraps what ix-card once held,
With aria and tabIndex carefully spelled.
Focus rings glow when the keyboard arrives,
And passive cards sit still while others take dives.
Axe finds no faults — the rabbit hops free! 🎉

🚥 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 changes: refactoring action cards to be focusable and keyboard activatable, which directly matches the core implementation across all modified files.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/action-card-keyboard-accessibility

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.

@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: f1ad162

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@netlify

netlify Bot commented Jun 12, 2026

Copy link
Copy Markdown

Deploy Preview for ix-storybook ready!

Name Link
🔨 Latest commit f1ad162
🔍 Latest deploy log https://app.netlify.com/projects/ix-storybook/deploys/6a38ddec4a50f900084fb725
😎 Deploy Preview https://deploy-preview-2598--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.

@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 introduces accessibility and keyboard interaction improvements to the IxActionCard component, making it focusable and interactive via the Enter and Space keys when not passive. The review feedback highlights a potential issue where keyboard events from nested interactive elements could bubble up and trigger unintended clicks, suggesting a target check to prevent this. Additionally, in accordance with the repository style guide, the reviewer requests the addition of a changeset for these user-facing changes and an axe-based component test to verify accessibility coverage.

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 Outdated
Comment thread packages/core/src/components/action-card/action-card.tsx Outdated
Comment thread packages/core/src/components/action-card/action-card.tsx Outdated
@sonarqubecloud

Copy link
Copy Markdown

@lakshmi-priya-b lakshmi-priya-b marked this pull request as ready for review June 23, 2026 04:37

@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 44-50: The CSS selectors in the action-card.scss file are using
the class selector notation (`:host(.passive)`) but the passive state is applied
as an attribute rather than a class. Update the selectors `:host(.passive)` on
line 44 and `:host(:not(.passive))` on line 49 to use attribute selector
notation instead, changing them to `:host([passive])` and
`:host(:not([passive]))` respectively. This will ensure the passive styling and
interaction rules apply correctly based on the passive attribute.

In `@packages/core/src/components/action-card/test/action-card.ct.ts`:
- Around line 12-39: Add a mandatory `renders` test before the accessibility
describe block that verifies the ix-action-card component hydrates correctly
without any attributes. In both the 'default' and 'passive' accessibility tests,
add a hydration assertion after mounting the ix-action-card component but before
calling makeAxeBuilder().analyze(), ensuring the component is fully hydrated
before performing the accessibility scan. The hydration check should verify that
the mounted element contains the expected hydrated state before running axe
accessibility tests.
- Around line 13-38: The existing tests only validate accessibility violations
but do not test the keyboard interaction behavior that is the core change in
this PR. Add new regression tests after the existing 'default' and 'passive'
accessibility test cases to verify keyboard activation behavior. Create a
behavior test for the default mode that mounts an ix-action-card component,
simulates pressing Enter or Space keys, and asserts that the activation is
triggered (by checking for a click event or state change). Then create another
behavior test for passive mode that verifies pressing Enter or Space keys does
NOT trigger any activation. Follow the test ordering guideline by placing these
behavior tests (user interactions) after the accessibility violation tests.
🪄 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: 87102973-f05c-4787-949e-aafb64fb183b

📥 Commits

Reviewing files that changed from the base of the PR and between 7c34939 and f1ad162.

📒 Files selected for processing (3)
  • 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 on lines +44 to +50
:host(.passive) & {
pointer-events: none;
cursor: default;
}

:host(:not(.passive)) &:active ix-card {
background-color: var(--theme-color-ghost--active);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Passive-state CSS selectors won’t match the component’s passive attribute.

Line 44 and Line 49 target :host(.passive), but the passive state is used as an attribute (passive), so these branches won’t apply reliably and passive styling/interaction gating can regress.

🔧 Proposed fix
-  :host(.passive) & {
+  :host([passive]) & {
     pointer-events: none;
     cursor: default;
   }

-  :host(:not(.passive)) &:active ix-card {
+  :host(:not([passive])) &:active ix-card {
     background-color: var(--theme-color-ghost--active);
   }
📝 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
:host(.passive) & {
pointer-events: none;
cursor: default;
}
:host(:not(.passive)) &:active ix-card {
background-color: var(--theme-color-ghost--active);
:host([passive]) & {
pointer-events: none;
cursor: default;
}
:host(:not([passive])) &:active ix-card {
background-color: var(--theme-color-ghost--active);
}
🤖 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 44 -
50, The CSS selectors in the action-card.scss file are using the class selector
notation (`:host(.passive)`) but the passive state is applied as an attribute
rather than a class. Update the selectors `:host(.passive)` on line 44 and
`:host(:not(.passive))` on line 49 to use attribute selector notation instead,
changing them to `:host([passive])` and `:host(:not([passive]))` respectively.
This will ensure the passive styling and interaction rules apply correctly based
on the passive attribute.

Comment on lines +12 to +39
regressionTest.describe('accessibility', () => {
regressionTest('default', async ({ mount, makeAxeBuilder }) => {
await mount(`
<ix-action-card
heading="Scan for new devices"
subheading="Secondary text"
></ix-action-card>
`);

const accessibilityScanResults = await makeAxeBuilder().analyze();

expect(accessibilityScanResults.violations).toEqual([]);
});

regressionTest('passive', async ({ mount, makeAxeBuilder }) => {
await mount(`
<ix-action-card
heading="Scan for new devices"
subheading="Secondary text"
passive
></ix-action-card>
`);

const accessibilityScanResults = await makeAxeBuilder().analyze();

expect(accessibilityScanResults.violations).toEqual([]);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add the required renders test and hydration assertions.

This file currently runs axe scans only. It is missing the mandatory renders test, and hydration is not asserted before accessibility checks.

✅ Proposed test additions
 regressionTest.describe('accessibility', () => {
   regressionTest('default', async ({ mount, makeAxeBuilder, page }) => {
     await mount(`
       <ix-action-card
         heading="Scan for new devices"
         subheading="Secondary text"
       ></ix-action-card>
     `);

+    await expect(page.locator('ix-action-card')).toHaveClass(/\bhydrated\b/);
+
     const accessibilityScanResults = await makeAxeBuilder().analyze();

     expect(accessibilityScanResults.violations).toEqual([]);
   });
@@
     await mount(`
       <ix-action-card
         heading="Scan for new devices"
         subheading="Secondary text"
         passive
       ></ix-action-card>
     `);

+    await expect(page.locator('ix-action-card')).toHaveClass(/\bhydrated\b/);
+
     const accessibilityScanResults = await makeAxeBuilder().analyze();

     expect(accessibilityScanResults.violations).toEqual([]);
   });
+
+  regressionTest('renders', async ({ mount, page }) => {
+    await mount(`
+      <ix-action-card
+        heading="Scan for new devices"
+        subheading="Secondary text"
+      ></ix-action-card>
+    `);
+
+    await expect(page.locator('ix-action-card')).toHaveClass(/\bhydrated\b/);
+    await expect(page.getByRole('button')).toBeVisible();
+  });
 });

As per coding guidelines, “Every test file should include a 'renders' test verifying basic hydration” and “Always verify components are hydrated before making further assertions … /\bhydrated\b/”; as per path instructions for packages/core/src/components/**/test/*.ct.ts, tests must include both accessibility and renders hydration coverage.

🤖 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/test/action-card.ct.ts` around lines
12 - 39, Add a mandatory `renders` test before the accessibility describe block
that verifies the ix-action-card component hydrates correctly without any
attributes. In both the 'default' and 'passive' accessibility tests, add a
hydration assertion after mounting the ix-action-card component but before
calling makeAxeBuilder().analyze(), ensuring the component is fully hydrated
before performing the accessibility scan. The hydration check should verify that
the mounted element contains the expected hydrated state before running axe
accessibility tests.

Sources: Coding guidelines, Path instructions

Comment on lines +13 to +38
regressionTest('default', async ({ mount, makeAxeBuilder }) => {
await mount(`
<ix-action-card
heading="Scan for new devices"
subheading="Secondary text"
></ix-action-card>
`);

const accessibilityScanResults = await makeAxeBuilder().analyze();

expect(accessibilityScanResults.violations).toEqual([]);
});

regressionTest('passive', async ({ mount, makeAxeBuilder }) => {
await mount(`
<ix-action-card
heading="Scan for new devices"
subheading="Secondary text"
passive
></ix-action-card>
`);

const accessibilityScanResults = await makeAxeBuilder().analyze();

expect(accessibilityScanResults.violations).toEqual([]);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add keyboard behavior regression tests for Enter/Space and passive mode.

The PR’s core change is keyboard activatability, but this file does not assert Enter/Space click activation or passive-mode non-activation, so the behavior can regress undetected.

⌨️ Proposed behavior tests
+regressionTest('keyboard activates on Enter and Space', async ({ mount, page }) => {
+  await mount(`
+    <ix-action-card heading="Scan for new devices" subheading="Secondary text"></ix-action-card>
+  `);
+
+  const host = page.locator('ix-action-card');
+  await expect(host).toHaveClass(/\bhydrated\b/);
+
+  const button = page.getByRole('button');
+  await button.focus();
+
+  const clickSpy = page.waitForEvent('console'); // replace with component click-event listener helper used in this repo
+  await page.keyboard.press('Enter');
+  await page.keyboard.press('Space');
+  await clickSpy;
+});
+
+regressionTest('passive is not keyboard-focusable', async ({ mount, page }) => {
+  await mount(`
+    <ix-action-card heading="Scan for new devices" subheading="Secondary text" passive></ix-action-card>
+  `);
+
+  await expect(page.locator('ix-action-card')).toHaveClass(/\bhydrated\b/);
+  await expect(page.getByRole('button')).toHaveAttribute('tabindex', '-1');
+});

As per coding guidelines, “Follow this test order within each file … 4. Behavior tests (user interactions, state 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/test/action-card.ct.ts` around lines
13 - 38, The existing tests only validate accessibility violations but do not
test the keyboard interaction behavior that is the core change in this PR. Add
new regression tests after the existing 'default' and 'passive' accessibility
test cases to verify keyboard activation behavior. Create a behavior test for
the default mode that mounts an ix-action-card component, simulates pressing
Enter or Space keys, and asserts that the activation is triggered (by checking
for a click event or state change). Then create another behavior test for
passive mode that verifies pressing Enter or Space keys does NOT trigger any
activation. Follow the test ordering guideline by placing these behavior tests
(user interactions) after the accessibility violation tests.

Source: Coding guidelines

@lakshmi-priya-b lakshmi-priya-b marked this pull request as draft June 23, 2026 10:25
@lakshmi-priya-b lakshmi-priya-b marked this pull request as ready for review June 30, 2026 05:42
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