WIP: WooCommerce Variation swatches a11y regression in 2.3.3

After the “Variation Swatches Keyboard accessibility” changes in 2.3.3, my PageSpeed Insights accessibility score dropped from 100 to 97 on WooCommerce product pages. Axe-core flags the swatch <ul> with:

Lists do not contain only <li> elements and script supporting elements (<script> and <template>).

Two issues in the current markup:

<ul class="bricks-variation-swatches bricks-swatch-label">
  <li role="button" aria-pressed="false" tabindex="0" data-value="0-5-kg" aria-label="0.5 kg">0.5 kg</li>
  <li role="button" aria-pressed="true"  tabindex="0" data-value="1-kg"   aria-label="1 kg" class="bricks-swatch-selected">1 kg</li>
  <input type="hidden" name="attribute_pa_size" class="variation-select" value="1-kg">
</ul>
  1. role="button" on <li> overrides the implicit listitem role.

  2. <input type="hidden"> is placed as a direct child of <ul>, which isn’t a valid list child.

Environment: Bricks 2.3.3, WooCommerce 10.7.0, WordPress 6.9.4

Impact: Affects all Bricks + WooCommerce sites using variation swatches.

1 Like

Thanks so much for creating a separate report on this!

I can replicate the problem and have added it to the internal bug tracker. We will update this thread as soon as it is fixed.

Best regards,
timmse

2 Likes

thats wild i was sent by Claude here. wow

There’s an active forum thread on exactly this. Let me read it to see what the community/Bricks team is recommending.

Fetched: WIP: WooCommerce Variation swatches a11y regression in 2.3.3

Fetched: WIP: WooCommerce Variation swatches a11y regression in 2.3.3

WIP: WooCommerce Variation swatches a11y regression in 2.3.3

forum.bricksbuilder.io

Done

This confirms what’s going on. The Bricks team has acknowledged this as a confirmed bug (added to internal tracker) on April 27, 2026, with no fix released yet. Two issues actually: the <input> inside <ul>, AND role="button" on <li> overriding the listitem role. Bricks Community Forum

So your PHP filter approach is correct in concept — the issue is that your regex breaks Bricks’ swatch JS. Bricks’ frontend script almost certainly does something like ul.querySelector('.variation-select') to read/write the selected variation when a swatch is clicked. Moving the input outside breaks that lookup.

The cleanest fix is to do it in the DOM after Bricks initialises — let Bricks find the input where it expects it, then relocate it. That way the swatch click handler has already wired itself up.

Here’s a better approach. Drop this into a WPCode snippet (PHP, site-wide, footer) or your child theme:

php

<?php
/**
 * Fix Bricks variation swatches a11y issues:
 * 1. <input class="variation-select"> placed inside <ul> (invalid list child)
 * 2. role="button" on <li> overrides implicit listitem role
 *
 * We DON'T filter the PHP output — Bricks' JS queries the input via the <ul>.
 * Instead, after Bricks wires up its swatch handlers, we:
 *   - move the input to be a sibling AFTER the <ul>
 *   - remove role="button" from <li> (the <li> keeps tabindex + aria-pressed,
 *     which is enough; screen readers will announce it as a list item that
 *     behaves like a toggle button via aria-pressed)
 *
 * Re-runs on every WooCommerce variation form refresh.
 */
add_action( 'wp_footer', function () {
	if ( ! function_exists( 'is_product' ) || ! is_product() ) {
		return;
	}
	?>
	<script id="bricks-swatch-a11y-fix">
	(function () {
		function fixSwatches(root) {
			var lists = (root || document).querySelectorAll('ul.bricks-variation-swatches');
			lists.forEach(function (ul) {
				// 1) Move hidden <input class="variation-select"> out of <ul>.
				var input = ul.querySelector(':scope > input.variation-select');
				if (input && ul.parentNode) {
					ul.parentNode.insertBefore(input, ul.nextSibling);
				}

				// 2) Strip role="button" off <li>; keep tabindex/aria-pressed/aria-label.
				ul.querySelectorAll('li[role="button"]').forEach(function (li) {
					li.removeAttribute('role');
				});
			});
		}

		// Initial pass.
		if (document.readyState === 'loading') {
			document.addEventListener('DOMContentLoaded', function () { fixSwatches(); });
		} else {
			fixSwatches();
		}

		// Re-run when Woo re-renders the variations form (price/availability updates,
		// reset link, AJAX add-to-cart plugins, etc.).
		if (window.jQuery) {
			jQuery(document.body).on(
				'woocommerce_update_variation_values found_variation reset_data show_variation',
				function () { fixSwatches(); }
			);
		}
	})();
	</script>
	<?php
}, 99 );

but i will rather wait for the patch fix.

Was really hopping this issue would have been fixed on this update.