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 ways a cart can open.
The cart is one element, smootify-cart, with a <form> inside it. The form’s success and error messages are the elements with form-done and form-fail: see Write components. 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. Product suggestions are in Cart upsells.
How it fits together
What Smootify needs:
- a
smootify-cartelement; - inside it, a
<form>. The form holds the line template (cart-item) and the checkout button; - next to the form,
form-doneandform-failelements, for the empty cart and for errors.
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:
<smootify-cart>
<form>
<cart-item>
<img cart-item="image" alt="" />
<div cart-item="title">Product</div>
<div cart-item="variant-title">Red / M</div>
<quantity-input>
<button type="button" data-action="minus">-</button>
<input type="number" name="quantity" />
<button type="button" data-action="plus">+</button>
</quantity-input>
<div cart-item="total">$0.00</div>
<button type="button" data-action="remove">Remove</button>
</cart-item>
<div cart="subtotal">$0.00</div>
<button type="submit">
<span data-state="default">Check out</span>
<span data-state="loading">Please wait…</span>
</button>
</form>
<div form-done hidden>Your cart is empty</div>
<div form-fail hidden>Something went wrong</div>
</smootify-cart>1. Add the cart and its form
- Write a
smootify-cartelement. For a mini cart, put it in the navbar (see Navbar). For a cart page, put it on a page of its own. - Inside it, write a
<form>, and next to the form aform-doneand aform-failelement, both withhidden.
Smootify uses the three parts of the cart this way:
<form>: the lines, the totals and the checkout button. Hidden while the cart is empty.form-done: the empty cart, shown when there are no lines. It is never a success message.form-fail: Shopify’s errors, written in its text and shown for 5 seconds. Screen readers read them out.
Design form-done as the empty cart, and leave a text element inside form-fail: 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 from that 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 cart-item element 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 an attribute to an 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 img | 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" | an a | links to the product page |
An a with cart-item="url" and only text inside also shows the product title. Every value is in cart line values.
Options and extra fields. An element 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.
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 quantity-input element with an <input name="quantity"> inside it. The minus and plus buttons are optional: they are <button type="button"> elements with 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, a B2B one or one of the purchase rules.
data-disable-maxonquantity-inputremoves 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. Their quantity field is removed.
Remove. Inside cart-item, add a <button type="button"> with data-action="remove". It removes the line, together with the other lines of its bundle.
4. Totals
Add an 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:
cart="count"The number of items. A line with 3 items counts as 3.cart="subtotal"Before the discounts on the whole order.cart="compare-at-total"At compare-at prices. Hidden unless higher than the total.cart="savings"Compare-at total minus total. Hidden when nothing is saved.cart="total"After every discount, here a €12 code.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 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. Every value is in cart values.
skeleton="text" on a total shows a shimmer until the cart loads. See loading.
5. Checkout
The checkout button is the form’s submit button, <button type="submit">. 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). Inside it, [data-state="default"] holds the label and [data-state="loading"] what shows while this happens.
- Shop Pay. A
smootify-shop-payelement insidesmootify-cartshows Shopify’s Shop Pay button for the cart. Insidesmootify-productit 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. The checkout is Shopify’s page and opens on the store’s primary domain, set in Shopify under Settings, Domains. The domain of your site is a separate step: see Deploy to Cloudflare.
- A quote instead of checkout. See Quotes and B2B.
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 attribute shopify-link="cart" to an a link, 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 in one of these, inside smootify-cart:
| Way | Build it with | Opens with | Good to know |
|---|---|---|---|
| A cart page | nothing: the <form> sits right inside smootify-cart | always shown, on a page of its own | open and close buttons do nothing here |
| Popover | an element with the attribute popover | its popovertarget button, or an open-cart button | stays open while the visitor types |
| Dialog | a dialog element; its first child is the panel | an open-cart button | a click on the backdrop and Escape close it |
| Details | a details element, with a summary | the summary | a click outside closes it |
You don’t tell Smootify which one you use. It looks inside smootify-cart, in this order, for a popover, a dialog and a details. The first it finds decides how the cart opens. Use only one.
Open and close buttons. A <button> with 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 nothing around its form, such as your cart page, data-open does nothing.
For developers, openCart and closeCart open and close it from code, and the cart_opened and cart_closed events tell when it happens.
In a dialog
A dialog is a panel over the page, such as a drawer. Put a dialog element 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.
<smootify-cart>
<button type="button" data-action="open-cart">Cart</button>
<dialog>
<div>
<button type="button" data-action="close-cart">Close</button>
<form>
<cart-item><div cart-item="title">Product</div><div cart-item="total">$0.00</div></cart-item>
<button type="submit">Check out</button>
</form>
<div form-done hidden>Your cart is empty</div>
<div form-fail hidden>Something went wrong</div>
</div>
</dialog>
</smootify-cart>- A click on the backdrop and the Escape key close it.
- The first child of the dialog fades and scales in (opacity 0 to 1, scale 0.9 to 1, in 0.5 seconds) and out (opacity 1 to 0, scale 1 to 0.85, in 0.3 seconds). To change it, set
window.cartInAnimation,window.cartOutAnimation,window.cartInAnimationSettingsandwindow.cartOutAnimationSettingsin an inline script that runs before Smootify starts: see Options. From CSS you style the dialog box and the backdrop. - The backdrop is 80% black. The CSS variable
--smootify-backdrop-colorchanges it: see CSS.
In a details element
Put a details element inside smootify-cart. Inside it, a summary element 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 an element with the attribute popover.
<smootify-cart data-open="on-add">
<button type="button" data-action="open-cart">Cart <span cart="count">0</span></button>
<div popover="auto">
<button type="button" data-action="close-cart">Close</button>
<form>
<cart-item><div cart-item="title">Product</div><div cart-item="total">$0.00</div></cart-item>
<button type="submit">Check out</button>
</form>
<div form-done hidden>Your cart is empty</div>
<div form-fail hidden>Something went wrong</div>
</div>
</smootify-cart>- Open it with a
data-action="open-cart"button as above, or with a button whosepopovertargetis the panel’sid. - The popover stays open while the visitor types in the cart’s fields and while the lines change.
- To animate the panel, use CSS transitions (
@starting-styleandtransition-behavior: allow-discrete): the animation plays when Smootify opens the cart after an add to cart too. - 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.
Before you publish
In the dev server and on your *.workers.dev address, Smootify has a debugger. Its panel lists the elements that are out of place on the page. Open it with Alt+Shift+D, or add ?smootify-debug to the address.
Check these first
- Only one line shows, or lines you designed disappear. Only the first
cart-itemis the template. Design one. - The quantity buttons do nothing.
quantity-inputis outside the form, or its buttons are links. Use<button type="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-openis missing, or the page has anothersmootify-cartwith nothing around its form, such as the cart page. - 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.