Display Product Variations Dropdown on the Shop Page
In this tutorial I am going to show how you can create your own variation dropdowns on the shop page (or any archive product page). When I’m saying “creating your own”, I mean it exactly, so we don’t just copy and paste a couple of hooks from single product pages.
Our another goal here is making sure that AJAX add to cart buttons are working great for our custom variation dropdowns as well.
Here on the screenshot I am showing what exactly we are going to create in this guide:

And also If you would like something more interesting, like on the screenshot below, with variation swatches, then consider checking my variation swatches plugin.

Now let’s start the coding. I decided to use woocommerce_loop_add_to_cart_link
filter hook because it allows to remove and modify “Select options” button and also to return “Add to cart” buttons for simple products without changes.
<?php
add_filter( 'woocommerce_loop_add_to_cart_link', 'rudr_select_variations_shop_page', 99, 2 );
function rudr_select_variations_shop_page( $add_to_cart, $product ) {
// do nothing in case it is not a variable product
if( ! $product->is_type( 'variable' ) ) {
return $add_to_cart;
}
// if there is no variations available for purchase, also do nothing
if( ! $variations = $product->get_available_variations() ) {
return $add_to_cart;
}
ob_start();
?>
<div class="rudr_variations" data-product_variations="<?php echo wc_esc_json( wp_json_encode( $variations ) ) ?>">
<?php
foreach( $product->get_attributes() as $attribute_name => $attribute ) :
?>
<div class="rudr-variation-select value">
<?php
wc_dropdown_variation_attribute_options(
array(
'show_option_none' => 'Select ' . strtolower( wc_attribute_label( $attribute_name ) ),
'options' => $attribute->get_slugs(),
'attribute' => $attribute_name,
'product' => $product
)
);
?>
</div>
<?php
endforeach;
?>
<a href="" data-product_id="" class="disabled button add_to_cart_button">Add to cart</a>
</div>
<?php
return ob_get_clean();
}
And also:
- I am storing the whole value of
$product->get_available_variations()
inside an HTML attribute like WooCommerce does on single product pages, but you can simplify that, because we only needattributes
,variation_id
,price_html
andimage
object properties. ob_start()
andob_end_clean()
are needed because we have to return (not print) the value towoocommerce_loop_add_to_cart_link
filter hook but the functionwc_dropdown_variation_attribute_options()
which is used inside always prints the dropdown.- And the last but not the least, you can see an AJAX add to cart button without
data-product_id
attribute and withoutajax_add_to_cart
class, more about how this button works, you can read in a separate tutorial here.
Do you think, that’s all?
Of course not, we also need some JavaScript as well.
jQuery( function( $ ) {
$( '.rudr-variation-select' ).find( 'select[data-attribute_name]' ).change( function() {
const el = $(this);
const variationsWrapper = el.closest( '.rudr_variations' );
const availableVariations = JSON.parse( variationsWrapper.attr( 'data-product_variations' ) );
const productWrapper = variationsWrapper.closest( 'li.product' );
const button = productWrapper.find( 'a.add_to_cart_button' );
// our matching variation will be stored in this object
let matchingVariation = {};
// now we can loop through all the variations and check them against the selected attributes
for( let i = 0; i < availableVariations.length; i++ ) {
// assign it as a matching variation in any case
matchingVariation = availableVariations[i];
// let's check all the attributes of this variation
for( const [ attr_name, attr_value ] of Object.entries( availableVariations[i].attributes ) ) {
// if any of attributes doesn't match
if( attr_value !== variationsWrapper.find( '[name="' + attr_name + '"]' ).val() ) {
matchingVariation = {};
}
}
// exit the loop if we have a matching variation
if( Object.keys( matchingVariation ).length !== 0 ) {
break;
}
}
// add a variation ID to the Add to cart button
if( matchingVariation.variation_id ) {
button.removeClass( 'disabled' ).addClass( 'ajax_add_to_cart' );
button.attr( 'data-product_id', matchingVariation.variation_id );
}
// update product price
if( matchingVariation.price_html ) {
productWrapper.find( 'span.price').replaceWith( matchingVariation.price_html );
}
// update product image
if( matchingVariation.image ) {
productWrapper.find( 'img' ).attr( {
src: matchingVariation.image.src,
srcset: matchingVariation.image.srcset,
width: matchingVariation.image.src_w,
height: matchingVariation.image.src_h
} );
}
} );
} );

Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
A good check here would be (Render attributes that has a price),
I normally got alot of attributes for filter use case or other reasons, so i dont want to render them.