Skip to content
Nizivo UI

Customer card

Who a customer is, what they are worth and how to reach them, an Avatar, their name and email, their orders and total spent, and their tags. A server composition; it ships no JavaScript.

From Figma 05 Commerce, Customer Card (754:530; an orphan master with no live component, seen in Stage 7275:14124). Uses Card, Avatar, Badge, Text and NumberValue.

Examples

Customer

Figma 05's Customer Card (754:530, an orphan master): who they are, what they are worth and how to reach them. The email is a link; the figures are a description list. A server composition: it ships no JavaScript. Without a photo the Avatar shows the initials; with one, pass its src.

Jonah Okafor

jonah@example.com
Orders
14
Total spent
$1,284.00
Avg. order
$91.71
  • VIP
  • Subscriber
import type { JSX } from 'react';
import { Avatar } from '@nizivo/react/avatar';
import { Badge, type BadgeTone } from '@nizivo/react/badge';
import { Card } from '@nizivo/react/card';
import { NumberValue } from '@nizivo/react/number-value';
import { Text } from '@nizivo/react/text';

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

interface Customer {
  name: string;
  email: string;
  orders: number;
  spent: number;
  tags: { label: string; tone: BadgeTone }[];
}

const CUSTOMER: Customer = {
  name: 'Jonah Okafor',
  email: 'jonah@example.com',
  orders: 14,
  spent: 1284,
  tags: [
    { label: 'VIP', tone: 'brand' },
    { label: 'Subscriber', tone: 'neutral' },
  ],
};

/** One figure: a `<dt>` label over its `<dd>` value. */
function Stat({ label, children }: { label: string; children: JSX.Element }): JSX.Element {
  return (
    <div className="nzp-customer-card-stat">
      <Text render={<dt />} variant="label-m" tone="secondary">
        {label}
      </Text>
      <Text render={<dd />} variant="numeric-m">
        {children}
      </Text>
    </div>
  );
}

export function CustomerCardExample(): JSX.Element {
  const average = CUSTOMER.spent / CUSTOMER.orders;
  return (
    <Card.Root
      className="nzp-customer-card"
      render={<section aria-labelledby="customer-card-name" />}
    >
      <div className="nzp-customer-card-identity">
        <Avatar name={CUSTOMER.name} size="xl" decorative />
        <div className="nzp-customer-card-name">
          <Card.Title id="customer-card-name">{CUSTOMER.name}</Card.Title>
          <a className="nzp-customer-card-email" href={`mailto:${CUSTOMER.email}`}>
            {CUSTOMER.email}
          </a>
        </div>
      </div>
      <dl className="nzp-customer-card-stats">
        <Stat label="Orders">
          <NumberValue value={CUSTOMER.orders} />
        </Stat>
        <Stat label="Total spent">
          <NumberValue value={CUSTOMER.spent} format={USD} />
        </Stat>
        <Stat label="Avg. order">
          <NumberValue value={average} format={USD} />
        </Stat>
      </dl>
      <ul className="nzp-customer-card-tags" aria-label="Tags">
        {CUSTOMER.tags.map((tag) => (
          <li key={tag.label}>
            <Badge tone={tag.tone} dot={false}>
              {tag.label}
            </Badge>
          </li>
        ))}
      </ul>
    </Card.Root>
  );
}

Stylesheet

customer-card.css
@layer nz.tokens, nz.themes, nz.reset, nz.base, nz.components, nz.patterns, nz.utilities;

@layer nz.patterns {
  .nzp-customer-card {
    max-inline-size: 23.75rem;
  }

  .nzp-customer-card-identity {
    display: flex;
    align-items: center;
    gap: var(--nz-space-3);
  }

  .nzp-customer-card-name {
    display: flex;
    flex-direction: column;
    gap: var(--nz-space-0-5);
    min-inline-size: 0;
  }

  .nzp-customer-card-email {
    overflow-wrap: anywhere;
    color: var(--nz-text-brand);
    font: var(--nz-font-body-m);
  }

  /* three figures in a row; they wrap on a narrow card */
  .nzp-customer-card-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
    gap: var(--nz-space-3);
    margin: 0;
  }

  .nzp-customer-card-stat {
    display: flex;
    flex-direction: column;
    gap: var(--nz-space-0-5);
    padding: var(--nz-space-2) var(--nz-space-3);
    border-radius: var(--nz-radius-md);
    background: var(--nz-bg-subtle);
  }

  .nzp-customer-card-stat > dd {
    margin: 0;
  }

  .nzp-customer-card-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nz-space-1-5);
    margin: 0;
    padding: 0;
    list-style: none;
  }
}

Notes

  • The Avatar is decorative, because the name is printed beside it, and shows the initials when there is no photo, which Figma's raw ellipse cannot. Pass src only for a photo that is known to load.
  • How to reach them. Figma's doc text promises it and draws no action; here the email is a mailto: link. Add a call or message action beside it if your app has one.
  • The figures are a description list: each label is a <dt>, each figure a <dd>, so they are read as pairs. Money is NumberValue with the store's currency, and the counts go through Intl.NumberFormat.
  • Contrast. Figma's stat labels are text/tertiary on bg/subtle, 4.16:1; here they are text/secondary.
  • Tags are a list of Badges without a dot: the words are the tags, and no colour means more than its word.

On this page