Cart Lines & Discounts
Inside a custom template you can add list containers that are populated after each render. Each container must include its own <template> child that defines the markup for a single row.
Three types of list are supported:
| Container attribute | Renders |
|---|---|
data-summary-lines | One row per cart line item |
data-summary-offer-discounts | One row per offer (promotion) discount |
data-summary-voucher-discounts | One row per voucher (coupon) discount |
Line Items List
Use data-summary-lines to render one row per cart line with full pricing detail. Lines are sorted by package_id ascending.
<div data-next-cart-summary>
<template>
<ul data-summary-lines>
<template>
<li>
<img src="{item.image}" alt="{item.name}" />
<span>{item.name}</span>
<span>{item.quantity} × {item.unitPrice}</span>
<span>{item.price}</span>
</li>
</template>
</ul>
<div class="row"><span>Subtotal</span><span>{subtotal}</span></div>
<div class="row"><span>Shipping</span><span>{shipping}</span></div>
<div class="row"><span>Total</span><span>{total}</span></div>
</template>
</div>Line item tokens
Use item.* tokens (canonical) or line.* (1:1 alias). Both vocabularies are 1:1 equivalents — pick whichever fits your mental model:
item.*— the cart-shopper view (the same field names used elsewhere when rendering cart items).line.*— the invoice / order-row view (matches the API fieldsummary.lines).
Don't mix them in the same template. Every {item.X} token below is also reachable as {line.X}.
| Token | Description |
|---|---|
{item.packageId} | Package ref_id |
{item.name} | Package display name |
{item.image} | Product image URL |
{item.quantity} | Quantity in cart |
{item.productName} | Product name |
{item.variantName} | Variant name, if applicable |
{item.sku} | Product SKU |
{item.price} | Line total after discounts (quantity × unitPrice) |
{item.originalPrice} | Line subtotal before discounts (quantity × originalUnitPrice) |
{item.unitPrice} | Per-unit price after discounts |
{item.originalUnitPrice} | Per-unit price before discounts |
{item.discountAmount} | Total discount applied to this line |
{item.discountPercentage} | Discount as a percentage of the original unit price, formatted with % (e.g. "20%") |
{item.hasDiscount} | "show" when a discount applies — use as a CSS class or visibility flag |
{item.isRecurring} | "true" / "false" — whether this is a subscription line |
{item.recurringPrice} | Recurring unit price for subscriptions |
{item.originalRecurringPrice} | Original recurring price before any discount |
{item.interval} | Billing interval: "day", "month", or empty for one-time |
{item.intervalCount} | Number of intervals per billing cycle (e.g. "3") |
{item.frequency} | Human-readable billing frequency (e.g. "Monthly", "Every 3 months") |
{item.currency} | Active currency code for this line (e.g. "USD") |
Pre-v0.4.11 line.* names removed
Several pre-v0.4.11 {line.*} token names no longer exist and will render as empty strings: {line.qty}, {line.priceTotal}, {line.packagePrice}, {line.originalPackagePrice}, {line.totalDiscount}, {line.priceRetail}, {line.priceRetailTotal}, {line.priceRecurring}, {line.priceRecurringTotal}, {line.hasSavings}. Note also that {line.price} previously rendered the per-unit price; it now renders the line total (matching {item.price}). See the migration table.
Campaign data fields
Image, name, product name, SKU, and recurring price fields come from the cart store's campaign-data enrichment. If campaign data is unavailable, these fields render as empty strings.
Per-Line Conditional Rendering
Inside any data-summary-lines row template (or any per-discount template), use data-next-show and data-next-hide to render elements conditionally per row. Conditions are evaluated synchronously at render time against raw line / discount data — real numbers, real booleans — so comparison operators work as expected. Hidden elements are removed from the DOM and the attributes are stripped, so global show/hide processing does not double-process them.
<ul data-summary-lines>
<template>
<li>
<span>{item.name}</span>
<span data-next-show="item.quantity > 1">×{item.quantity}</span>
<span data-next-hide="item.hasDiscount">{item.price}</span>
<span data-next-show="item.isRecurring">Renews {item.frequency}</span>
</li>
</template>
</ul>Syntax rules
- Use the no-braces syntax. Write
item.quantity > 1, not{item.quantity} > 1— the latter is substituted before evaluation and produces a malformed expression. - Supported operators:
>,>=,<,<=,==,===,!=,!==,&&,||,!, parentheses. - A bare property is treated as a truthy check:
data-next-show="item.isRecurring". - Function-call conditions (e.g.
item.hasFlag(x)) are not handled.
item.* namespace (or line.*)
Available in data-summary-lines row templates and inside any nested data-line-discounts template. Conditions can use line.X interchangeably with item.X — e.g. data-next-show="line.quantity > 1" works identically to data-next-show="item.quantity > 1".
| Path | Type | Description |
|---|---|---|
item.packageId | number | Package ref_id |
item.name | string | Package display name |
item.image | string | Product image URL |
item.quantity | number | Unit quantity for this line |
item.productName | string | Product name |
item.variantName | string | Variant name (empty when no variant) |
item.sku | string | Product SKU |
item.isRecurring | boolean | Whether the line is a subscription |
item.interval | 'day' | 'month' | null | Subscription interval |
item.intervalCount | number | null | Subscription interval count |
item.frequency | string | Human-readable frequency (e.g. "Monthly", "Every 7 days") |
item.recurringPrice | number | null | Recurring unit price |
item.originalRecurringPrice | number | null | Original recurring unit price |
item.price | number | Package price after discounts |
item.originalPrice | number | Package price before discounts |
item.unitPrice | number | Unit price after discounts |
item.originalUnitPrice | number | Unit price before discounts |
item.discountAmount | number | Total discount on the line |
item.discountPercentage | number | Discount as a percentage of original (e.g. 25 for 25%) |
item.hasDiscount | boolean | Whether the line has any discount applied |
item.currency | string | Active currency code (e.g. "USD") |
Raw values vs text tokens
The item.* paths used in conditions are raw types (numbers, booleans). The same paths used as {item.*} text tokens still render as currency-formatted strings or "show" / "hide" for backwards compatibility. So data-next-show="item.hasDiscount" evaluates a boolean, while class="{item.hasDiscount}" substitutes the literal string show or hide.
discount.* namespace
Available in data-summary-offer-discounts, data-summary-voucher-discounts, and data-line-discounts row templates.
| Path | Type | Description |
|---|---|---|
discount.name | string | Offer or voucher name |
discount.amount | number | Discount amount as a raw number (currency symbols stripped) |
discount.amountFormatted | string | Original currency-formatted amount string |
discount.description | string | Offer or voucher description |
Cart-wide conditions belong outside line templates
Conditions referencing other namespaces (e.g. cart.hasItems) inside a line template are passed through untouched and processed by the global show/hide system. Each line is destroyed and recreated on every cart re-render, so attaching global conditions to short-lived line elements thrashes. Place cart-wide conditions outside the data-summary-lines template.
Cart Line Recipes
Common patterns for displaying line item pricing. Every recipe assumes the markup lives inside a data-summary-lines row template.
Show final price with strikethrough original
{item.price} is the final per-line total after discounts. {item.originalPrice} is the same total before discounts. Wrap the strikethrough in data-next-show="item.hasDiscount" so it only renders on discounted lines.
<div class="line-prices">
<s data-next-show="item.hasDiscount">{item.originalPrice}</s>
<strong>{item.price}</strong>
</div>The <s> element is removed from the DOM entirely on full-priced lines — no CSS rule needed, and screen readers do not pick up an irrelevant original price.
Show per-unit prices ("each")
Use {item.unitPrice} and {item.originalUnitPrice} for the per-unit value before quantity is multiplied in.
<div class="unit-prices">
<s data-next-show="item.hasDiscount">{item.originalUnitPrice} ea</s>
<span>{item.unitPrice} ea</span>
</div>Pair this with {item.quantity} to make the math obvious to the customer:
<span>{item.quantity} × {item.unitPrice} = {item.price}</span>Show savings amount and percentage
{item.discountAmount} is the formatted total discount for the line. {item.discountPercentage} is the formatted percentage (e.g., "25%").
<div class="savings" data-next-show="item.hasDiscount">
Save {item.discountAmount} ({item.discountPercentage} off)
</div>Place data-next-show="item.hasDiscount" on the wrapper so the entire savings block is removed when there is no discount.
For a discount badge that only appears on meaningful discounts (not 1% rounding errors):
<span class="badge" data-next-show="item.discountPercentage >= 20">
-{item.discountPercentage}
</span>Show recurring (subscription) line with frequency and one-time fallback
Use data-next-show="item.isRecurring" and data-next-hide="item.isRecurring" together to render two mutually exclusive badges. Each line gets exactly one — the conditions are evaluated independently against the same boolean.
<li class="line-item">
<img src="{item.image}" alt="{item.name}" />
<div class="line-details">
<strong>{item.name}</strong>
<span>×{item.quantity}</span>
<span class="recurring-badge" data-next-show="item.isRecurring">
🔁 {item.frequency} · then {item.recurringPrice}
</span>
<span class="one-time-badge" data-next-hide="item.isRecurring">
One-time purchase
</span>
</div>
<span class="line-total">{item.price}</span>
</li>{item.frequency} resolves to human-readable strings like "Daily", "Monthly", "Every 7 days", "Every 3 months". {item.recurringPrice} is currency-formatted and empty when the line is not recurring.
Combine final, original, savings, and per-unit
<li class="line-item">
<img src="{item.image}" alt="{item.name}" />
<div class="info">
<strong>{item.name}</strong>
<span class="variant">{item.variantName}</span>
<span class="qty-original" data-next-show="item.hasDiscount">
was {item.originalUnitPrice} per unit
</span>
</div>
<div class="prices">
<span class="unit">{item.unitPrice} × {item.quantity}</span>
<strong class="total">{item.price}</strong>
<s class="line-original" data-next-show="item.hasDiscount">{item.originalPrice}</s>
</div>
<div class="savings" data-next-show="item.hasDiscount">
Save {item.discountAmount} ({item.discountPercentage})
</div>
</li>All seven item.* price tokens come from the same API summary calculation, so the numbers always add up. Both data-next-show="item.hasDiscount" blocks are evaluated and removed in a single per-line pass.
Cart Line CSS Utilities
Helpers exposed for line item layouts. Use these to drive show/hide and styling without JavaScript.
| Selector | Where it lives | When applied |
|---|---|---|
[data-summary-lines].next-summary-empty | line list container | cart has no lines |
[data-summary-lines].next-summary-has-items | line list container | cart has at least one line |
[data-line-discounts].next-summary-empty | per-line discount container | this line has no discounts |
[data-line-discounts].next-summary-has-items | per-line discount container | this line has at least one discount |
For per-element show/hide on individual rows, prefer data-next-show / data-next-hide over CSS classes — the elements are removed from the DOM at render time and the attributes are stripped, so there is no flash of unstyled content and no orphan markup.
/* hide the entire per-line discounts list when empty */
[data-line-discounts].next-summary-empty { display: none; }
/* hide the whole line items section when the cart is empty */
[data-summary-lines].next-summary-empty { display: none; }Legacy CSS class pattern
The {item.hasDiscount} text token still renders as the literal string "show" or "hide", so existing templates that drop it into a class attribute (<s class="{item.hasDiscount}">…</s> paired with a .hide { display: none } rule) continue to work. New templates should prefer data-next-show / data-next-hide — same behaviour, no global CSS rule required, and the element is fully removed instead of just hidden.
Want a single value outside a template?
If you only need one cart-level number (e.g., total discount, item count) and not a full per-line render, use Cart Display instead — it binds one element to one cart property without a <template>.
Discount Lists
Use data-summary-offer-discounts or data-summary-voucher-discounts to render one row per applied discount.
<div data-next-cart-summary>
<template>
<div class="row"><span>Subtotal</span><span>{subtotal}</span></div>
<ul data-summary-offer-discounts>
<template><li>{discount.name} — -{discount.amount}</li></template>
</ul>
<ul data-summary-voucher-discounts>
<template><li>Coupon {discount.name}: -{discount.amount}</li></template>
</ul>
<div class="row"><span>Total</span><span>{total}</span></div>
</template>
</div>Discount row tokens
| Token | Description |
|---|---|
{discount.name} | Discount or offer name (e.g., "Bundle deal", "SAVE20") |
{discount.amount} | Formatted discount amount (e.g., "$10.00") |
{discount.description} | Optional human-readable description; empty string when absent |
Voucher discounts only appear after a coupon is applied — the voucher list will be empty until then. Use next-summary-empty on the container to hide it when blank.
Per-Line Discount List
Each line item can have its own list of individual discounts. Place a data-line-discounts container inside the data-summary-lines item template:
<ul data-summary-lines>
<template>
<li class="line-item">
<span>{item.quantity}× {item.name}</span>
<span>{item.price}</span>
<ul data-line-discounts>
<template>
<li class="line-discount">{discount.name} — -{discount.amount}</li>
</template>
</ul>
</li>
</template>
</ul>data-line-discounts receives the same next-summary-empty / next-summary-has-items classes as the other list containers.
List State Classes
All list containers receive these classes after each render:
| Class | When |
|---|---|
next-summary-empty | No items in the list |
next-summary-has-items | One or more items in the list |
Use .next-summary-empty { display: none } to hide an empty discount section automatically.
Full Example
<div data-next-cart-summary>
<template>
<!-- Per-item breakdown -->
<ul class="line-items" data-summary-lines>
<template>
<li class="line-item">
<img src="{item.image}" alt="{item.name}" />
<div>
<strong>{item.name}</strong>
<span>{item.variantName}</span>
<span class="recurring" data-next-show="item.isRecurring">
Renews {item.frequency}
</span>
</div>
<div class="prices">
<s data-next-show="item.hasDiscount">{item.originalUnitPrice}</s>
<span>{item.unitPrice} × {item.quantity}</span>
<strong>{item.price}</strong>
</div>
<ul data-line-discounts>
<template>
<li class="line-discount">{discount.name} — -{discount.amount}</li>
</template>
</ul>
</li>
</template>
</ul>
<!-- Totals -->
<div class="row"><span>Subtotal</span><span>{subtotal}</span></div>
<!-- Offer discounts -->
<ul class="discount-list" data-summary-offer-discounts>
<template>
<li class="discount-item">{discount.name} — -{discount.amount}</li>
</template>
</ul>
<!-- Voucher discounts -->
<ul class="discount-list" data-summary-voucher-discounts>
<template>
<li class="discount-item">Coupon {discount.name}: -{discount.amount}</li>
</template>
</ul>
<div class="row shipping-row">
<span>Shipping</span>
<span>{shipping}</span>
</div>
<div class="row total-row"><span>Total</span><span>{total}</span></div>
</template>
</div>
<style>
.next-free-shipping .shipping-row { display: none; }
.next-summary-empty { display: none; }
</style>