Show Stock Status in the WooCommerce Variation Dropdown (With an Out-of-Stock Class)

Quick Summary

  • Filter woocommerce_dropdown_variation_attribute_options_html to rebuild the variation dropdown with In Stock or Out of Stock after each option and an outofstock class on sold-out ones.
  • It only changes products with a single variation attribute, and a variation set to Any counts for every option without its own variation.
  • A disabled attribute added in PHP is cleared by WooCommerce's variation script, so use the woocommerce_variation_is_active filter to gray sold-out options out.

On a variable product, the WooCommerce variation dropdown doesn’t tell shoppers which options have no stock left. They only find out after selecting one. This snippet rebuilds the dropdown through the woocommerce_dropdown_variation_attribute_options_html filter so every option shows its stock status after the name, for example Large – (In Stock) or Blue – (Out of Stock), and sold-out options get an outofstock CSS class you can style. It works on products with one variation attribute (one dropdown).

Add the code to your child theme’s functions.php or a code snippets plugin. If you used an earlier version of this code, replace it: both declare get_stock_status_text(), and PHP stops with a fatal error when a function is declared twice.

Show the stock status in the variation dropdown and add a class

PHP
// Text added after each option name.
function get_stock_status_text( $in_stock ) {
    return $in_stock ? ' - (In Stock)' : ' - (Out of Stock)';
}

// Rebuild the variation dropdown with the stock status of each option.
add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'show_stock_status_in_dropdown', 10, 2 );
function show_stock_status_in_dropdown( $html, $args ) {
    $product   = $args['product'];
    $attribute = $args['attribute'];

    // Only for variable products with a single variation attribute (one dropdown).
    if ( ! $product instanceof WC_Product_Variable || count( $product->get_variation_attributes() ) !== 1 ) {
        return $html;
    }

    $options = $args['options'];
    if ( empty( $options ) && ! empty( $attribute ) ) {
        $attributes = $product->get_variation_attributes();
        $options    = isset( $attributes[ $attribute ] ) ? $attributes[ $attribute ] : array();
    }
    if ( empty( $options ) ) {
        return $html;
    }

    // Read the stock status of every available variation once.
    $key      = 'attribute_' . sanitize_title( $attribute );
    $in_stock = array();
    $any      = false; // A variation set to "Any" for this attribute covers every option.
    foreach ( $product->get_available_variations() as $variation ) {
        $value = isset( $variation['attributes'][ $key ] ) ? $variation['attributes'][ $key ] : '';
        if ( '' === $value ) {
            $any = $any || $variation['is_in_stock'];
        } else {
            $in_stock[ $value ] = ! empty( $in_stock[ $value ] ) || $variation['is_in_stock'];
        }
    }

    // Collect value, label and "selected" for each option, as WooCommerce does.
    $items = array();
    if ( taxonomy_exists( $attribute ) ) {
        $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) );
        foreach ( $terms as $term ) {
            if ( in_array( $term->slug, $options, true ) ) {
                $items[] = array(
                    $term->slug,
                    apply_filters( 'woocommerce_variation_option_name', $term->name, $term, $attribute, $product ),
                    selected( sanitize_title( $args['selected'] ), $term->slug, false ),
                );
            }
        }
    } else {
        foreach ( $options as $option ) {
            $items[] = array(
                $option,
                apply_filters( 'woocommerce_variation_option_name', $option, null, $attribute, $product ),
                sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false ),
            );
        }
    }

    $name      = $args['name'] ? $args['name'] : $key;
    $id        = $args['id'] ? $args['id'] : sanitize_title( $attribute );
    $none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' );

    $html  = '<select id="' . esc_attr( $id ) . '" class="' . esc_attr( $args['class'] ) . '" name="' . esc_attr( $name ) . '"';
    $html .= empty( $args['aria-label'] ) ? '' : ' aria-label="' . esc_attr( $args['aria-label'] ) . '"';
    $html .= ' data-attribute_name="' . esc_attr( $key ) . '" data-show_option_none="' . ( $args['show_option_none'] ? 'yes' : 'no' ) . '"';
    $html .= empty( $args['required'] ) ? '' : ' required';
    $html .= '>';
    $html .= '<option value="">' . esc_html( $none_text ) . '</option>';

    foreach ( $items as $item ) {
        list( $value, $label, $selected ) = $item;
        $available = isset( $in_stock[ $value ] ) ? $in_stock[ $value ] : $any;
        $class     = $available ? '' : ' class="outofstock"';
        $html     .= '<option value="' . esc_attr( $value ) . '" ' . $selected . $class . '>' . esc_html( $label . get_stock_status_text( $available ) ) . '</option>';
    }

    return $html . '</select>';
}

With three sizes where Medium is sold out, the dropdown becomes:

HTML
<select id="pa_size" class="" name="attribute_pa_size" data-attribute_name="attribute_pa_size" data-show_option_none="yes">
    <option value="">Choose an option</option>
    <option value="small">Small - (In Stock)</option>
    <option value="medium" class="outofstock">Medium - (Out of Stock)</option>
    <option value="large">Large - (In Stock)</option>
</select>

How the variation dropdown gets its stock status

  • WooCommerce prints each attribute dropdown with wc_dropdown_variation_attribute_options() and passes the finished HTML and its arguments through the filter hook. The function builds a new <select> with the same ID, name, data attributes, placeholder and selected option, and returns it in place of the original.
  • If the product has more than one variation attribute, the function returns the original HTML unchanged. With several dropdowns, whether an option is available depends on the choices in the other dropdowns. So a fixed label would be wrong.
  • The function calls get_available_variations() once per dropdown. For each variation, the function reads the value it uses for this attribute and whether it is in stock. A variation set to Any for the attribute has an empty value and counts for every option that has no variation of its own.
  • Global attributes, created under Products > Attributes, are listed from their terms in the order WooCommerce uses; custom attributes entered on the product are listed from their values. Both go through the woocommerce_variation_option_name filter, as in WooCommerce’s own function.
  • The code also labels an option out of stock when it has no variation a customer can buy. That includes a disabled variation or one without a price.
  • To change the wording, edit the two strings in get_stock_status_text().

Disable or hide sold-out options

An earlier version of this post added the disabled attribute to sold-out options in PHP. That doesn’t last: on products whose variation data is printed in the page (up to 30 variations by default), WooCommerce’s add-to-cart-variation.js copies the options, clears their disabled state and then disables only the options whose variations are not active. In a test with WooCommerce 11.1.2’s script, the sold-out option was selectable again right after the page loaded.

To really gray them out, keep this snippet for the labels and add the woocommerce_variation_is_active filter from graying out out-of-stock variations. In the same test, WooCommerce then disabled the sold-out option and kept its outofstock class and label. The two snippets use different function names, so they can run together.

To remove sold-out options completely, enable Hide out of stock items from the catalog under WooCommerce > Settings > Products > Inventory. That setting affects the whole store, not only the dropdown.

Style the class

CSS
.variations select option.outofstock {
    color: #999;
}

Browsers limit how much you can style <option> elements, and the native pickers on phones may ignore these styles, so test in the browsers your customers use. The text label works everywhere.

Things to check

  • The filter only runs in the classic variable product template. The variation selector of the Add to Cart with Options block doesn’t use it.
  • If the dropdown doesn’t change, check that the product really has a single variation attribute, and that a swatches plugin isn’t replacing the dropdown with its own markup.
  • Clear any page cache after adding the code, and again when stock changes if your cache keeps product pages for a long time.

The code is based on this Stack Overflow thread, updated for WooCommerce 11.

For variation dropdowns, swatches and stock labels built into your store’s theme, see my WooCommerce development services.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More