# Magic Box

> A box the customer fills with products you choose, under rules you set, that reaches the cart and checkout as one line with its own title, image and discount.

Status: Ready (Smootify 2.0)  
Add-on: Magic Box  
1.x page: https://docs.smootify.io/docs/ecommerce/add-ons/magic-box  
URL: https://www.smootify.io/docs/features/magic-box  
Updated: 2026-10-11

Magic Box lets the customer build a box: a case of 6 wines, a sampler of 3 coffees, a PC with one part from each category. The customer picks products from the lists you design, and the box goes to the cart as **one line**, with its own title and image. At checkout it stays one line, and it can carry a discount. This page covers the Shopify side, the box, its rules, categories and the box panel.

> [!NOTE]
> Turn on **Magic Box** in the Smootify dashboard of the site. It is included in every plan, the free one too, but it always needs to be turned on, also on your `.webflow.io` address. Turning it on adds the **Magic Box** metaobject definition to Shopify and the checkout function that merges the box into one line.

## How it fits together

Everything goes inside one Custom element, `smootify-magic-box`.

What Smootify needs:

- a `smootify-magic-box` element;
- inside it, the products the customer can pick, each with its own add-to-cart form. Inside the box, these forms add to the box instead of the cart;
- inside it, a `smootify-magic-box-cart`: the box panel. It holds a `box-item` template, repeated once per product in the box, and a Form Block that adds the whole box to the cart. Without it, the box is removed.

What is up to you:

- how the products are listed: a Collection list, several lists, a Slider or Tabs;
- where the box panel sits (a sidebar, a sticky bar, under the products) and what it shows;
- the layout, classes and styles of every part.

A fixed box of 6 looks like this in the Navigator:

```html
<smootify-magic-box data-min="6" data-max="6">
  <!-- Collection list settings. Source: Products. Filter: Collections contains Wines -->
  <div class="w-dyn-list">
    <div class="w-dyn-items">
      <div class="w-dyn-item">
        <smootify-product data-id="{Shopify ID}">
          <div product="title">{Name}</div>
          <smootify-add-to-cart>
            <div class="w-form">
              <form><input type="submit" value="Add to box"></form>
              <div class="w-form-done"><div>Added</div></div>
              <div class="w-form-fail"><div>Something went wrong</div></div>
            </div>
          </smootify-add-to-cart>
        </smootify-product>
      </div>
    </div>
  </div>
  <smootify-magic-box-cart>
    <items-list>
      <box-item><div box-item="title">Product</div><div box-item="quantity">1</div><button data-action="remove">Remove</button></box-item>
    </items-list>
    <empty-state>Your box is empty</empty-state>
    <div class="w-form">
      <form><input type="submit" value="Add the box to cart"></form>
      <div class="w-form-done"><div>Added</div></div>
      <div class="w-form-fail"><div>Something went wrong</div></div>
    </div>
  </smootify-magic-box-cart>
</smootify-magic-box>
```

## 1. Give the box a title, an image and a discount

In the cart and at checkout, the box line shows a title and an image of its own. There are two ways to set them.

**A Magic Box entry in Shopify.** In Shopify, go to **Content > Metaobjects > Magic Box** and create an entry with a **Title**, an **Image** and, if you want one, a **Discount Percentage**. On `smootify-magic-box`, set `data-handle` to the entry’s handle. The merchant can then change the title, the image or the discount in Shopify without touching the site.

**A static box.** Set `data-title` and `data-image` on `smootify-magic-box`. The image must be a Shopify file: upload it in Shopify under **Content > Files** and copy its URL. Checkout ignores an image from anywhere else.

> [!WARNING]
> The discount can only come from a Magic Box entry. A static box has no discount.

The discount applies to every box the customer can add to the cart under its limits, not only to a full one. For a discount only on a full box, give the box the same `data-min` and `data-max`, such as `data-min="12" data-max="12"`.

The discount is a percentage off the whole box, applied at checkout.

## 2. Build the box

1. Add a Custom element with the tag `smootify-magic-box` around the products and the box panel.
2. Inside it, list the products. Any list works: a Collection list of [product cards](https://www.smootify.io/docs/build/product-cards), or a list loaded from Shopify without the CMS ([product lists](https://www.smootify.io/docs/build/product-lists)). Each card needs a `smootify-add-to-cart` with a Form Block inside. Its button adds the product to the box. Variant pickers and a quantity field work as on the [product page](https://www.smootify.io/docs/build/product-page).
3. Inside `smootify-magic-box`, add a Custom element with the tag `smootify-magic-box-cart`. Put inside it:
   - a Custom element with the tag `items-list`, holding one Custom element with the tag `box-item`. The `box-item` is the template: Smootify repeats it for each product in the box. `items-list` shows only while the box has products;
   - optionally, a Custom element with the tag `empty-state`, shown only while the box is empty;
   - a Form Block with a submit button. It adds the whole box to the cart.

Inside `box-item`:

- `box-item="title"`, `box-item="image"`, `box-item="price"`, `box-item="total"` (price × quantity), `box-item="quantity"` and `box-item="category"` show the product in the box;
- the `product` and `variant` values also work, such as `variant="title"` for the variant name. See [box item values](https://www.smootify.io/docs/reference/attributes/box-item);
- a Custom element with the tag `button` and `data-action="remove"` takes the product out of the box.

After a product goes into the box, the quantity field of its form goes back to 1.

## 3. Set the size of the box

These attributes go on `smootify-magic-box`:

| Attribute | What it does | Default |
|---|---|---|
| `data-min` | the fewest products the box can hold | 1 |
| `data-max` | the most products the box can hold | 100 (also the highest value) |
| `data-step` | the box size must be a multiple of this | 1 |
| `data-unique` | the same product can go in only once | off |

The box can be bought when the number of products is **between `data-min` and `data-max` and a multiple of `data-step`**. The add button stays disabled until then. Some sizes and the boxes they allow:

- **A fixed size.** `data-min="6"` and `data-max="6"`: exactly 6 products.
- **Sizes you choose.** `data-min="6"`, `data-max="12"`, `data-step="6"`: 6 or 12, nothing in between.
- **A free mix with a limit.** `data-max="10"` alone: from 1 to 10 products.
- **Sizes that do not share the step.** With `data-min="10"`, `data-max="25"` and `data-step="15"`, the box can be bought with 15 products only: 10 and 25 are not multiples of 15. A box of 10 or 20 needs `data-step="10"`.

> [!WARNING]
> Every size must be a multiple of the same step. A box of 1, 3 or 6 cannot be written with one step.

`data-unique` and the category rules below are on whenever they have a value, even `false`. Write `true`, and remove the attribute to turn a rule off.

When a product does not fit, the product’s form shows an error in its Error message. You can change the text of each error:

- `data-full-error`: the box is full. Default “Box is full”;
- `data-too-many-error`: the quantity is more than what fits. `{quantity}` in the text becomes the number that still fits;
- `data-only-one`: the product is already in the box and `data-unique` is on. Default “You can add only 1 item inside the box”;
- `data-only-one-per-category`: the category already has a product. `{category}` becomes the category name. Default “You can add only 1 item of {category} inside the box”.

## 4. One product per category

For a box that needs one piece from each group (a base, a sauce and a topping; a PC with a motherboard, a GPU and RAM), split the products into categories.

1. Wrap each group of products in a Custom element with the tag `smootify-box-category`.
2. Give it the custom attribute `data-name` with the category name, such as `Sauce`.

```html
<smootify-box-category data-name="Sauce">
  <!-- Collection list settings. Source: Products. Filter: Collections contains Sauces -->
  <div class="w-dyn-list">
    <div class="w-dyn-items">
      <div class="w-dyn-item">
        <smootify-product data-id="{Shopify ID}">
          <div product="title">{Name}</div>
          <smootify-add-to-cart>
            <div class="w-form">
              <form><input type="submit" value="Add"></form>
              <div class="w-form-done"><div>Added</div></div>
              <div class="w-form-fail"><div>Something went wrong</div></div>
            </div>
          </smootify-add-to-cart>
        </smootify-product>
      </div>
    </div>
  </div>
</smootify-box-category>
```

Then choose the rules on `smootify-magic-box`. For a pizza with a Base, a Sauce and a Topping:

- `data-unique-per-category="true"`: one product per category. A second pick in the same category shows an error in the Error message of the product's form.
- `data-auto-remove-category="true"`, with the one above: a new pick replaces the old one in its category instead of showing an error. Use it for a configurator.
- `data-all-categories="true"`: the box can be bought only when every category has a product. Until then, the add button stays disabled.

**Steps without interactions.** Put each category in a slide of a Webflow **Slider**, or in a pane of a Webflow **Tabs** element. When the customer adds a product, the Slider or Tabs moves on: to the next empty category with `data-all-categories`, otherwise to the next slide or tab. When the customer removes a product, it goes back to that product’s category.

**Completed categories.** A category gets the class `is-complete` while the box holds at least one of its products, and so do the slide and the tab link that show it when every category in them has one. To show a check mark inside a tab link, put it in an element with `box-condition="category-complete"`; `box-condition="category-incomplete"` is the opposite.

In the box panel, `data-prop="missingCategories"` lists the categories still empty, separated by commas. `separator` changes the separator, and `separator="repeat"` makes one copy of the element per missing category.

## 5. Show progress and the price

Inside `smootify-magic-box-cart`, outside the `box-item`, these elements follow the box:

```html
<div><span data-prop="currentQuantity">0</span> / <span data-prop="maxQuantity">6</span></div>
<div><div data-prop="leftQuantityWidth"></div></div>
<div box-condition="cannot-purchase">Missing: <span data-prop="missingCategories">Base, Sauce</span></div>
<smootify-price>
  <div data-prop="price">$0.00</div>
  <div data-prop="compareAtPrice">$0.00</div>
</smootify-price>
<div box-condition="can-purchase">You save <span data-prop="discount-amount">$0.00</span></div>
```

- `data-prop="currentQuantity"`, `"minQuantity"`, `"maxQuantity"`, `"leftQuantity"` (how many more fit): numbers;
- `data-prop="leftQuantityWidth"` on a Div block: its width follows how full the box is, for a progress bar. `data-prop="leftQuantityPercentage"` shows the same as text;
- `data-prop="discount-percentage"` and `data-prop="discount-amount"`: the discount of the box, as a percentage and as money;
- a `smootify-price` shows the box total after the discount in `data-prop="price"`, and the total before the discount in `data-prop="compareAtPrice"` when there is one;
- `box-condition="can-purchase"` and `box-condition="cannot-purchase"` show an element only while the box can, or cannot yet, be bought.

`smootify-magic-box-cart` also gets classes you can style as combo classes. For a box of 6 wines at $20.00 each, the panel goes through these states:

| State | Classes on `smootify-magic-box-cart` | What shows |
|---|---|---|
| Empty | `is-empty`, `is-disabled` | `empty-state` instead of `items-list`, `box-condition="cannot-purchase"`; the add button is disabled |
| Filling, 4 of 6 | `is-disabled` | `items-list`, `box-condition="cannot-purchase"`; the add button is disabled |
| Ready, 6 of 6 | `is-purchasable` | `box-condition="can-purchase"`; the add button is enabled; `data-prop="price"` $120.00 |
| Ready, with a 10% discount from a Magic Box entry | `is-purchasable`, `is-saving` | `data-prop="price"` $108.00 after the discount, `data-prop="compareAtPrice"` $120.00 before it, `data-prop="discount-amount"` $12.00 |

The full lists are in [Magic Box values](https://www.smootify.io/docs/reference/attributes/magic-box) and [Magic Box conditions](https://www.smootify.io/docs/reference/conditions/magic-box).

**The add button.** A Form Button works, or a Custom element with the tag `button` and `type="submit"`. It is disabled until the box can be bought. A second Custom element with the tag `button`, `type="submit"` and `name="buy-now"` adds the box and goes straight to checkout. `data-buy-now="true"` on `smootify-magic-box-cart` sends every submit to checkout.

**Extra fields.** A field in the box’s form, such as a gift message, is saved with the box, under its field name, and shows with the box in the cart. A name that starts with `_` is saved but not shown.

## 6. In the cart and at checkout

The box is one line in the cart, with the box title and image. In the [cart](https://www.smootify.io/docs/build/cart), a `box-item` inside the `cart-item` template repeats once per product in the box, with the cart line values such as `box-item="title"` and `box-item="quantity"`. The customer cannot change the quantity of a box: its quantity field is removed. With a `box-item` in the template, `cart-item="options"` lists only the box’s own fields, such as a gift message. Without one, it lists the products as text (“1 x Wool Socks”), as 1.x did.

At checkout Shopify shows the box as one line, with its title, image and discount.

**Subscriptions.** A box cannot be sold as a subscription: Shopify does not merge cart lines that have a selling plan. See [subscriptions](https://www.smootify.io/docs/features/subscriptions) for what you can build instead.

Developers can add a box to the cart from code with [`addBoxToCart`](https://www.smootify.io/docs/reference/api/methods/addboxtocart). They can also change the box title and image as the box fills, for example “Box of 4 wines”: see [the Magic Box example](https://www.smootify.io/docs/developers/events#example-a-magic-box-title-that-follows-its-contents).

## Before you publish

On your `.webflow.io` address, the Smootify debugger lists the Magic Box elements and tells you when they are not running. See [the debugger](https://www.smootify.io/docs/start/debugger).

> [!STUCK]
> - **Products go straight to the cart, one at a time.** Magic Box is not turned on in the Smootify dashboard. It needs to be on, also on `.webflow.io`.
> - **The whole box disappears.** `smootify-magic-box-cart` is missing, or `data-handle` does not match the handle of a Magic Box entry in Shopify.
> - **The add button never turns on.** The number of products is not between `data-min` and `data-max`, or not a multiple of `data-step`, or a category is empty with `data-all-categories`.
> - **There is no discount.** The box is static (`data-title`, `data-image`). Only a Magic Box entry carries a discount.
> - **The box image is missing at checkout.** The `data-image` URL is not a Shopify file. Upload it under **Content > Files**.
> - **A rule is on though you wrote `false`.** These rules turn on with any value. Remove the attribute instead.

## Coming from 1.x

- The default texts of `data-only-one` and `data-only-one-per-category` say “inside the box”, where 1.x said “inside the cart”.
