# Build the cart

> The cart as a page or as a mini cart that opens over the page. Covers the line template, totals, quantity and remove, the empty state, errors, checkout, and the five ways a cart can open.

Status: Ready (Smootify 2.0)  
1.x page: https://docs.smootify.io/docs/cart/mini-cart  
URL: https://www.smootify.io/docs/build/cart  
Updated: 2026-10-10

The cart is one element, `smootify-cart`, with a Webflow Form Block inside it. The same element makes a cart page or a mini cart that opens over the page: only what you wrap the form in changes. This guide builds the cart first, then shows the ways to open it.

Discount codes, notes and progress bars are in [Discounts, notes and progress bars](https://www.smootify.io/docs/build/cart-extras). Product suggestions are in [Cart upsells](https://www.smootify.io/docs/build/cart-upsells).

## How it fits together

What Smootify needs:

- a `smootify-cart` element;
- inside it, a Form Block. Its Form holds the line template (`cart-item`) and the checkout button.

What is up to you:

- where the cart lives: a page of its own, the navbar, or both;
- the layout, the order of the parts, the classes and the styles;
- which parts you show. A mini cart can show only the title and total of each line.

The smallest cart looks like this in the Navigator:

```html
<smootify-cart>
  <div class="w-form">
    <form>
      <cart-item>
        <img cart-item="image">
        <div cart-item="title">Product</div>
        <div cart-item="variant-title">Red / M</div>
        <quantity-input><button data-action="minus">-</button><input name="quantity"><button data-action="plus">+</button></quantity-input>
        <div cart-item="total">$0.00</div>
        <button data-action="remove">Remove</button>
      </cart-item>
      <div cart="subtotal">$0.00</div>
      <input type="submit" value="Check out" data-wait="Please wait…">
    </form>
    <div class="w-form-done"><div>Your cart is empty</div></div>
    <div class="w-form-fail"><div>Something went wrong</div></div>
  </div>
</smootify-cart>
```

## 1. Add the cart and its form

1. Add a Custom element with the tag `smootify-cart`. For a mini cart, put it in the navbar (see [Navbar](https://www.smootify.io/docs/build/navbar)). For a cart page, put it on a page of its own.
2. Inside it, add a Form Block.

Smootify uses the three parts of the Form Block this way:

- **Form**: the lines, the totals and the checkout button. Hidden while the cart is empty.
- **Success message**: the empty cart, shown when there are no lines. Never used as a success message.
- **Error message**: Shopify's errors, written in its text and shown for 5 seconds. Screen readers read them out.

Design the Success message as the empty cart, and leave a text element inside the Error message: Smootify writes each error into it, such as a quantity above the stock. A change that cannot reach Shopify, such as a quantity, a removal, a code, the note or the checkout while the visitor is offline, shows its error there too, and the line comes back from its loading state.

While the cart is empty, `smootify-cart` has the class `is-empty-cart`. You can style it as a combo class.

The cart uses the first form inside `smootify-cart`. A form inside a product card in the cart, such as an upsell, is left alone.

## 2. Design one line

Add a Custom element with the tag `cart-item` inside the Form. It is the template of a line: Smootify copies it once for each line in the cart. Design only one. Any other `cart-item` you add is removed.

Inside it, add a custom attribute to a Webflow element to fill it with a value of the line:

| Attribute | Put it on | What it shows |
|---|---|---|
| `cart-item="title"` | text | the product title |
| `cart-item="variant-title"` | text | the variant, such as “Red / M”; hidden for products without variants |
| `cart-item="image"` | an Image | the variant image (`product-image` for the product’s main image) |
| `cart-item="price"` | text | the price of one item |
| `cart-item="total"` | text | the line total, after the line’s discounts |
| `cart-item="compare-at-price"` | text | the compare-at price; hidden when there is none |
| `cart-item="savings"` | text | what the line saves; hidden when it saves nothing |
| `cart-item="unit-price"` | text | the price per unit, such as €1.50/ 100 g; empty without one |
| `cart-item="url"` | a Link block or Text link | links to the product page |

A Text link with `cart-item="url"` and only text inside also shows the product title. Every value is in [cart line values](https://www.smootify.io/docs/reference/attributes/cart-item).

**Options and extra fields.** A Div block with `cart-item="options"` is repeated once for each option of the variant. Inside it, `option="name"` shows the name (“Size”) and `option="value"` the value (“M”). The same template also lists the extra fields the visitor filled in the add-to-cart form, such as an engraving, and the name of the subscription plan.

**Metafields.** The product’s metafields work inside the line, with the same attributes as on the product page: see [Metafields on the page](https://www.smootify.io/docs/build/metafields).

**While a line changes.** A line shimmers and ignores clicks while its quantity changes or while it is removed. It has the class `updating` or `removing` during that time.

## 3. Quantity and remove

**Quantity.** Inside `cart-item`, add a Custom element with the tag `quantity-input` with a Text field named `quantity` inside it. The minus and plus buttons are optional: they are Custom elements with the tag `button` and `data-action="minus"` or `data-action="plus"`.

- A change updates the line at once. Going down to 0 removes the line.
- Plus stops at the stock or at the product’s quantity rule. `data-disable-max` on `quantity-input` removes that limit.
- With both buttons, the field does not take clicks: the number changes through the buttons. Without buttons, the visitor types the number.
- Some lines cannot change quantity, such as a [Magic Box](https://www.smootify.io/docs/features/magic-box). Their quantity field is removed.

**Remove.** Inside `cart-item`, add a Custom element with the tag `button` and `data-action="remove"`. It removes the line, together with the other lines of its bundle.

## 4. Totals

Add a custom attribute to a text element anywhere inside `smootify-cart`, inside or outside the Form. Here, three items cost €150 at compare-at prices, €120 at their sale prices, and a discount code takes €12 off the order:

| Attribute | What it shows |
|---|---|
| `cart="count"` | the number of items (a line with 3 items counts as 3) |
| `cart="subtotal"` | the amount before the discounts on the whole order |
| `cart="compare-at-total"` | the total at compare-at prices; hidden unless it is higher than the total |
| `cart="savings"` | the compare-at total minus the total; hidden when nothing is saved |
| `cart="total"` | the amount after every discount |

`cart="savings"` replaces what is inside it with the amount, and hides only itself. A label such as “You save” placed next to it stays visible when there is nothing to save.

**Shipping.** For a logged-in customer with an address, Shopify adds a shipping estimate to the total. `cart="shipping"` shows it, so that subtotal, shipping and taxes add up to the total. Without an estimate (no address yet, free shipping, or draft prices) the element is hidden. To show a text instead, such as “Calculated at checkout”, give it the custom attribute `data-fallback` with that text. `data-fallback` works the same on every `cart` value, and an empty one is ignored.

Taxes, and shipping without an address, are calculated at checkout, so the total in the cart is not always the final amount. The discount and gift card lists are in [Discounts, notes and progress bars](https://www.smootify.io/docs/build/cart-extras). Every value is in [cart values](https://www.smootify.io/docs/reference/attributes/cart).

`skeleton="text"` on a total shows a shimmer until the cart loads. See [loading](https://www.smootify.io/docs/reference/attributes/loading).

## 5. Checkout

The checkout button is the Form’s **Form Button**. A click sends the visitor to Shopify’s checkout. Before that, Smootify saves the cart note and the other fields of the cart form (see [cart attributes](https://www.smootify.io/docs/build/cart-extras)). The Form Button’s **Waiting text** setting shows while this happens.

- **Shop Pay.** A Custom element with the tag `smootify-shop-pay` inside `smootify-cart` shows Shopify’s Shop Pay button for the cart. Inside `smootify-product` it shows the button for the chosen variant. Shopify draws the button, and it always fills the element: for a narrower button, give the element a width, not a background or a centred layout.
- **Checkout domain.** Where the checkout opens depends on your domains: see [Domains and checkout](https://www.smootify.io/docs/start/domains).
- **A quote instead of checkout.** See [Quotes and B2B](https://www.smootify.io/docs/features/quotes).

**A link to Shopify’s own cart.** Some Shopify apps work only on the cart page of Shopify’s online store, such as pickup and delivery schedulers like Pickeasy. For them, add the custom attribute `shopify-link="cart"` to a Text link or a Link block, on any page. Smootify points the link to the online store’s cart, on your store’s `myshopify.com` address, with the visitor’s lines and their extra fields, and updates it every time the cart changes. When more than one line has extra fields or is a bundle, the link opens the visitor’s cart itself instead. Any other value is a path of the store’s address: `shopify-link="account"` opens `/account` there.

## 6. A page or a mini cart

A `smootify-cart` with nothing around its form is a cart page: it shows in the page. To make a mini cart, wrap the Form Block in one of these, inside `smootify-cart`:

| Way | Build it with | Opens with | Good to know |
|---|---|---|---|
| A cart page | nothing: the Form Block sits right inside `smootify-cart` | always shown, on a page of its own | open and close buttons do nothing here |
| Webflow Dropdown | a Dropdown, with the Form Block in its list | the Dropdown toggle | opens again when the lines change |
| Dialog | a Custom element with the tag `dialog`; its first child is the panel | an open-cart button | a click on the backdrop and Escape close it |
| Details | a Custom element with the tag `details`, with a `summary` | the `summary` | a click outside closes it |
| Popover | Webflow's Popover element, or a Div block with the attribute `popover` | its trigger, or an open-cart button | stays open while the visitor types |
| Your own interaction | two elements with `data-trigger="open"` and `data-trigger="close"` | your Webflow interaction | Smootify clicks them to open and close the cart |

You don’t tell Smootify which one you use. It looks inside `smootify-cart`, in this order, for a popover, a dialog, a details and a Dropdown. The first it finds decides how the cart opens. Use only one.

**Open and close buttons.** A Custom element with the tag `button` and `data-action="open-cart"` opens the cart; `data-action="close-cart"` closes it (`open` and `close` also work). Both must be inside the same `smootify-cart`. Keep them outside the Form, or give them `type="button"`: inside the Form, a button without it also submits the cart and goes to checkout.

**Open after an add.** Add `data-open="on-add"` to `smootify-cart`. The mini cart then opens each time a product is added, except with Buy now, which goes straight to checkout. On a page that has another `smootify-cart` with no Dropdown and no popover inside it, such as your cart page, `data-open` does nothing.

For developers, [`openCart`](https://www.smootify.io/docs/reference/api/methods/opencart) and [`closeCart`](https://www.smootify.io/docs/reference/api/methods/closecart) open and close it from code, and the [`cart_opened`](https://www.smootify.io/docs/reference/api/events/cart-opened) and [`cart_closed`](https://www.smootify.io/docs/reference/api/events/cart-closed) events tell when it happens.

### In a Webflow Dropdown

The toggle holds the cart icon and the count. The Dropdown list holds the Form Block.

```html
<smootify-cart data-open="on-add">
  <div class="w-dropdown">
    <div class="w-dropdown-toggle"><div>Cart</div><div cart="count">0</div></div>
    <nav class="w-dropdown-list">
      <div class="w-form">
        <form>
          <cart-item><div cart-item="title">Product</div><div cart-item="total">$0.00</div></cart-item>
          <input type="submit" value="Check out">
        </form>
        <div class="w-form-done"><div>Your cart is empty</div></div>
        <div class="w-form-fail"><div>Something went wrong</div></div>
      </div>
    </nav>
  </div>
</smootify-cart>
```

A click inside the list does not close it, and Smootify opens it again when the lines change. A Dropdown can still close while the visitor types in the cart’s fields. A popover stays open.

### In a dialog

A dialog is a panel over the page, such as a drawer. Put a Custom element with the tag `dialog` inside `smootify-cart`. Its first child is the panel: design it as you like, such as a column fixed to the right. Everything around the panel is the backdrop.

```html
<smootify-cart>
  <button data-action="open-cart">Cart</button>
  <dialog>
    <div>
      <button data-action="close-cart">Close</button>
      <div class="w-form">
        <form>
          <cart-item><div cart-item="title">Product</div><div cart-item="total">$0.00</div></cart-item>
          <input type="submit" value="Check out">
        </form>
        <div class="w-form-done"><div>Your cart is empty</div></div>
        <div class="w-form-fail"><div>Something went wrong</div></div>
      </div>
    </div>
  </dialog>
</smootify-cart>
```

- A click on the backdrop and the Escape key close it.
- The panel fades and scales in. A developer can change the animation: see [Options](https://www.smootify.io/docs/developers/options).
- The backdrop is 80% black. The CSS variable `--smootify-backdrop-color` changes it: see [CSS](https://www.smootify.io/docs/developers/css).
- A dialog is hidden in the Designer. To design it, add the custom attribute `open` with the value `true` while you work, and remove it before you publish: a dialog with `open` shows when the page loads.

### In a details element

Put a Custom element with the tag `details` inside `smootify-cart`. Inside it, a Custom element with the tag `summary` is the trigger, and can hold the count. The element after it is the panel.

A click on the summary opens and closes the cart, and a click outside the details closes it. A close button goes inside the panel.

### In a popover

A popover opens above the page. Escape and a click outside close it, and the browser handles the focus. Use Webflow’s Popover element where your site has it, or a Div block with the custom attribute `popover`.

```html
<smootify-cart data-open="on-add">
  <button data-action="open-cart">Cart <span cart="count">0</span></button>
  <div popover="auto">
    <button data-action="close-cart">Close</button>
    <div class="w-form">
      <form>
        <cart-item><div cart-item="title">Product</div><div cart-item="total">$0.00</div></cart-item>
        <input type="submit" value="Check out">
      </form>
      <div class="w-form-done"><div>Your cart is empty</div></div>
      <div class="w-form-fail"><div>Something went wrong</div></div>
    </div>
  </div>
</smootify-cart>
```

- Webflow’s Popover element comes with its own trigger, which works as it is. With the custom attribute, open it with a `data-action="open-cart"` button as above, or with a button whose `popovertarget` is the panel’s ID.
- The popover stays open while the visitor types in the cart’s fields and while the lines change.
- Smootify does not animate a popover: it keeps the transitions you design.
- Unless the cart is in a dialog or a details, a popover anywhere inside `smootify-cart`, even in a line, makes Smootify open the cart as a popover. Keep other popovers out of the cart.

More in [Popovers](https://www.smootify.io/docs/build/popovers).

### With your own interaction

When none of the above is inside `smootify-cart`, you can build the panel with Webflow interactions. Give one element `data-trigger="open"` and another `data-trigger="close"`, and make interactions that show and hide the panel when they are clicked. When Smootify needs to open or close the cart, for example after an add, it clicks them.

## Before you publish

On your `.webflow.io` address, Smootify shows a debugger button. Its panel lists the elements that are out of place on the page.

> [!STUCK]
> - **Only one line shows, or lines you designed disappear.** Only the first `cart-item` is the template. Design one.
> - **The quantity buttons do nothing.** `quantity-input` is outside the Form, or its buttons are Webflow Buttons, which are links. Use Custom elements with the tag `button`.
> - **A close button goes to checkout.** It is inside the Form without `type="button"`.
> - **The mini cart does not open after an add.** `data-open` is missing, or the page has another `smootify-cart` with no Dropdown and no popover, such as the cart page.
> - **The cart shows open when the page loads.** The dialog still has the `open` attribute you used to design it.
> - **The compare-at total is missing.** It shows only when it is higher than the total: nothing in the cart is on sale or discounted.

## Coming from 1.x

- In 1.x, the open and close buttons worked only with a dialog or a details. In 2.0 they work with every way, and a popover is a new way.
- `cart="compare-at-total"` now adds up every line, including the lines at full price, so the crossed-out total is correct.
