Want personalized guidance adding this to your store?
Check out our Insiders community: https://www.skool.com/the-prompted
Members of The Prompted community receive a detailed store audit, 1-on-1 guidance for implementing new features, and access to an exclusive theme. You'll also get marketing support, the same tactics we use to spend over $100k/mo on Meta Ads.
---
In this tutorial, we'll show you how to add a Cart Progress Bar to your Shopify store. This feature encourages customers to add more items to their cart to unlock free shipping, helping you increase your average order value (AOV).
Compatible Themes: This code should work on all free Shopify themes (Dawn, Refresh, Craft, Studio, Publisher, Crave, Origin, Taste, Colorblock, Sense, Ride, Spotlight).
Create metafield
Create new market metafield
- Name: Cart Progress Threshold Number
- Namespace and key: custom.cart_progress_threshold_number
- Type: Decimal (one value)
- Minimum value: 0
- Maximum decimal places: 2
Edit Theme Code
Add settings to settings_schema.json
{
"name": "Cart Progress Bar",
"settings": [
{
"type": "paragraph",
"content": "Cart Progress Thresholds are set in Market Metafields"
},
{
"type": "checkbox",
"id": "enable_cart_progress_bar",
"label": "Enable Cart Progress Bar",
"default": false
},
{
"type": "text",
"id": "cart_pre_goal_message",
"label": "Pre-Goal Message",
"default": "You're only [remaining_for_goal] away from <strong>FREE SHIPPING</strong>",
"info": "Message displayed before reaching the goal. Use [remaining_for_goal] to insert the dynamic remaining amount."
},
{
"type": "text",
"id": "cart_post_goal_message",
"label": "Post-Goal Message",
"default": "🎉 Congrats! You've unlocked <strong>FREE SHIPPING</strong>",
"info": "Message displayed after reaching the goal."
},
{
"type": "color",
"id": "cart_progress_bar_color",
"label": "Progress Bar Color",
"default": "#d53600",
"info": "Color of the progress bar."
},
{
"type": "color",
"id": "cart_progress_bar_full_color",
"label": "Progress Bar Full Color",
"default": "#d53600",
"info": "Color of the progress bar when complete."
},
{
"type": "color",
"id": "cart_progress_bar_background_color",
"label": "Progress Bar Background Color",
"default": "#eee",
"info": "Color of the progress bar background."
},
{
"type": "color",
"id": "cart_progress_bar_text_color",
"label": "Progress Bar Text Color",
"default": "#333"
}
]
}
Create a new snippet cart-progress-bar-custom.liquid
{% if cart != empty and localization.market.metafields.custom.cart_progress_threshold_number != nil and settings.enable_cart_progress_bar %}
{% assign progress_threshold_cents = localization.market.metafields.custom.cart_progress_threshold_number | times: 100 %}
{% assign pre_goal_message_template = settings.cart_pre_goal_message %}
{% assign post_goal_message = settings.cart_post_goal_message %}
{% assign cart_total_cents = cart.total_price %}
{% assign progress_fraction = cart_total_cents | times: 1.0 | divided_by: progress_threshold_cents %}
{% assign progress_percentage = progress_fraction | times: 100 %}
{% if progress_percentage > 100 %}
{% assign progress_percentage = 100 %}
{% endif %}
{% assign remaining_for_goal = progress_threshold_cents | minus: cart_total_cents %}
{% if remaining_for_goal < 0 %}
{% assign remaining_for_goal = 0 %}
{% endif %}
{% if settings.currency_code_enabled %}
{% assign remaining_for_goal_formatted = remaining_for_goal | money_with_currency %}
{% else %}
{% assign remaining_for_goal_formatted = remaining_for_goal | money %}
{% endif %}
{% assign dynamic_pre_goal_message = pre_goal_message_template | replace: '[remaining_for_goal]', remaining_for_goal_formatted %}
<div id="cart-progress-wrapper"
class="{% if progress_percentage == 100 %}full{% else %}not-full{% endif %}"
data-threshold="{{ progress_threshold_cents }}"
data-pre-goal-message-template="{{ pre_goal_message_template | escape }}"
data-post-goal-message="{{ post_goal_message | escape }}"
data-currency-format="
{% if settings.currency_code_enabled %}
{{ shop.money_with_currency_format | escape }}
{% else %}
{{ shop.money_format | escape }}
{% endif %}
"
>
<div class="cart-progress-bar-container">
<div id="cart-progress-bar" style="width: {{ progress_percentage }}%;"></div>
</div>
<div class="goal-message">
{% if remaining_for_goal > 0 %}
{{ dynamic_pre_goal_message }}
{% else %}
{{ post_goal_message }}
{% endif %}
</div>
</div>
{% endif %}
<style>
.cart-progress-bar-container {
width: 100%;
background-color: {{ settings.cart_progress_bar_background_color }};
border-radius: 10px;
margin: 2px auto;
padding: 1px;
overflow: hidden;
}
#cart-progress-bar {
display: block;
height: 10px;
background-color: var(--progress-bar-color, {{ settings.cart_progress_bar_color }}); /* Default color */
border-radius: 2px;
transition: width 0.5s ease-in-out;
border: 1px solid var(--progress-bar-border-color, {{ settings.cart_progress_bar_color }});
padding: 5px 0;
box-sizing: border-box;
}
#cart-progress-wrapper.full #cart-progress-bar {
--progress-bar-color: {{ settings.cart_progress_bar_full_color }};
--progress-bar-border-color: {{ settings.cart_progress_bar_full_color }};
}
.goal-message {
text-align: center;
margin: 2px auto 10px;
font-size: 1em;
color: {{ settings.cart_progress_bar_text_color }};
}
</style>
Edit main-cart-items.liquid
{%- unless settings.cart_type == 'drawer' -%}
{% render 'cart-progress-bar-custom' %}
{%- endunless -%}
Edit cart-drawer.liquid
{% render 'cart-progress-bar-custom' %}
Add a new method in cart.js
Call the new method in updateQuantity
const updatedCartTotal = parsedState.total_price;
this.updateProgressBar(updatedCartTotal);
New methods
updateProgressBar(cartTotal, itemCount) {
const progressWrapper = document.getElementById('cart-progress-wrapper');
const currencyFormat = progressWrapper.dataset.currencyFormat;
const progressThreshold = parseInt(progressWrapper.dataset.threshold, 10);
const preGoalMessageTemplate = progressWrapper.dataset.preGoalMessageTemplate;
const postGoalMessage = progressWrapper.dataset.postGoalMessage;
const progressBar = document.getElementById('cart-progress-bar');
const goalMessageElement = document.querySelector('.goal-message');
if (itemCount === 0 || cartTotal === 0) {
if (progressWrapper) {
progressWrapper.style.display = 'none';
}
if (goalMessageElement) {
goalMessageElement.style.display = 'none';
}
} else {
if (progressWrapper) {
progressWrapper.style.display = 'block';
}
if (progressBar) {
progressBar.style.display = 'block';
const progressPercentage = Math.min((cartTotal / progressThreshold) * 100, 100);
progressBar.style.width = `${progressPercentage}%`;
if (progressPercentage >= 100) {
progressWrapper.classList.add('full');
} else {
progressWrapper.classList.remove('full');
}
}
if (goalMessageElement) {
goalMessageElement.style.display = 'block';
let remainingForGoal = progressThreshold - cartTotal;
if (remainingForGoal < 0) {
remainingForGoal = 0;
}
const remainingAmount = remainingForGoal / 100;
const remainingAmountFormatted = this.formatCurrency(currencyFormat, remainingAmount);
const preGoalMessage = preGoalMessageTemplate.replace('[remaining_for_goal]', remainingAmountFormatted);
goalMessageElement.innerHTML = remainingForGoal > 0 ? preGoalMessage : postGoalMessage;
}
}
}
formatCurrency(currencyFormat, amount) {
let formattedAmount = '';
formattedAmount = currencyFormat
.replace('{{amount}}', amount.toFixed(2)) // Standard with two decimals
.replace('{{amount_no_decimals}}', amount.toFixed(0)) // No decimals
.replace('{{amount_with_comma_separator}}', amount.toFixed(2).replace('.', ',')) // Replace period with comma
.replace('{{amount_no_decimals_with_comma_separator}}', amount.toFixed(0).replace('.', ',')) // No decimals, use comma
.replace('{{amount_with_apostrophe_separator}}', amount.toFixed(2).replace('.', "'")) // Apostrophe separator
.replace('{{amount_no_decimals_with_space_separator}}', amount.toFixed(0).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ' ')) // No decimals, space
.replace('{{amount_with_space_separator}}', amount.toFixed(2).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ' ').replace('.', ',')) // Space separator
.replace('{{amount_with_period_and_space_separator}}', amount.toFixed(2).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ' ')); // Period and space
return formattedAmount;
}