Skip to content
Nizivo UI

Order summary

An order's lines and totals on a Card, the totals a description list with the amounts in tabular figures. A server composition; it ships no JavaScript.

From Figma 05 Commerce, Order Summary (754:897; an orphan master with no live component, seen in Stage 7275:14350). Uses Card, Text, NumberValue and Separator.

Example

Figma 05's Order Summary (754:897, an orphan master): the order's lines, then its totals as a description list. A server composition: it ships no JavaScript. Each line's thumbnail stands in for the product's Image.

Order #1042

  • Stoneware mugMoss · 350 ml×2$56.00
  • Serving bowlKiln white×1$42.00
  • Side plateAsh · set of 4×1$14.50

Subtotal
$112.50
Shipping
Free
Tax
$8.74

Total
$121.24
import type { JSX } from 'react';
import { Card } from '@nizivo/react/card';
import { NumberValue } from '@nizivo/react/number-value';
import { Separator } from '@nizivo/react/separator';
import { Text } from '@nizivo/react/text';

/** Money as the order's currency: Intl formats it, never string concatenation. */
const USD = { style: 'currency', currency: 'USD' } as const;

interface LineItem {
  id: string;
  title: string;
  variant: string;
  quantity: number;
  /** The line's total, not the unit price (Figma 05 §3.10). */
  total: number;
}

const ITEMS: LineItem[] = [
  { id: 'mug', title: 'Stoneware mug', variant: 'Moss · 350 ml', quantity: 2, total: 56 },
  { id: 'bowl', title: 'Serving bowl', variant: 'Kiln white', quantity: 1, total: 42 },
  { id: 'plate', title: 'Side plate', variant: 'Ash · set of 4', quantity: 1, total: 14.5 },
];

/** One amount in the totals list: a `<dl>` pair, the value at the line's end in tabular figures. */
function Row({ label, value }: { label: string; value: number | string }): JSX.Element {
  return (
    <div className="nzp-order-summary-row">
      <Text render={<dt />} tone="secondary">
        {label}
      </Text>
      <Text render={<dd />} variant="numeric-m">
        {typeof value === 'number' ? <NumberValue value={value} format={USD} /> : value}
      </Text>
    </div>
  );
}

export function OrderSummaryExample(): JSX.Element {
  const subtotal = ITEMS.reduce((sum, item) => sum + item.total, 0);
  const tax = 8.74;
  return (
    <Card.Root className="nzp-order-summary" render={<section aria-labelledby="order-1042" />}>
      <Card.Title id="order-1042">Order #1042</Card.Title>
      <ul className="nzp-order-summary-lines">
        {ITEMS.map((item) => (
          <li key={item.id} className="nzp-order-summary-line">
            <span className="nzp-order-summary-thumb" aria-hidden="true" />
            <span className="nzp-order-summary-item">
              <Text render={<span />} variant="label-m-strong">
                {item.title}
              </Text>
              <Text render={<span />} tone="tertiary">
                {item.variant}
              </Text>
            </span>
            <Text render={<span />} variant="mono-m" tone="tertiary">
              ×{item.quantity}
            </Text>
            <Text render={<span />} variant="numeric-m">
              <NumberValue value={item.total} format={USD} />
            </Text>
          </li>
        ))}
      </ul>
      <Separator />
      <dl className="nzp-order-summary-totals">
        <Row label="Subtotal" value={subtotal} />
        <Row label="Shipping" value="Free" />
        <Row label="Tax" value={tax} />
      </dl>
      <Separator />
      <dl className="nzp-order-summary-totals">
        <div className="nzp-order-summary-row">
          <Text render={<dt />} variant="label-l">
            Total
          </Text>
          <Text render={<dd />} variant="numeric-l">
            <NumberValue value={subtotal + tax} format={USD} />
          </Text>
        </div>
      </dl>
    </Card.Root>
  );
}

Stylesheet

order-summary.css
@layer nz.tokens, nz.themes, nz.reset, nz.base, nz.components, nz.patterns, nz.utilities;

@layer nz.patterns {
  .nzp-order-summary {
    display: flex;
    flex-direction: column;
    gap: var(--nz-space-4);
    max-inline-size: 23.75rem;
  }

  .nzp-order-summary-lines {
    display: flex;
    flex-direction: column;
    gap: var(--nz-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .nzp-order-summary-line {
    display: flex;
    align-items: center;
    gap: var(--nz-space-3);
  }

  .nzp-order-summary-thumb {
    flex: none;
    inline-size: 3rem;
    block-size: 3rem;
    border-radius: var(--nz-radius-md);
    background: var(--nz-bg-muted);
  }

  .nzp-order-summary-item {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--nz-space-0-5);
    min-inline-size: 0;
  }

  .nzp-order-summary-totals {
    display: flex;
    flex-direction: column;
    gap: var(--nz-space-2);
    margin: 0;
  }

  .nzp-order-summary-row {
    display: flex;
    justify-content: space-between;
    gap: var(--nz-space-4);
  }

  .nzp-order-summary-row > dd {
    margin: 0;
    text-align: end;
  }
}

Notes

  • Money. NumberValue formats every amount with Intl.NumberFormat and the order's currency, so a zero-decimal currency or another locale needs no other change. Never build an amount by concatenating strings.
  • Line totals. Figma shows the line's total with no unit price (05 §3.10); say so in a column header or a visually hidden word if your readers could take it for the unit price.
  • Totals. A <dl>: each label is a <dt>, each amount a <dd>, read as pairs by screen readers. A discount row is a negative amount; give it words ("Discount") rather than colour alone.

On this page