Learn Shopify Dev: Free Shipping Cart Notice with Web Components


Offering free shipping can be a game-changer for delighting customers and boosting sales. Showing proximity to free shipping can be an effective tool for incentivizing customers to add an extra item or two. Today, we’re diving into the realm of Shopify’s cart slideout and harnessing web components to seamlessly integrate a dynamic free shipping notice into your Shopify store’s slideout cart.

Github link here.

Prefer Video?

Otherwise read on!

Web Components

Shopify’s dawn theme utilizes web components for the slideout cart. If you haven’t used web components much, they should look familiar if you’ve ever used React. Web components are basically platform agnostic components that are written in a way that encapsulate HTML template, CSS, JS and Shadow DOM in one place that you can reference easily. Think of them as building blocks. To learn more, check out this link. If you investigate the Dawn theme cart in the inspector you’ll see the cart web component is defined in cart.js and looks like this:

class CartItems extends HTMLElement {
  constructor() {
    super();
    ...
  }

  connectedCallback() {
    this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartUpdate, (event) => {
      if (event.source === 'cart-items') {
        return;
      }
      this.onCartUpdate();
    });
  }

Now let’s build our own web component!

Creating our Shopify Cart Web Component

We want to follow this convention by first creating our own section file. We’ll name it free-shipping-countdown.liquid. Note we could also just create a Javascript file and then create/reference our threshold  from the global theme settings but for this example we will use a section and section schema.

In our free-shipping-countdown file we are going to add the following code:

<script>
  class FreeShippingCountdown extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({mode: 'open'});
    }

    connectedCallback() {
      this.initEventListener();
      this.updateFreeShippingCountdown();
    }

    async getShopifyCart() {
      try {
        const response = await fetch('/cart.js', {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
          }
        });

        if (!response.ok) {
          throw new Error('Network response was not ok');
        }

        const cartData = await response.json();
        return cartData;
      } catch (error) {
        console.error('Error fetching Shopify cart:', error);
      }
    }

    async updateFreeShippingCountdown() {
      const cart = await this.getShopifyCart();
      const threshold = {{ section.settings.free-shipping-countdown }} || 0
      if (threshold <= 0) return 

      const amountUntilFreeShipping = threshold - (cart.total_price / 100)

      const str = amountUntilFreeShipping > 0 ?
        `<p>$${amountUntilFreeShipping.toFixed(2)} amount until you hit free shipping</p>`
        :
        `<p>Congratulations, you have reached free shipping!</p>`

      this.shadowRoot.innerHTML = str
    }

    initEventListener() {
      document.addEventListener('cartUpdated', () => {
        this.updateFreeShippingCountdown();
      })
    }
  }

  customElements.define('free-shipping-countdown', FreeShippingCountdown)
</script>

{% schema %}
  {
    "name": "Free Shipping Countdown",
    "settings": [
      {
        "id": "free-shipping-countdown",
        "type": "text",
        "label": "Set the amount of $ for free shipping (no $ sign or commas)"
      }
    ]
  }
{% endschema %}

This code creates our web component which creates a class that extends HTML components, sets a connectedCallback method which is automatically called as soon as the component is attached to the DOM. It also sets some event listeners waiting for updates to the cart to rerun the main function where we fetch the cart total and determine the difference between it and the free shipping threshold. (Also notice the schema which will allow us to collect the threshold from the merchant in the theme customizer).

Creating our Cart Update Event Emitters

Next we need to set the event emitters for the listeners we just created. In many themes, you can search the code base for event emitters for things like ‘cart:updated’ or ‘cart-changed’ which already exist. In the newest Dawn theme, this logic is all happening within the web components in cart.js. We are going to add this code:

...
onCartUpdate() {
  fetch(...)
  .then((responseText) => {
    ...

    const cartEvent = new CustomEvent('cartUpdated'); <!-- add the event emitter -->
    document.dispatchEvent(cartEvent);
  })
}

...

updateQuantity() {
  ...

  .then((state) => {
    ... 
    const cartEvent = new CustomEvent('cartUpdated'); <!-- add the event emitter -->
     document.dispatchEvent(cartEvent);
    ...
  })
}

Into the onCartUpdate() and updateQuantity() functions. Now when these functions run on a cart change, it will emit an event that our new component can listen for.

Reference Web Component in Liquid File

Lastly we need to add our web component into the liquid/html code. Navigate to the cart-drawer.liquid file in the snippets folder. Right above the for loop of our cart items we want to paste this code:

...

<div>
  <free-shipping-countdown></free-shipping-countdown> <!-- add this div -->
</div>

<tbody role="rowgroup">
  {%- for item in cart.items -%}
...

Adding the New Section To theme.liquid

Now we want this code to be accessible to our non technical merchants so they can easily change the shipping thresholds. Let’s add this reference to our section in theme.liquid:

{%- if settings.cart_type == 'drawer' -%}
  {% section 'free-shipping-countdown' %} <!-- add this -->
  {%- render 'cart-drawer' -%}
{%- endif -%}

Add Threshold in Theme Customizer

And now open the theme customizer and you should see your new section. After clicking in we can set the threshold, save and open a preview. (If you created your web component in a JS file in the assets folder you can reference the threshold by using {{ section.free-shipping-countdown }}.

Conclusion

After adding a product to cart, you should see your free shipping notice above the cart items.

I hope you enjoyed this tutorial on Shopify development and learned a bit more about web components and the Shopify cart API.