Commit f1c1d4ea9d7 for woocommerce
commit f1c1d4ea9d7e94bf443163a8764ab7d9b9b377a3
Author: Tom Cafferkey <tjcafferkey@gmail.com>
Date: Thu Jul 16 17:53:56 2026 +0100
Add checkout terms checkbox validation message (#66449)
* Fix cart clear filter override
* Add validation text for checkbox and tests
* Format checkout terms checkbox handler
* Add changelog entry for checkout terms validation
* Revert messaging
* Update aria prop
* Fix tests
* Reset cart clear filter changes
* Use describeby
diff --git a/plugins/woocommerce/changelog/fix-checkout-terms-checkbox-validation-message b/plugins/woocommerce/changelog/fix-checkout-terms-checkbox-validation-message
new file mode 100644
index 00000000000..cf480e92718
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-checkout-terms-checkbox-validation-message
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Add a visible validation message for the checkout terms and conditions checkbox.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/frontend.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/frontend.tsx
index 9075ba99d95..c4a9a8a8314 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/frontend.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/frontend.tsx
@@ -4,7 +4,10 @@
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import { useState, useEffect } from '@wordpress/element';
-import { CheckboxControl } from '@woocommerce/blocks-components';
+import {
+ CheckboxControl,
+ ValidationInputError,
+} from '@woocommerce/blocks-components';
import { useCheckoutSubmit } from '@woocommerce/base-context/hooks';
import { withInstanceId } from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
@@ -36,11 +39,15 @@ const FrontendBlock = ( {
const { setValidationErrors, clearValidationError } =
useDispatch( validationStore );
- const error = useSelect(
+ const { error, validationErrorHtmlId } = useSelect(
( select ) => {
- return select( validationStore ).getValidationError(
- validationErrorId
- );
+ const store = select( validationStore );
+
+ return {
+ error: store.getValidationError( validationErrorId ),
+ validationErrorHtmlId:
+ store.getValidationErrorId( validationErrorId ),
+ };
},
[ validationErrorId ]
);
@@ -90,20 +97,31 @@ const FrontendBlock = ( {
) }
>
{ checkbox ? (
- <CheckboxControl
- id="terms-and-conditions"
- checked={ checked }
- onChange={ () => setChecked( ( value ) => ! value ) }
- hasError={ hasError }
- disabled={ isDisabled }
- >
- <span
- className="wc-block-components-checkbox__label"
- dangerouslySetInnerHTML={ {
- __html: text || termsCheckboxDefaultText,
- } }
+ <>
+ <CheckboxControl
+ id="terms-and-conditions"
+ checked={ checked }
+ onChange={ () =>
+ setChecked( ( value ) => ! value )
+ }
+ hasError={ hasError }
+ aria-describedby={
+ hasError ? validationErrorHtmlId : undefined
+ }
+ disabled={ isDisabled }
+ >
+ <span
+ className="wc-block-components-checkbox__label"
+ dangerouslySetInnerHTML={ {
+ __html: text || termsCheckboxDefaultText,
+ } }
+ />
+ </CheckboxControl>
+ <ValidationInputError
+ propertyName={ validationErrorId }
+ elementId={ validationErrorId }
/>
- </CheckboxControl>
+ </>
) : (
<span
className="wc-block-components-checkbox__label"
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/test/frontend.js b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/test/frontend.js
index 4fc258d5f80..043dfbfdeb0 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/test/frontend.js
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-terms-block/test/frontend.js
@@ -6,9 +6,13 @@ import {
findByLabelText,
queryByLabelText,
act,
+ screen,
+ waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
+import { dispatch } from '@wordpress/data';
+import { validationStore } from '@woocommerce/block-data';
/**
* Internal dependencies
@@ -89,4 +93,35 @@ describe( 'FrontendBlock', () => {
expect.stringMatching( /terms-and-conditions-\d/ )
);
} );
+
+ it( 'Renders and describes the validation error when the checkbox is required and unchecked', async () => {
+ const { container } = render(
+ <SlotFillProvider>
+ <FrontendBlock
+ checkbox={ true }
+ text={ 'I agree to the terms and conditions' }
+ showSeparator={ false }
+ />
+ </SlotFillProvider>
+ );
+ const checkbox = await findByLabelText(
+ container,
+ 'I agree to the terms and conditions'
+ );
+
+ await act( async () => {
+ dispatch( validationStore ).showAllValidationErrors();
+ } );
+
+ const errorMessage = await screen.findByText(
+ 'Please read and accept the terms and conditions.'
+ );
+
+ await waitFor( () => {
+ expect( checkbox ).toHaveAttribute(
+ 'aria-describedby',
+ errorMessage.closest( 'p' ).id
+ );
+ } );
+ } );
} );