diff --git a/packages/eui/changelogs/upcoming/8907.md b/packages/eui/changelogs/upcoming/8907.md
new file mode 100644
index 00000000000..0274c14d418
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/8907.md
@@ -0,0 +1 @@
+- Enhanced `EuiCheckableCard` to make non-interactive children clickable for card selection
diff --git a/packages/eui/src/components/card/checkable_card/__snapshots__/checkable_card.test.tsx.snap b/packages/eui/src/components/card/checkable_card/__snapshots__/checkable_card.test.tsx.snap
index 55547d8d909..a6ad896bd48 100644
--- a/packages/eui/src/components/card/checkable_card/__snapshots__/checkable_card.test.tsx.snap
+++ b/packages/eui/src/components/card/checkable_card/__snapshots__/checkable_card.test.tsx.snap
@@ -120,6 +120,7 @@ exports[`EuiCheckableCard renders children 1`] = `
Child
diff --git a/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx b/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx
index 81d2b27de55..0d7ffdfe86e 100644
--- a/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx
+++ b/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx
@@ -10,7 +10,7 @@
///
///
-import React, { FunctionComponent, useState } from 'react';
+import { FunctionComponent, useState } from 'react';
import { EuiCheckableCard, type EuiCheckableCardProps } from '../index';
@@ -34,12 +34,40 @@ describe('EuiCheckableCard', () => {
};
describe('Click behavior', () => {
- it('fired onChange only once when the checkbox is clicked', () => {
+ it('fires onChange only once when the checkbox is clicked', () => {
cy.realMount();
cy.get('[data-test-subj=checkableCard]').realClick();
cy.get('[data-test-subj=checkableCard]').should('be.checked');
});
+
+ it('fires onChange when clicking on non-interactive children', () => {
+ cy.realMount(
+
+
+ Non-interactive text content
+
+
+ );
+
+ cy.get('[data-test-subj=non-interactive-content]').realClick();
+
+ cy.get('[data-test-subj=checkableCard]').should('be.checked');
+ });
+
+ it('does not fire onChange when clicking on interactive children', () => {
+ cy.realMount(
+
+
+
+ );
+
+ cy.get('[data-test-subj=interactive-button]').realClick();
+
+ cy.get('[data-test-subj=checkableCard]').should('not.be.checked');
+ });
});
});
diff --git a/packages/eui/src/components/card/checkable_card/checkable_card.stories.tsx b/packages/eui/src/components/card/checkable_card/checkable_card.stories.tsx
index 6b791ef3e9e..64d2cf6a19f 100644
--- a/packages/eui/src/components/card/checkable_card/checkable_card.stories.tsx
+++ b/packages/eui/src/components/card/checkable_card/checkable_card.stories.tsx
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { EuiCheckableCard, EuiCheckableCardProps } from './checkable_card';
@@ -48,3 +49,63 @@ export const Playground: Story = {
label: 'Checkable option',
},
};
+
+export const WithNonInteractiveChildren: Story = {
+ args: {
+ id: 'checkable-card-non-interactive',
+ label: 'Service Plan',
+ children: (
+
+
Basic plan includes:
+
+ - Up to 5 users
+ - 10GB storage
+ - Email support
+
+
Perfect for small teams getting started.
+
+ ),
+ },
+};
+
+export const WithInteractiveChildren: Story = {
+ args: {
+ id: 'checkable-card-interactive',
+ label: 'Advanced Configuration',
+ children: (
+
+ ),
+ },
+};
diff --git a/packages/eui/src/components/card/checkable_card/checkable_card.tsx b/packages/eui/src/components/card/checkable_card/checkable_card.tsx
index da3e8e77800..ff3c3b98627 100644
--- a/packages/eui/src/components/card/checkable_card/checkable_card.tsx
+++ b/packages/eui/src/components/card/checkable_card/checkable_card.tsx
@@ -6,8 +6,15 @@
* Side Public License, v 1.
*/
-import React, { FunctionComponent, ReactNode, useRef } from 'react';
+import React, {
+ FunctionComponent,
+ ReactNode,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
import classNames from 'classnames';
+import { tabbable } from 'tabbable';
import {
EuiRadio,
@@ -76,8 +83,19 @@ export const EuiCheckableCard: FunctionComponent = ({
const { id } = rest;
const labelEl = useRef(null);
const inputEl = useRef(null);
+ const childrenWrapperEl = useRef(null);
+
const classes = classNames('euiCheckableCard', className);
+ const [hasInteractiveChildren, setHasInteractiveChildren] = useState(false);
+
+ useEffect(() => {
+ const interactiveElements = childrenWrapperEl.current
+ ? tabbable(childrenWrapperEl.current)
+ : [];
+ setHasInteractiveChildren(interactiveElements.length > 0);
+ }, [children, childrenWrapperEl]);
+
let checkableElement;
if (checkableType === 'radio') {
checkableElement = (
@@ -101,9 +119,13 @@ export const EuiCheckableCard: FunctionComponent = ({
const labelClasses = classNames('euiCheckableCard__label');
const onChangeAffordance = (e: React.MouseEvent) => {
- if (labelEl.current && e.target !== inputEl.current) {
- labelEl.current.click();
- }
+ if (!labelEl.current || e.target === inputEl.current) return;
+ labelEl.current.click();
+ };
+
+ const onChildrenClick = () => {
+ if (hasInteractiveChildren) return;
+ labelEl.current?.click();
};
return (
@@ -136,9 +158,15 @@ export const EuiCheckableCard: FunctionComponent = ({
{children && (
{children}