Commit 2c9e2e48efa for woocommerce

commit 2c9e2e48efae428ff246a5df934b673e78cc2db2
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date:   Wed Jun 24 12:58:45 2026 +0200

    Minor enhancements to product swatches and editing Color attributes in the editor (#65693)

    * Format wc-enhanced-select.js

    * Add changelog

    * Make product swatches bigger so they are more accessible

    * Add badge to global attributes in the product editor

    * Rename 'Add existing' to 'Add global' when adding attributes to variable products

    * Add swatches to visual attributes in the product editor

    * Update PHPStan

    * CodeRabbit suggestions

    * PHPStan fix

diff --git a/plugins/woocommerce/changelog/update-swatches-improvements b/plugins/woocommerce/changelog/update-swatches-improvements
new file mode 100644
index 00000000000..a109e6faf58
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-swatches-improvements
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Minor enhancements to product swatches and editing Color attributes in the editor
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss
index 4a43dbc60ac..cdd0d1a403f 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/chips/style.scss
@@ -146,8 +146,8 @@
 		background-position: center;
 		background-repeat: no-repeat;
 		display: inline-block;
-		width: 1.8em;
-		height: 1.8em;
+		width: 2.3em;
+		height: 2.3em;
 		border-radius: 50%;
 		border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
 		flex-shrink: 0;
diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss
index 6ddce06519e..636d5337114 100644
--- a/plugins/woocommerce/client/legacy/css/admin.scss
+++ b/plugins/woocommerce/client/legacy/css/admin.scss
@@ -6619,6 +6619,7 @@ img.help_tip {
 				margin-top: 0.25em;
 			}
 		}
+
 		&.woocommerce_variation h3 {
 			a.delete,
 			a.edit,
@@ -6627,6 +6628,19 @@ img.help_tip {
 			}
 		}

+		&.woocommerce_attribute h3 .woocommerce-attribute-global-badge {
+			display: inline-block;
+			margin-inline-start: 6px;
+			border-radius: 4px;
+			background: $gray-100;
+			padding: 4px 6px;
+			color: #3c434a;
+			font-size: 12px;
+			font-weight: 400;
+			line-height: 16px;
+			vertical-align: middle;
+		}
+
 		table {
 			width: 100%;
 			position: relative;
@@ -9746,6 +9760,34 @@ body.woocommerce-settings-payments-section_legacy {
 	}
 }

+.wc-admin-visual-attribute-term-option {
+	display: inline-flex;
+	align-items: center;
+	gap: 8px;
+
+	.wc-admin-color-swatch {
+		margin-right: 0;
+		flex-shrink: 0;
+		height: 16px;
+		width: 16px;
+		background-size: cover;
+		background-position: center;
+		background-repeat: no-repeat;
+	}
+}
+
+.select2-selection__choice .wc-admin-visual-attribute-term-option {
+	gap: 6px;
+	vertical-align: middle;
+	margin-bottom: 1px;
+	margin-inline-start: 2px;
+}
+
+.select2-results__option .wc-admin-visual-attribute-term-option {
+	margin-top: 2px;
+	width: 100%;
+}
+
 .wc-admin-visual-attribute-type fieldset {
 	display: flex;
 	gap: 1.5em;
diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
index afff111b241..75343c67b12 100644
--- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
+++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
@@ -6,6 +6,28 @@ jQuery( function ( $ ) {
 		isPageUnloading = true;
 	} );

+	function appendAttributeTermOption( select, term ) {
+		if (
+			! select ||
+			! term ||
+			term.term_id === undefined ||
+			term.name === undefined
+		) {
+			return;
+		}
+
+		const option = document.createElement( 'option' );
+		option.value = String( term.term_id );
+		option.selected = true;
+		option.textContent = term.name;
+
+		if ( term.visual ) {
+			option.dataset.visual = JSON.stringify( term.visual );
+		}
+
+		select.appendChild( option );
+	}
+
 	// Scroll to first checked category
 	// https://github.com/scribu/wp-category-checklist-tree/blob/d1c3c1f449e1144542efa17dde84a9f52ade1739/category-checklist-tree.php
 	$( function () {
@@ -791,15 +813,12 @@ jQuery( function ( $ ) {
 							if ( currentItem && currentItem.length > 0 ) {
 								currentItem.prop( 'selected', 'selected' );
 							} else {
-								$wrapper
-									.find( 'select.attribute_values' )
-									.append(
-										'<option value="' +
-											term.term_id +
-											'" selected="selected">' +
-											term.name +
-											'</option>'
-									);
+								appendAttributeTermOption(
+									$wrapper.find(
+										'select.attribute_values'
+									)[ 0 ],
+									term
+								);
 							}
 						} );
 						$wrapper
@@ -1023,11 +1042,11 @@ jQuery( function ( $ ) {
 							'select.attribute_values'
 						);
 						if ( select ) {
-							const option = document.createElement( 'option' );
-							option.value = String( response.term_id );
-							option.selected = true;
-							option.textContent = response.name;
-							select.appendChild( option );
+							appendAttributeTermOption( select, {
+								term_id: response.term_id,
+								name: response.name,
+								visual: response.visual,
+							} );

 							// Trigger change event natively.
 							const changeEvent = new Event( 'change', {
@@ -1062,8 +1081,7 @@ jQuery( function ( $ ) {

 			const wrapper = this.closest( '.woocommerce_attribute' );
 			const attribute = wrapper ? wrapper.dataset.taxonomy : '';
-			const isVisualAttribute =
-				this.dataset.isVisualAttribute === 'yes';
+			const isVisualAttribute = this.dataset.isVisualAttribute === 'yes';

 			currentAttributeTermCreationContext = {
 				wrapper,
@@ -1455,9 +1473,9 @@ jQuery( function ( $ ) {
 	const setProductImageLink = $( '#set-post-thumbnail' );
 	// Escape the translated label before interpolating into the attribute so a
 	// translation containing quotes or markup cannot break the rendered span.
-	const tooltipMarkup = `<span class="woocommerce-help-tip" tabindex="0" aria-label="${
-		_.escape( woocommerce_admin_meta_boxes.i18n_product_image_tip )
-	}"></span>`;
+	const tooltipMarkup = `<span class="woocommerce-help-tip" tabindex="0" aria-label="${ _.escape(
+		woocommerce_admin_meta_boxes.i18n_product_image_tip
+	) }"></span>`;
 	const tooltipData = {
 		attribute: 'data-tip',
 		content: woocommerce_admin_meta_boxes.i18n_product_image_tip,
diff --git a/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js b/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js
index 3a32ad534a2..4cf2793315b 100644
--- a/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js
+++ b/plugins/woocommerce/client/legacy/js/admin/wc-enhanced-select.js
@@ -1,104 +1,235 @@
 /*global wc_enhanced_select_params */
-jQuery( function( $ ) {
+jQuery( function ( $ ) {
+	function getVisualTermSwatchStyle( visual ) {
+		if ( ! visual.type || ! visual.value ) {
+			return '';
+		}
+
+		if ( 'image' === visual.type ) {
+			return (
+				"background-image:url('" +
+				String( visual.value ).replace( /'/g, '%27' ) +
+				"')"
+			);
+		}
+
+		if ( 'color' === visual.type ) {
+			return 'background-color:' + visual.value;
+		}
+
+		return '';
+	}
+
+	function formatVisualTermOption( term ) {
+		const swatchStyle = getVisualTermSwatchStyle( term.visual );
+		const swatchClass =
+			'wc-admin-color-swatch' + ( swatchStyle ? '' : ' is-empty' );
+		const wrapper = document.createElement( 'span' );
+		wrapper.className = 'wc-admin-visual-attribute-term-option';
+
+		const swatch = document.createElement( 'span' );
+		swatch.className = swatchClass;
+		if ( swatchStyle ) {
+			swatch.setAttribute( 'style', swatchStyle );
+		}
+		swatch.setAttribute( 'aria-hidden', 'true' );
+
+		const label = document.createElement( 'span' );
+		label.className = 'wc-admin-visual-attribute-term-label';
+		label.textContent = term.text;
+
+		wrapper.appendChild( swatch );
+		wrapper.appendChild( label );
+
+		return wrapper;
+	}
+
+	function getTermDataWithVisual( $select, data ) {
+		if ( data.visual ) {
+			return data;
+		}
+
+		const $option = $select.find( 'option' ).filter( function () {
+			return $( this ).val() === String( data.id );
+		} );
+
+		if ( ! $option.length ) {
+			return data;
+		}
+
+		const visual = $option.data( 'visual' );
+
+		if ( ! visual ) {
+			return data;
+		}
+
+		return Object.assign( {}, data, { visual: visual } );
+	}
+
+	function updateVisualAttributeTermChoices( $select ) {
+		var $container = $select.next( '.select2-container' );
+
+		if ( ! $container.length ) {
+			return;
+		}
+
+		$container.find( '.select2-selection__choice' ).each( function () {
+			const $choice = $( this );
+			const data = $choice.data( 'data' );
+
+			if ( ! data ) {
+				return;
+			}
+
+			const $removeButton = $choice
+				.find( '.select2-selection__choice__remove' )
+				.detach();
+			const termData = getTermDataWithVisual( $select, data );
+
+			$choice
+				.empty()
+				.append( $removeButton )
+				.append( formatVisualTermOption( termData ) );
+			$choice.prop( 'title', termData.title || termData.text );
+		} );
+	}

 	function getEnhancedSelectFormatString() {
 		return {
-			'language': {
-				errorLoading: function() {
+			language: {
+				errorLoading: function () {
 					// Workaround for https://github.com/select2/select2/issues/4355 instead of i18n_ajax_error.
 					return wc_enhanced_select_params.i18n_searching;
 				},
-				inputTooLong: function( args ) {
+				inputTooLong: function ( args ) {
 					var overChars = args.input.length - args.maximum;

 					if ( 1 === overChars ) {
 						return wc_enhanced_select_params.i18n_input_too_long_1;
 					}

-					return wc_enhanced_select_params.i18n_input_too_long_n.replace( '%qty%', overChars );
+					return wc_enhanced_select_params.i18n_input_too_long_n.replace(
+						'%qty%',
+						overChars
+					);
 				},
-				inputTooShort: function( args ) {
+				inputTooShort: function ( args ) {
 					var remainingChars = args.minimum - args.input.length;

 					if ( 1 === remainingChars ) {
 						return wc_enhanced_select_params.i18n_input_too_short_1;
 					}

-					return wc_enhanced_select_params.i18n_input_too_short_n.replace( '%qty%', remainingChars );
+					return wc_enhanced_select_params.i18n_input_too_short_n.replace(
+						'%qty%',
+						remainingChars
+					);
 				},
-				loadingMore: function() {
+				loadingMore: function () {
 					return wc_enhanced_select_params.i18n_load_more;
 				},
-				maximumSelected: function( args ) {
+				maximumSelected: function ( args ) {
 					if ( args.maximum === 1 ) {
 						return wc_enhanced_select_params.i18n_selection_too_long_1;
 					}

-					return wc_enhanced_select_params.i18n_selection_too_long_n.replace( '%qty%', args.maximum );
+					return wc_enhanced_select_params.i18n_selection_too_long_n.replace(
+						'%qty%',
+						args.maximum
+					);
 				},
-				noResults: function() {
+				noResults: function () {
 					return wc_enhanced_select_params.i18n_no_matches;
 				},
-				searching: function() {
+				searching: function () {
 					return wc_enhanced_select_params.i18n_searching;
-				}
-			}
+				},
+			},
 		};
 	}

 	try {
 		$( document.body )
-
-			.on( 'wc-enhanced-select-init', function() {
-
+			.on( 'wc-enhanced-select-init', function () {
 				// Regular select boxes
-				$( ':input.wc-enhanced-select, :input.chosen_select' ).filter( ':not(.enhanced)' ).each( function() {
-					var select2_args = $.extend({
-						minimumResultsForSearch: 10,
-						allowClear:  $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder: $( this ).data( 'placeholder' )
-					}, getEnhancedSelectFormatString() );
-
-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-				});
-
-				$( ':input.wc-enhanced-select-nostd, :input.chosen_select_nostd' ).filter( ':not(.enhanced)' ).each( function() {
-					var select2_args = $.extend({
-						minimumResultsForSearch: 10,
-						allowClear:  true,
-						placeholder: $( this ).data( 'placeholder' )
-					}, getEnhancedSelectFormatString() );
-
-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-				});
+				$( ':input.wc-enhanced-select, :input.chosen_select' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var select2_args = $.extend(
+							{
+								minimumResultsForSearch: 10,
+								allowClear: $( this ).data( 'allow_clear' )
+									? true
+									: false,
+								placeholder: $( this ).data( 'placeholder' ),
+							},
+							getEnhancedSelectFormatString()
+						);
+
+						$( this )
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+					} );
+
+				$(
+					':input.wc-enhanced-select-nostd, :input.chosen_select_nostd'
+				)
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var select2_args = $.extend(
+							{
+								minimumResultsForSearch: 10,
+								allowClear: true,
+								placeholder: $( this ).data( 'placeholder' ),
+							},
+							getEnhancedSelectFormatString()
+						);
+
+						$( this )
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+					} );

 				function display_result( self, select2_args ) {
-					select2_args = $.extend( select2_args, getEnhancedSelectFormatString() );
+					select2_args = $.extend(
+						select2_args,
+						getEnhancedSelectFormatString()
+					);

 					$( self ).selectWoo( select2_args ).addClass( 'enhanced' );

 					if ( $( self ).data( 'sortable' ) ) {
-						var $select = $(self);
-						var $list   = $( self ).next( '.select2-container' ).find( 'ul.select2-selection__rendered' );
-
-						$list.sortable({
-							placeholder : 'ui-state-highlight select2-selection__choice',
+						var $select = $( self );
+						var $list = $( self )
+							.next( '.select2-container' )
+							.find( 'ul.select2-selection__rendered' );
+
+						$list.sortable( {
+							placeholder:
+								'ui-state-highlight select2-selection__choice',
 							forcePlaceholderSize: true,
-							items       : 'li:not(.select2-search__field)',
-							tolerance   : 'pointer',
-							stop: function() {
-								$( $list.find( '.select2-selection__choice' ).get().reverse() ).each( function() {
-									var id     = $( this ).data( 'data' ).id;
-									var option = $select.find( 'option[value="' + id + '"]' )[0];
+							items: 'li:not(.select2-search__field)',
+							tolerance: 'pointer',
+							stop: function () {
+								$(
+									$list
+										.find( '.select2-selection__choice' )
+										.get()
+										.reverse()
+								).each( function () {
+									var id = $( this ).data( 'data' ).id;
+									var option = $select.find(
+										'option[value="' + id + '"]'
+									)[ 0 ];
 									$select.prepend( option );
 								} );
-							}
-						});
-					// Keep multiselects ordered alphabetically if they are not sortable.
+							},
+						} );
+						// Keep multiselects ordered alphabetically if they are not sortable.
 					} else if ( $( self ).prop( 'multiple' ) ) {
-						$( self ).on( 'change', function(){
+						$( self ).on( 'change', function () {
 							var $children = $( self ).children();
-							$children.sort(function(a, b){
+							$children.sort( function ( a, b ) {
 								var atext = a.text.toLowerCase();
 								var btext = b.text.toLowerCase();

@@ -109,308 +240,487 @@ jQuery( function( $ ) {
 									return -1;
 								}
 								return 0;
-							});
+							} );
 							$( self ).html( $children );
-						});
+						} );
 					}
 				}

 				// Ajax product search box
-				$( ':input.wc-product-search' ).filter( ':not(.enhanced)' ).each( function() {
-					var select2_args = {
-						allowClear:  $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder: $( this ).data( 'placeholder' ),
-						minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3',
-						escapeMarkup: function( m ) {
-							return m;
-						},
-						ajax: {
-							url:         wc_enhanced_select_params.ajax_url,
-							dataType:    'json',
-							delay:       250,
-							data:        function( params ) {
-								return {
-									term         : params.term,
-									action       : $( this ).data( 'action' ) || 'woocommerce_json_search_products_and_variations',
-									security     : wc_enhanced_select_params.search_products_nonce,
-									exclude      : $( this ).data( 'exclude' ),
-									exclude_type : $( this ).data( 'exclude_type' ),
-									include      : $( this ).data( 'include' ),
-									limit        : $( this ).data( 'limit' ),
-									display_stock: $( this ).data( 'display_stock' )
-								};
+				$( ':input.wc-product-search' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var select2_args = {
+							allowClear: $( this ).data( 'allow_clear' )
+								? true
+								: false,
+							placeholder: $( this ).data( 'placeholder' ),
+							minimumInputLength: $( this ).data(
+								'minimum_input_length'
+							)
+								? $( this ).data( 'minimum_input_length' )
+								: '3',
+							escapeMarkup: function ( m ) {
+								return m;
 							},
-							processResults: function( data ) {
-								var terms = [];
-								if ( data ) {
-									$.each( data, function( id, text ) {
-										terms.push( { id: id, text: text } );
-									});
-								}
-								return {
-									results: terms
-								};
+							ajax: {
+								url: wc_enhanced_select_params.ajax_url,
+								dataType: 'json',
+								delay: 250,
+								data: function ( params ) {
+									return {
+										term: params.term,
+										action:
+											$( this ).data( 'action' ) ||
+											'woocommerce_json_search_products_and_variations',
+										security:
+											wc_enhanced_select_params.search_products_nonce,
+										exclude: $( this ).data( 'exclude' ),
+										exclude_type:
+											$( this ).data( 'exclude_type' ),
+										include: $( this ).data( 'include' ),
+										limit: $( this ).data( 'limit' ),
+										display_stock:
+											$( this ).data( 'display_stock' ),
+									};
+								},
+								processResults: function ( data ) {
+									var terms = [];
+									if ( data ) {
+										$.each( data, function ( id, text ) {
+											terms.push( {
+												id: id,
+												text: text,
+											} );
+										} );
+									}
+									return {
+										results: terms,
+									};
+								},
+								cache: true,
 							},
-							cache: true
-						}
-					};
+						};

-					display_result( this, select2_args );
-				});
+						display_result( this, select2_args );
+					} );

 				// Ajax Page Search.
-				$( ':input.wc-page-search' ).filter( ':not(.enhanced)' ).each( function() {
-					var select2_args = {
-						allowClear:  $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder: $( this ).data( 'placeholder' ),
-						minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3',
-						escapeMarkup: function( m ) {
-							return m;
-						},
-						ajax: {
-							url:         wc_enhanced_select_params.ajax_url,
-							dataType:    'json',
-							delay:       250,
-							data:        function( params ) {
-								return {
-									term         : params.term,
-									action       : $( this ).data( 'action' ) || 'woocommerce_json_search_pages',
-									security     : wc_enhanced_select_params.search_pages_nonce,
-									exclude      : $( this ).data( 'exclude' ),
-									post_status  : $( this ).data( 'post_status' ),
-									limit        : $( this ).data( 'limit' ),
-								};
+				$( ':input.wc-page-search' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var select2_args = {
+							allowClear: $( this ).data( 'allow_clear' )
+								? true
+								: false,
+							placeholder: $( this ).data( 'placeholder' ),
+							minimumInputLength: $( this ).data(
+								'minimum_input_length'
+							)
+								? $( this ).data( 'minimum_input_length' )
+								: '3',
+							escapeMarkup: function ( m ) {
+								return m;
 							},
-							processResults: function( data ) {
-								var terms = [];
-								if ( data ) {
-									$.each( data, function( id, text ) {
-										terms.push( { id: id, text: text } );
-									} );
-								}
-								return {
-									results: terms
-								};
+							ajax: {
+								url: wc_enhanced_select_params.ajax_url,
+								dataType: 'json',
+								delay: 250,
+								data: function ( params ) {
+									return {
+										term: params.term,
+										action:
+											$( this ).data( 'action' ) ||
+											'woocommerce_json_search_pages',
+										security:
+											wc_enhanced_select_params.search_pages_nonce,
+										exclude: $( this ).data( 'exclude' ),
+										post_status:
+											$( this ).data( 'post_status' ),
+										limit: $( this ).data( 'limit' ),
+									};
+								},
+								processResults: function ( data ) {
+									var terms = [];
+									if ( data ) {
+										$.each( data, function ( id, text ) {
+											terms.push( {
+												id: id,
+												text: text,
+											} );
+										} );
+									}
+									return {
+										results: terms,
+									};
+								},
+								cache: true,
 							},
-							cache: true
-						}
-					};
+						};

-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-				});
+						$( this )
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+					} );

 				// Ajax customer search boxes
-				$( ':input.wc-customer-search' ).filter( ':not(.enhanced)' ).each( function() {
-					var select2_args = {
-						allowClear:  $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder: $( this ).data( 'placeholder' ),
-						minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '1',
-						escapeMarkup: function( m ) {
-							return m;
-						},
-						ajax: {
-							url:         wc_enhanced_select_params.ajax_url,
-							dataType:    'json',
-							delay:       1000,
-							data:        function( params ) {
-								return {
-									term:     params.term,
-									action:   'woocommerce_json_search_customers',
-									security: wc_enhanced_select_params.search_customers_nonce,
-									exclude:  $( this ).data( 'exclude' )
-								};
+				$( ':input.wc-customer-search' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var select2_args = {
+							allowClear: $( this ).data( 'allow_clear' )
+								? true
+								: false,
+							placeholder: $( this ).data( 'placeholder' ),
+							minimumInputLength: $( this ).data(
+								'minimum_input_length'
+							)
+								? $( this ).data( 'minimum_input_length' )
+								: '1',
+							escapeMarkup: function ( m ) {
+								return m;
 							},
-							processResults: function( data ) {
-								var terms = [];
-								if ( data ) {
-									$.each( data, function( id, text ) {
-										terms.push({
-											id: id,
-											text: text
-										});
-									});
-								}
-								return {
-									results: terms
-								};
+							ajax: {
+								url: wc_enhanced_select_params.ajax_url,
+								dataType: 'json',
+								delay: 1000,
+								data: function ( params ) {
+									return {
+										term: params.term,
+										action: 'woocommerce_json_search_customers',
+										security:
+											wc_enhanced_select_params.search_customers_nonce,
+										exclude: $( this ).data( 'exclude' ),
+									};
+								},
+								processResults: function ( data ) {
+									var terms = [];
+									if ( data ) {
+										$.each( data, function ( id, text ) {
+											terms.push( {
+												id: id,
+												text: text,
+											} );
+										} );
+									}
+									return {
+										results: terms,
+									};
+								},
+								cache: true,
 							},
-							cache: true
+						};
+
+						select2_args = $.extend(
+							select2_args,
+							getEnhancedSelectFormatString()
+						);
+
+						$( this )
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+
+						if ( $( this ).data( 'sortable' ) ) {
+							var $select = $( this );
+							var $list = $( this )
+								.next( '.select2-container' )
+								.find( 'ul.select2-selection__rendered' );
+
+							$list.sortable( {
+								placeholder:
+									'ui-state-highlight select2-selection__choice',
+								forcePlaceholderSize: true,
+								items: 'li:not(.select2-search__field)',
+								tolerance: 'pointer',
+								stop: function () {
+									$(
+										$list
+											.find(
+												'.select2-selection__choice'
+											)
+											.get()
+											.reverse()
+									).each( function () {
+										var id = $( this ).data( 'data' ).id;
+										var option = $select.find(
+											'option[value="' + id + '"]'
+										)[ 0 ];
+										$select.prepend( option );
+									} );
+								},
+							} );
 						}
-					};
-
-					select2_args = $.extend( select2_args, getEnhancedSelectFormatString() );
+					} );

-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-
-					if ( $( this ).data( 'sortable' ) ) {
-						var $select = $(this);
-						var $list   = $( this ).next( '.select2-container' ).find( 'ul.select2-selection__rendered' );
+				// Ajax category search boxes
+				$( ':input.wc-category-search' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var return_format = $( this ).data( 'return_id' )
+							? 'id'
+							: 'slug';
+
+						var select2_args = $.extend(
+							{
+								allowClear: $( this ).data( 'allow_clear' )
+									? true
+									: false,
+								placeholder: $( this ).data( 'placeholder' ),
+								minimumInputLength: $( this ).data(
+									'minimum_input_length'
+								)
+									? $( this ).data( 'minimum_input_length' )
+									: '3',
+								escapeMarkup: function ( m ) {
+									return m;
+								},
+								ajax: {
+									url: wc_enhanced_select_params.ajax_url,
+									dataType: 'json',
+									delay: 250,
+									data: function ( params ) {
+										return {
+											term: params.term,
+											action: 'woocommerce_json_search_categories',
+											security:
+												wc_enhanced_select_params.search_categories_nonce,
+										};
+									},
+									processResults: function ( data ) {
+										var terms = [];
+										if ( data ) {
+											$.each(
+												data,
+												function ( id, term ) {
+													terms.push( {
+														id:
+															'id' ===
+															return_format
+																? term.term_id
+																: term.slug,
+														text: term.formatted_name,
+													} );
+												}
+											);
+										}
+										return {
+											results: terms,
+										};
+									},
+									cache: true,
+								},
+							},
+							getEnhancedSelectFormatString()
+						);

-						$list.sortable({
-							placeholder : 'ui-state-highlight select2-selection__choice',
-							forcePlaceholderSize: true,
-							items       : 'li:not(.select2-search__field)',
-							tolerance   : 'pointer',
-							stop: function() {
-								$( $list.find( '.select2-selection__choice' ).get().reverse() ).each( function() {
-									var id     = $( this ).data( 'data' ).id;
-									var option = $select.find( 'option[value="' + id + '"]' )[0];
-									$select.prepend( option );
-								} );
-							}
-						});
-					}
-				});
+						$( this )
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+					} );

 				// Ajax category search boxes
-				$( ':input.wc-category-search' ).filter( ':not(.enhanced)' ).each( function() {
-					var return_format = $( this ).data( 'return_id' ) ? 'id' : 'slug';
-
-					var select2_args = $.extend( {
-						allowClear        : $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder       : $( this ).data( 'placeholder' ),
-						minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3',
-						escapeMarkup      : function( m ) {
-							return m;
-						},
-						ajax: {
-							url:         wc_enhanced_select_params.ajax_url,
-							dataType:    'json',
-							delay:       250,
-							data:        function( params ) {
-								return {
-									term:     params.term,
-									action:   'woocommerce_json_search_categories',
-									security: wc_enhanced_select_params.search_categories_nonce
-								};
+				$( ':input.wc-taxonomy-term-search' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var return_format = $( this ).data( 'return_id' )
+							? 'id'
+							: 'slug';
+						var isVisualAttribute =
+							'yes' === $( this ).data( 'isVisualAttribute' );
+
+						var select2_args = $.extend(
+							{
+								allowClear: $( this ).data( 'allow_clear' )
+									? true
+									: false,
+								placeholder: $( this ).data( 'placeholder' ),
+								minimumInputLength:
+									$( this ).data( 'minimum_input_length' ) !==
+										null &&
+									$( this ).data( 'minimum_input_length' ) !==
+										undefined
+										? $( this ).data(
+												'minimum_input_length'
+										  )
+										: '3',
+								escapeMarkup: function ( m ) {
+									return m;
+								},
+								ajax: {
+									url: wc_enhanced_select_params.ajax_url,
+									dataType: 'json',
+									delay: 250,
+									data: function ( params ) {
+										return {
+											taxonomy:
+												$( this ).data( 'taxonomy' ),
+											limit: $( this ).data( 'limit' ),
+											orderby:
+												$( this ).data( 'orderby' ),
+											term: params.term,
+											action: 'woocommerce_json_search_taxonomy_terms',
+											security:
+												wc_enhanced_select_params.search_taxonomy_terms_nonce,
+										};
+									},
+									processResults: function ( data ) {
+										var terms = [];
+										if ( data ) {
+											$.each(
+												data,
+												function ( id, term ) {
+													var termData = {
+														id:
+															'id' ===
+															return_format
+																? term.term_id
+																: term.slug,
+														text: term.name,
+													};
+
+													if ( isVisualAttribute ) {
+														termData.visual =
+															term.visual || {
+																type: 'none',
+																value: '',
+															};
+													}
+
+													terms.push( termData );
+												}
+											);
+										}
+										return {
+											results: terms,
+										};
+									},
+									cache: true,
+								},
 							},
-							processResults: function( data ) {
-								var terms = [];
-								if ( data ) {
-									$.each( data, function( id, term ) {
-										terms.push({
-											id:   'id' === return_format ? term.term_id : term.slug,
-											text: term.formatted_name
-										});
-									});
+							getEnhancedSelectFormatString()
+						);
+
+						if ( isVisualAttribute ) {
+							select2_args.templateResult = function ( term ) {
+								if ( term.loading ) {
+									return term.text;
 								}
-								return {
-									results: terms
-								};
-							},
-							cache: true
+
+								return formatVisualTermOption( term );
+							};
 						}
-					}, getEnhancedSelectFormatString() );

-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-				});
+						var $select = $( this );

-				// Ajax category search boxes
-				$( ':input.wc-taxonomy-term-search' ).filter( ':not(.enhanced)' ).each( function() {
-					var return_format = $( this ).data( 'return_id' ) ? 'id' : 'slug';
-
-					var select2_args = $.extend( {
-						allowClear        : $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder       : $( this ).data( 'placeholder' ),
-						minimumInputLength: $( this ).data( 'minimum_input_length' ) !== null && $( this ).data( 'minimum_input_length' ) !== undefined ? $( this ).data( 'minimum_input_length' ) : '3',
-						escapeMarkup      : function( m ) {
-							return m;
-						},
-						ajax: {
-							url:         wc_enhanced_select_params.ajax_url,
-							dataType:    'json',
-							delay:       250,
-							data:        function( params ) {
-								return {
-									taxonomy: $( this ).data( 'taxonomy' ),
-									limit:    $( this ).data( 'limit' ),
-									orderby:  $( this ).data( 'orderby'),
-									term:     params.term,
-									action:   'woocommerce_json_search_taxonomy_terms',
-									security: wc_enhanced_select_params.search_taxonomy_terms_nonce
-								};
-							},
-							processResults: function( data ) {
-								var terms = [];
-								if ( data ) {
-									$.each( data, function( id, term ) {
-										terms.push({
-											id:   'id' === return_format ? term.term_id : term.slug,
-											text: term.name
-										});
-									});
+						$select
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+
+						if ( isVisualAttribute ) {
+							updateVisualAttributeTermChoices( $select );
+
+							$select.on(
+								'change.wcVisualAttributeTerms select2:select.wcVisualAttributeTerms',
+								function () {
+									updateVisualAttributeTermChoices( $select );
 								}
-								return {
-									results: terms
-								};
-							},
-							cache: true
+							);
 						}
-					}, getEnhancedSelectFormatString() );
-
-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-				});
-
-				$( ':input.wc-attribute-search' ).filter( ':not(.enhanced)' ).each( function() {
-					var select2Element = this;
-					var select2_args = $.extend( {
-						allowClear        : $( this ).data( 'allow_clear' ) ? true : false,
-						placeholder       : $( this ).data( 'placeholder' ),
-						minimumInputLength: $( this ).data( 'minimum_input_length' ) !== null && $( this ).data( 'minimum_input_length' ) !== undefined ? $( this ).data( 'minimum_input_length' ) : '3',
-						escapeMarkup      : function( m ) {
-							return m;
-						},
-						ajax: {
-							url:         wc_enhanced_select_params.ajax_url,
-							dataType:    'json',
-							delay:       250,
-							data:        function( params ) {
-								return {
-									term:     params.term,
-									action:   'woocommerce_json_search_product_attributes',
-									security: wc_enhanced_select_params.search_product_attributes_nonce
-								};
+					} );
+
+				$( ':input.wc-attribute-search' )
+					.filter( ':not(.enhanced)' )
+					.each( function () {
+						var select2Element = this;
+						var select2_args = $.extend(
+							{
+								allowClear: $( this ).data( 'allow_clear' )
+									? true
+									: false,
+								placeholder: $( this ).data( 'placeholder' ),
+								minimumInputLength:
+									$( this ).data( 'minimum_input_length' ) !==
+										null &&
+									$( this ).data( 'minimum_input_length' ) !==
+										undefined
+										? $( this ).data(
+												'minimum_input_length'
+										  )
+										: '3',
+								escapeMarkup: function ( m ) {
+									return m;
+								},
+								ajax: {
+									url: wc_enhanced_select_params.ajax_url,
+									dataType: 'json',
+									delay: 250,
+									data: function ( params ) {
+										return {
+											term: params.term,
+											action: 'woocommerce_json_search_product_attributes',
+											security:
+												wc_enhanced_select_params.search_product_attributes_nonce,
+										};
+									},
+									processResults: function ( data ) {
+										var disabledItems =
+											$( select2Element ).data(
+												'disabled-items'
+											) || [];
+										var terms = [];
+										if ( data ) {
+											$.each(
+												data,
+												function ( id, term ) {
+													terms.push( {
+														id: term.slug,
+														text: term.name,
+														disabled:
+															disabledItems.includes(
+																term.slug
+															),
+													} );
+												}
+											);
+										}
+										return {
+											results: terms,
+										};
+									},
+									cache: true,
+								},
 							},
-							processResults: function( data ) {
-								var disabledItems = $( select2Element ).data('disabled-items') || [];
-								var terms = [];
-								if ( data ) {
-									$.each( data, function( id, term ) {
-										terms.push({
-											id:   term.slug,
-											text: term.name,
-											disabled: disabledItems.includes( term.slug )
-										});
-									});
-								}
-								return {
-									results: terms
-								};
-							},
-							cache: true
-						}
-					}, getEnhancedSelectFormatString() );
+							getEnhancedSelectFormatString()
+						);

-					$( this ).selectWoo( select2_args ).addClass( 'enhanced' );
-				});
-			})
+						$( this )
+							.selectWoo( select2_args )
+							.addClass( 'enhanced' );
+					} );
+			} )

 			// WooCommerce Backbone Modal
-			.on( 'wc_backbone_modal_before_remove', function() {
-				$( '.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search' ).filter( '.select2-hidden-accessible' )
+			.on( 'wc_backbone_modal_before_remove', function () {
+				$(
+					'.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search'
+				)
+					.filter( '.select2-hidden-accessible' )
 					.selectWoo( 'close' );
-			})
+			} )

 			.trigger( 'wc-enhanced-select-init' );

-		$( 'html' ).on( 'click', function( event ) {
+		$( 'html' ).on( 'click', function ( event ) {
 			if ( this === event.target ) {
-				$( '.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search' ).filter( '.select2-hidden-accessible' )
+				$(
+					'.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search'
+				)
+					.filter( '.select2-hidden-accessible' )
 					.selectWoo( 'close' );
 			}
 		} );
-	} catch( err ) {
+	} catch ( err ) {
 		// If select2 failed (conflict?) log the error but don't stop other scripts breaking.
 		window.console.log( err );
 	}
-});
+} );
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
index 163d8921209..b076419367f 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
@@ -8,6 +8,8 @@
 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
 }
+
+use Automattic\WooCommerce\Internal\ProductAttributes\VisualAttributeTermMeta;
 ?>

 <table cellpadding="0" cellspacing="0">
@@ -39,7 +41,8 @@ if ( ! defined( 'ABSPATH' ) ) {
 					'select' === $attribute_taxonomy->attribute_type ||
 					'wc-visual' === $attribute_taxonomy->attribute_type
 				) {
-					$attribute_orderby = ! empty( $attribute_taxonomy->attribute_orderby ) ? $attribute_taxonomy->attribute_orderby : 'name';
+					$is_visual_attribute = 'wc-visual' === $attribute_taxonomy->attribute_type;
+					$attribute_orderby   = ! empty( $attribute_taxonomy->attribute_orderby ) ? $attribute_taxonomy->attribute_orderby : 'name';
 					/**
 					* Filter the length (number of terms) rendered in the list.
 					*
@@ -55,11 +58,39 @@ if ( ! defined( 'ABSPATH' ) ) {
 							data-orderby="<?php echo esc_attr( $attribute_orderby ); ?>"
 							class="multiselect attribute_values wc-taxonomy-term-search"
 							name="attribute_values[<?php echo esc_attr( $i ); ?>][]"
-							data-taxonomy="<?php echo esc_attr( $attribute->get_taxonomy() ); ?>">
+							data-taxonomy="<?php echo esc_attr( $attribute->get_taxonomy() ); ?>"
+							data-is-visual-attribute="<?php echo esc_attr( wc_bool_to_string( $is_visual_attribute ) ); ?>">
 						<?php
 						$selected_terms = $attribute->get_terms();
+						$term_visuals   = array();
+
+						if ( $selected_terms && $is_visual_attribute ) {
+							$term_visuals = VisualAttributeTermMeta::get_term_visuals( wp_list_pluck( $selected_terms, 'term_id' ) );
+						}
+
 						if ( $selected_terms ) {
 							foreach ( $selected_terms as $selected_term ) {
+								$option_attributes = array(
+									'value'    => $selected_term->term_id,
+									'selected' => 'selected',
+								);
+
+								if ( $is_visual_attribute ) {
+									$option_attributes['data-visual'] = wp_json_encode(
+										$term_visuals[ $selected_term->term_id ] ?? VisualAttributeTermMeta::get_empty_visual()
+									);
+								}
+
+								$option_attribute_string = '';
+
+								foreach ( $option_attributes as $attribute_name => $attribute_value ) {
+									$option_attribute_string .= sprintf(
+										' %1$s="%2$s"',
+										esc_attr( $attribute_name ),
+										esc_attr( $attribute_value )
+									);
+								}
+
 								/**
 								 * Filter the selected attribute term name.
 								 *
@@ -67,7 +98,7 @@ if ( ! defined( 'ABSPATH' ) ) {
 								 * @param string  $name Name of selected term.
 								 * @param array   $term The selected term object.
 								 */
-								echo '<option value="' . esc_attr( $selected_term->term_id ) . '" selected="selected">' . esc_html( apply_filters( 'woocommerce_product_attribute_term_name', $selected_term->name, $selected_term ) ) . '</option>';
+								echo '<option' . $option_attribute_string . '>' . esc_html( apply_filters( 'woocommerce_product_attribute_term_name', $selected_term->name, $selected_term ) ) . '</option>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 							}
 						}
 						?>
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php
index 8fb230e3ce2..618d5b31042 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute.php
@@ -2,6 +2,9 @@
 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
 }
+if ( ! isset( $attribute ) ) {
+	return;
+}
 ?>
 <div data-taxonomy="<?php echo esc_attr( $attribute->get_taxonomy() ); ?>" class="woocommerce_attribute wc-metabox postbox closed <?php echo esc_attr( implode( ' ', $metabox_class ) ); ?>" rel="<?php echo esc_attr( $attribute->get_position() ); ?>">
 	<h3>
@@ -9,6 +12,13 @@ if ( ! defined( 'ABSPATH' ) ) {
 		<div class="tips sort" data-tip="<?php esc_attr_e( 'Drag and drop to set admin attribute order', 'woocommerce' ); ?>"></div>
 		<a href="#" class="remove_row delete"><?php esc_html_e( 'Remove', 'woocommerce' ); ?></a>
 		<strong class="attribute_name<?php echo esc_attr( $attribute->get_name() === '' ? ' placeholder' : '' ); ?>"><?php echo esc_html( $attribute->get_name() !== '' ? wc_attribute_label( $attribute->get_name() ) : __( 'New attribute', 'woocommerce' ) ); ?></strong>
+		<?php if ( $attribute->is_taxonomy() ) : ?>
+			<?php
+			/* translators: 'Global' refers to 'global attribute'. */
+			$global_attribute_badge_label = __( 'Global', 'woocommerce' );
+			?>
+			<span class="woocommerce-attribute-global-badge"><?php echo esc_html( $global_attribute_badge_label ); ?></span>
+		<?php endif; ?>
 	</h3>
 	<div class="woocommerce_attribute_data wc-metabox-content hidden">
 		<?php require __DIR__ . '/html-product-attribute-inner.php'; ?>
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php
index 3a6ccd62c7f..5716ddbf412 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php
@@ -45,8 +45,12 @@ $product_attributes = $product_object->get_attributes( 'edit' );
 			<a href="#" class="expand_all"><?php esc_html_e( 'Expand', 'woocommerce' ); ?></a> / <a href="#" class="close_all"><?php esc_html_e( 'Close', 'woocommerce' ); ?></a>
 		</span>
 		<div class="actions">
+			<?php
+			/* translators: 'Global' refers to 'global attribute'. */
+			$add_global_attribute_placeholder = __( 'Add global', 'woocommerce' );
+			?>
 			<button type="button" class="button add_custom_attribute"><?php esc_html_e( 'Add new', 'woocommerce' ); ?></button>
-			<select class="wc-attribute-search" data-placeholder="<?php esc_attr_e( 'Add existing', 'woocommerce' ); ?>" data-minimum-input-length="0">
+			<select class="wc-attribute-search" data-placeholder="<?php echo esc_attr( $add_global_attribute_placeholder ); ?>" data-minimum-input-length="0">
 			</select>
 		</div>
 	</div>
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index 5ca71e3c2d2..4dea2ad4f33 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -771,13 +771,26 @@ class WC_AJAX {
 					VisualAttributeTermMeta::save_term_visual_from_request( (int) $result['term_id'], $taxonomy, $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

 					$term = get_term_by( 'id', $result['term_id'], $taxonomy );
-					wp_send_json(
-						array(
-							'term_id' => $term->term_id,
-							'name'    => $term->name,
-							'slug'    => $term->slug,
-						)
+
+					if ( ! $term ) {
+						wp_send_json(
+							array(
+								'error' => __( 'Term not found', 'woocommerce' ),
+							)
+						);
+					}
+
+					$response = array(
+						'term_id' => $term->term_id,
+						'name'    => $term->name,
+						'slug'    => $term->slug,
 					);
+
+					if ( VisualAttributeTermMeta::is_visual_attribute_taxonomy( $taxonomy ) ) {
+						$response['visual'] = VisualAttributeTermMeta::get_term_visual( (int) $term->term_id );
+					}
+
+					wp_send_json( $response );
 				}//end if
 			}//end if
 		}//end if
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 52d25f58cee..ffbbe1e8be3 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -6132,12 +6132,6 @@ parameters:
 			count: 9
 			path: includes/admin/meta-boxes/views/html-product-attribute-inner.php

-		-
-			message: '#^Variable \$attribute might not be defined\.$#'
-			identifier: variable.undefined
-			count: 5
-			path: includes/admin/meta-boxes/views/html-product-attribute.php
-
 		-
 			message: '#^Variable \$metabox_class might not be defined\.$#'
 			identifier: variable.undefined
@@ -8526,36 +8520,18 @@ parameters:
 			count: 4
 			path: includes/class-wc-ajax.php

-		-
-			message: '#^Cannot access property \$name on WP_Term\|false\.$#'
-			identifier: property.nonObject
-			count: 1
-			path: includes/class-wc-ajax.php
-
 		-
 			message: '#^Cannot access property \$parent on WP_Error\|WP_Term\|null\.$#'
 			identifier: property.nonObject
 			count: 1
 			path: includes/class-wc-ajax.php

-		-
-			message: '#^Cannot access property \$slug on WP_Term\|false\.$#'
-			identifier: property.nonObject
-			count: 1
-			path: includes/class-wc-ajax.php
-
 		-
 			message: '#^Cannot access property \$term_id on WP_Error\|WP_Term\|null\.$#'
 			identifier: property.nonObject
 			count: 1
 			path: includes/class-wc-ajax.php

-		-
-			message: '#^Cannot access property \$term_id on WP_Term\|false\.$#'
-			identifier: property.nonObject
-			count: 1
-			path: includes/class-wc-ajax.php
-
 		-
 			message: '#^Cannot call method add_order_note\(\) on WC_Order\|WC_Order_Refund\|false\.$#'
 			identifier: method.nonObject
@@ -54825,7 +54801,6 @@ parameters:
 			count: 1
 			path: src/Blocks/QueryFilters.php

-
 		-
 			message: '#^Method Automattic\\WooCommerce\\Blocks\\Registry\\Container\:\:register\(\) has no return type specified\.$#'
 			identifier: missingType.return
diff --git a/plugins/woocommerce/src/Internal/ProductAttributes/VisualAttributeTermAdmin.php b/plugins/woocommerce/src/Internal/ProductAttributes/VisualAttributeTermAdmin.php
index ff60a0f5499..7d84015dc46 100644
--- a/plugins/woocommerce/src/Internal/ProductAttributes/VisualAttributeTermAdmin.php
+++ b/plugins/woocommerce/src/Internal/ProductAttributes/VisualAttributeTermAdmin.php
@@ -55,6 +55,44 @@ class VisualAttributeTermAdmin implements RegisterHooksInterface {
 		}

 		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_visual_attribute_script' ) );
+		add_filter( 'woocommerce_json_search_found_product_attribute_terms', array( $this, 'add_visual_data_to_attribute_terms' ), 10, 2 );
+	}
+
+	/**
+	 * Add visual swatch data to attribute term search results.
+	 *
+	 * @internal
+	 *
+	 * @param array|\WP_Error $terms    The list of matched terms.
+	 * @param string          $taxonomy The terms taxonomy.
+	 * @return array|\WP_Error
+	 */
+	public function add_visual_data_to_attribute_terms( $terms, string $taxonomy ) {
+		if ( is_wp_error( $terms ) || empty( $terms ) || ! VisualAttributeTermMeta::is_visual_attribute_taxonomy( $taxonomy ) ) {
+			return $terms;
+		}
+
+		// Because `$terms` might be filtered by a plugin, make sure we only operate on valid terms.
+		$valid_terms = array_filter(
+			$terms,
+			static function ( $term ) {
+				return is_object( $term ) && isset( $term->term_id );
+			}
+		);
+
+		$term_ids = array_map( 'intval', wp_list_pluck( $valid_terms, 'term_id' ) );
+		$visuals  = VisualAttributeTermMeta::get_term_visuals( $term_ids );
+
+		foreach ( $valid_terms as $term ) {
+			/**
+			 * Term object with dynamic visual property.
+			 *
+			 * @var \stdClass $term
+			 */
+			$term->visual = $visuals[ $term->term_id ] ?? VisualAttributeTermMeta::get_empty_visual();
+		}
+
+		return $terms;
 	}

 	/**