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
@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.
NumberValueformats every amount withIntl.NumberFormatand 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.
Patterns
Domain compositions built only from the published packages, for you to copy into your app and change. The source is MIT, not Apache-2.0, so a copy you edit owes no change notice.
Product card
A catalogue tile whose whole card links to the product, with a photo, the title, the price (and on a sale the price before it) and a status Badge. A server composition; it ships no JavaScript.