diff --git a/packages/eui/changelogs/upcoming/8786.md b/packages/eui/changelogs/upcoming/8786.md
new file mode 100644
index 00000000000..4429eaed6f7
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/8786.md
@@ -0,0 +1,4 @@
+**Bug fixes**
+
+- Fixed `onChange` being triggered twice when the checkbox in `EuiCheckableCard` is clicked
+
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
new file mode 100644
index 00000000000..81d2b27de55
--- /dev/null
+++ b/packages/eui/src/components/card/checkable_card/checkable_card.spec.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+///
+///
+///
+
+import React, { FunctionComponent, useState } from 'react';
+
+import { EuiCheckableCard, type EuiCheckableCardProps } from '../index';
+
+describe('EuiCheckableCard', () => {
+ const StatefulCheckableCard: FunctionComponent<
+ Partial
+ > = ({ checkableType = 'checkbox', ...rest }) => {
+ const [isChecked, setChecked] = useState(false);
+
+ return (
+ setChecked((checked) => !checked)}
+ />
+ );
+ };
+
+ describe('Click behavior', () => {
+ it('fired 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');
+ });
+ });
+});
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 ccaa25be195..da3e8e77800 100644
--- a/packages/eui/src/components/card/checkable_card/checkable_card.tsx
+++ b/packages/eui/src/components/card/checkable_card/checkable_card.tsx
@@ -75,6 +75,7 @@ export const EuiCheckableCard: FunctionComponent = ({
const { id } = rest;
const labelEl = useRef(null);
+ const inputEl = useRef(null);
const classes = classNames('euiCheckableCard', className);
let checkableElement;
@@ -88,14 +89,19 @@ export const EuiCheckableCard: FunctionComponent = ({
);
} else {
checkableElement = (
-
+
);
}
const labelClasses = classNames('euiCheckableCard__label');
- const onChangeAffordance = () => {
- if (labelEl.current) {
+ const onChangeAffordance = (e: React.MouseEvent) => {
+ if (labelEl.current && e.target !== inputEl.current) {
labelEl.current.click();
}
};
diff --git a/packages/eui/src/components/form/checkbox/checkbox.tsx b/packages/eui/src/components/form/checkbox/checkbox.tsx
index 280cb5ee144..45d286c14dd 100644
--- a/packages/eui/src/components/form/checkbox/checkbox.tsx
+++ b/packages/eui/src/components/form/checkbox/checkbox.tsx
@@ -13,6 +13,7 @@ import React, {
InputHTMLAttributes,
LabelHTMLAttributes,
useCallback,
+ Ref,
} from 'react';
import classNames from 'classnames';
@@ -28,7 +29,7 @@ export interface EuiCheckboxProps
id: string;
checked?: boolean;
onChange: ChangeEventHandler; // overriding to make it required
- inputRef?: (element: HTMLInputElement) => void;
+ inputRef?: Ref | ((element: HTMLInputElement) => void);
label?: ReactNode;
disabled?: boolean;
indeterminate?: boolean;
diff --git a/packages/website/docs/components/containers/card.mdx b/packages/website/docs/components/containers/card.mdx
index d74d9e213c2..8553f6c4605 100644
--- a/packages/website/docs/components/containers/card.mdx
+++ b/packages/website/docs/components/containers/card.mdx
@@ -363,7 +363,7 @@ export default () => {
checkableType="checkbox"
value="checkbox1"
checked={checkbox}
- onChange={() => setCheckbox(!checkbox)}
+ onChange={() => setCheckbox(checked => !checked)}
/>
);
};