A checkout page that just says "add 15 more for free shipping" in plain text gets ignored. A progress bar that fills up as the cart grows gets watched, and it nudges average order value upward because the gap to close is visible, not something the shopper has to do the maths on. Here is the snippet I use to add one to WooCommerce, with no extra plugin.
How it works
The snippet reads the minimum amount required for free shipping straight from your WooCommerce shipping zone's Free Shipping method, so there is nothing to hardcode or keep in sync by hand. It calculates the current cart total, respecting that method's "ignore discounts" setting, and renders a bar with the remaining amount or a success message once the threshold is hit. It hooks into woocommerce_add_to_cart_fragments so the bar updates live over AJAX the moment something is added to the cart, on both the cart and checkout pages.
The PHP
Add this as a new snippet via the Code Snippets plugin, never in functions.php, since theme updates wipe that file clean.
/**
* Free shipping bar indicator (WooCommerce cart + checkout view)
*/
// Hide shipping calculator on Cart page only
add_filter( 'woocommerce_cart_needs_shipping', 'filter_cart_needs_shipping' );
function filter_cart_needs_shipping( $needs_shipping ) {
if ( is_cart() ) {
$needs_shipping = false;
}
return $needs_shipping;
}
// Make progress bar update with AJAX fragments
add_filter( 'woocommerce_add_to_cart_fragments', 'add_free_shipping_bar_to_fragments' );
function add_free_shipping_bar_to_fragments( $fragments ) {
ob_start();
display_free_shipping_progress_bar(); // Render progress bar HTML
$fragments['.free-shipping-bar-container'] = ob_get_clean(); // Target container class
return $fragments;
}
// Display on Cart page (above cart totals)
add_action( 'woocommerce_before_cart_collaterals', 'display_free_shipping_progress_bar' );
// Display on Checkout page (above checkout form)
add_action( 'woocommerce_before_checkout_form', 'display_free_shipping_progress_bar', 5 );
// Main function to display the progress bar
function display_free_shipping_progress_bar() {
// Get all zones
$zones = WC_Shipping_Zones::get_zones();
// Find the zone for Poland (PL)
$poland_zone = null;
foreach ( $zones as $zone_data ) {
foreach ( $zone_data['zone_locations'] as $location ) {
if ( 'PL' === $location->code && 'country' === $location->type ) {
$poland_zone = $zone_data;
break 2;
}
}
}
if ( ! $poland_zone ) {
return;
}
$free_shipping_method = null;
foreach ( $poland_zone['shipping_methods'] as $method ) {
if ( 'free_shipping' === $method->id ) {
$free_shipping_method = $method;
break;
}
}
if ( ! $free_shipping_method ) {
return;
}
$min_amount = isset( $free_shipping_method->instance_settings['min_amount'] ) ? $free_shipping_method->instance_settings['min_amount'] : 0;
if ( $min_amount <= 0 ) {
return;
}
$cart_total = WC()->cart->get_displayed_subtotal();
$ignore_discounts = $free_shipping_method->settings['ignore_discounts'] ?? 'no';
if ( 'no' === $ignore_discounts ) {
$cart_total -= WC()->cart->get_discount_total();
if ( WC()->cart->display_prices_including_tax() ) {
$cart_total -= WC()->cart->get_discount_tax();
}
}
if ( $cart_total >= $min_amount ) {
$progress = 100;
$message = __( 'Gratulacje! Masz darmową dostawę!', 'woocommerce' );
} else {
$remaining = $min_amount - $cart_total;
$progress = ( $cart_total / $min_amount ) * 100;
$message = sprintf(
__( 'Do darmowej dostawy brakuje ci %s', 'woocommerce' ),
'<strong>' . number_format( $remaining, 2, ',', '' ) . ' zł</strong>'
);
}
// Note: default messages are in Polish; edit the strings above to translate.
echo '<div class="free-shipping-bar-container">';
echo '<div class="free-shipping-bar">';
echo '<p>' . wp_kses_post( $message ) . '</p>';
echo '<div class="progress-bar-container">';
echo '<div class="progress-bar" style="width: ' . esc_attr( $progress ) . '%;"></div>';
echo '</div>';
echo '</div>';
echo '<div class="clearfix"></div>';
echo '</div>';
}
The CSS
Add this to your theme's stylesheet, or under Appearance > Customize > Additional CSS:
.free-shipping-bar {
margin: 30px 0 24px 0;
padding: 35px;
background-color: #fff;
box-shadow: 0px 30px 60px 0px rgba(0, 0, 0, 0.1);
color: #848484;
}
.free-shipping-bar .progress-bar-container {
margin-top: 10px;
background-color: #f2f2f2;
border-radius: 20px;
height: 20px;
overflow: hidden;
}
.free-shipping-bar .progress-bar {
background-color: #f80;
height: 100%;
}
.free-shipping-bar strong {
color: #323232;
}
Notes
The version in the repo is written for a Poland (PL) shipping zone, which is what I needed it for; swap the country code to adapt it to any other zone. The two messages are in Polish by default, since that is where I built it for, so edit those two strings if you need another language. It targets the built-in WooCommerce Free Shipping method, so it works with the shipping setup most WooCommerce stores already have, nothing exotic required.
Full source on GitHub: djaysan/woo-free-shipping-bar-indicator. If you want this wired into a store I am building or maintaining for you, get in touch.