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
+			);
+		} );
+	} );
 } );