Next Commerce
Cart Summary

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 attributeRenders
data-summary-linesOne row per cart line item
data-summary-offer-discountsOne row per offer (promotion) discount
data-summary-voucher-discountsOne 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 field summary.lines).

Don't mix them in the same template. Every {item.X} token below is also reachable as {line.X}.

TokenDescription
{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".

PathTypeDescription
item.packageIdnumberPackage ref_id
item.namestringPackage display name
item.imagestringProduct image URL
item.quantitynumberUnit quantity for this line
item.productNamestringProduct name
item.variantNamestringVariant name (empty when no variant)
item.skustringProduct SKU
item.isRecurringbooleanWhether the line is a subscription
item.interval'day' | 'month' | nullSubscription interval
item.intervalCountnumber | nullSubscription interval count
item.frequencystringHuman-readable frequency (e.g. "Monthly", "Every 7 days")
item.recurringPricenumber | nullRecurring unit price
item.originalRecurringPricenumber | nullOriginal recurring unit price
item.pricenumberPackage price after discounts
item.originalPricenumberPackage price before discounts
item.unitPricenumberUnit price after discounts
item.originalUnitPricenumberUnit price before discounts
item.discountAmountnumberTotal discount on the line
item.discountPercentagenumberDiscount as a percentage of original (e.g. 25 for 25%)
item.hasDiscountbooleanWhether the line has any discount applied
item.currencystringActive 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.

PathTypeDescription
discount.namestringOffer or voucher name
discount.amountnumberDiscount amount as a raw number (currency symbols stripped)
discount.amountFormattedstringOriginal currency-formatted amount string
discount.descriptionstringOffer 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.

SelectorWhere it livesWhen applied
[data-summary-lines].next-summary-emptyline list containercart has no lines
[data-summary-lines].next-summary-has-itemsline list containercart has at least one line
[data-line-discounts].next-summary-emptyper-line discount containerthis line has no discounts
[data-line-discounts].next-summary-has-itemsper-line discount containerthis 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

TokenDescription
{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:

ClassWhen
next-summary-emptyNo items in the list
next-summary-has-itemsOne 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>

On this page