Skip to content

Early-bird lifetime licence: $199 once, for the first 200 buyers only. See the offer

Let customers buy several sizes in one click (no plugin)

A WooCommerce variable product has one set of dropdowns and one quantity box. To buy two pairs in size 5 and four in size 7, a customer picks size 5, sets 2, adds to cart, goes back, picks size 7, sets 4, and adds to cart again. Wholesale buyers and parents shopping for a family give up.

The usual advice is to rebuild the product as a grouped product, which means turning 13 sizes into 13 separate products. Or buy a product-table plugin. You don’t need either.

What the snippet does

It replaces the dropdowns on the product page with a table: one row per size, with its price and its own quantity box. Sizes that are out of stock say so. One Add to cart button adds everything with a quantity above 0.

It uses WooCommerce’s own pieces throughout: its quantity boxes, cart, stock checks and “added to your cart” message. Stock limits, maximum quantities and other plugins’ add-to-cart checks still apply.

The code

<?php

// Product IDs that get the grid, e.g. array( 12, 34 ). Leave it empty for every variable product.
$only_products = array();

$grid = static function ( WC_Product_Variable $product ) {
	$attributes = $product->get_variation_attributes();
	$heading    = 1 === count( $attributes ) ? wc_attribute_label( (string) array_key_first( $attributes ), $product ) : __( 'Product', 'woocommerce' );

	$rows = '';
	foreach ( $product->get_children() as $variation_id ) {
		$variation = wc_get_product( $variation_id );
		if ( ! $variation instanceof WC_Product_Variation || ! $variation->variation_is_visible() ) {
			continue;
		}
		$quantity = esc_html__( 'Out of stock', 'woocommerce' );
		if ( $variation->is_purchasable() && $variation->is_in_stock() ) {
			$quantity = woocommerce_quantity_input(
				array(
					'input_name'  => 'size_grid_qty[' . $variation_id . ']',
					'input_value' => 0,
					'min_value'   => 0,
					'max_value'   => $variation->get_max_purchase_quantity(),
				),
				$variation,
				false
			);
		}
		$rows .= '<tr><td>' . esc_html( wc_get_formatted_variation( $variation, true, false ) ) . '</td><td>' . wp_kses_post( $variation->get_price_html() ) . '</td><td>' . $quantity . '</td></tr>';
	}

	return '<form class="size-grid" method="post" action="' . esc_url( get_permalink( $product->get_id() ) ) . '">'
		. '<table class="shop_table"><thead><tr><th>' . esc_html( $heading ) . '</th><th>' . esc_html__( 'Price', 'woocommerce' ) . '</th><th>' . esc_html__( 'Quantity', 'woocommerce' ) . '</th></tr></thead>'
		. '<tbody>' . $rows . '</tbody></table>'
		. '<input type="hidden" name="size_grid_product" value="' . absint( $product->get_id() ) . '">'
		. '<button type="submit" class="single_add_to_cart_button button alt">' . esc_html__( 'Add to cart', 'woocommerce' ) . '</button></form>';
};

// Swap the dropdowns for the grid on product pages.
add_action(
	'wp',
	static function () use ( $grid, $only_products ) {
		if ( ! function_exists( 'is_product' ) || ! is_product() ) {
			return;
		}
		$product = wc_get_product( get_queried_object_id() );
		if ( ! $product instanceof WC_Product_Variable || ( $only_products && ! in_array( $product->get_id(), $only_products, true ) ) ) {
			return;
		}
		remove_action( 'woocommerce_variable_add_to_cart', 'woocommerce_variable_add_to_cart', 30 );
		add_action(
			'woocommerce_variable_add_to_cart',
			static function () use ( $grid, $product ) {
				echo $grid( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped when built.
			},
			30
		);
	}
);

// Add every size with a quantity above 0 to the cart.
add_action(
	'wp_loaded',
	static function () {
		// phpcs:disable WordPress.Security.NonceVerification.Missing -- Like WooCommerce's own add-to-cart form, so it works on cached pages.
		if ( ! isset( $_POST['size_grid_product'], $_POST['size_grid_qty'] ) || ! function_exists( 'WC' ) || null === WC()->cart ) {
			return;
		}
		$product_id = absint( $_POST['size_grid_product'] );
		$quantities = map_deep( (array) wp_unslash( $_POST['size_grid_qty'] ), 'sanitize_text_field' );
		// phpcs:enable
		$added = array();
		foreach ( $quantities as $variation_id => $qty ) {
			$variation_id = absint( $variation_id );
			$qty          = is_scalar( $qty ) ? wc_stock_amount( $qty ) : 0;
			$variation    = $qty > 0 ? wc_get_product( $variation_id ) : null;
			if ( ! $variation instanceof WC_Product_Variation || $variation->get_parent_id() !== $product_id ) {
				continue;
			}
			$chosen = $variation->get_variation_attributes();
			if ( ! apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $qty, $variation_id, $chosen ) ) {
				continue;
			}
			if ( false !== WC()->cart->add_to_cart( $product_id, $qty, $variation_id, $chosen ) ) {
				$added[ $variation_id ] = $qty;
			}
		}
		if ( ! $added ) {
			if ( ! wc_notice_count( 'error' ) ) {
				wc_add_notice( 'Please enter a quantity for at least one size.', 'error' );
			}
			return;
		}
		wc_add_to_cart_message( $added, true );
		wp_safe_redirect( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ? wc_get_cart_url() : get_permalink( $product_id ) );
		exit;
	},
	20
);

Add it to your child theme’s functions.php or a code snippets plugin.

  • Only some products? Put their IDs in $only_products, e.g. array( 12, 34 ). Empty means every variable product.
  • Block themes: it works with the standard Single Product template (WooCommerce’s Add to Cart with Options block), which we tested with Twenty Twenty-Five.
  • “Any size” variations won’t work in a grid. Each row needs its own value, which is how size ranges are normally set up.

Why there’s no nonce

Forms usually carry a security token (nonce). WooCommerce’s own Add to cart form doesn’t, because adding to a cart isn’t a privileged action, and a token would break on cached product pages. This form follows WooCommerce. It still checks that every size belongs to the product being added.

Tested with

WordPress 7.1, WooCommerce 11.1, on Storefront and Twenty Twenty-Five, in a real browser: grid rendering, out-of-stock rows, adding two sizes at once, the cart contents, empty and invalid submissions.

In Snipfire, this is “Order several sizes at once” in the WooCommerce library. It arrives switched off so you can choose the products first.

Move your snippets over this afternoon.

Importing changes nothing until you switch over, and your old shortcodes keep working.