Skip to content
Nizivo UI

Discounts page

A discount index inside the app shell, with a notice, key figures, status links and a card per discount showing its status, dates and usage on a Meter. It ships no JavaScript.

From Figma 08 Overview Dashboard, Pattern 5 · Discounts (7275:16204). Uses PageHeader, Button, Alert, KpiTile, TabNav, Card, Badge, Meter and Text.

The example is the page's content only: in the app it goes inside AppShell, which owns the sidebar, the top bars and <main>.

// app/(store)/layout.tsx: the shell wraps every store page; the page renders its content
import { AppShell } from '@nizivo/react/app-shell';

<AppShell sidebar={sidebar} topBar={topBar} mobileTopBar={mobileTopBar} tabBar={tabBar}>
  <DiscountsPageExample />
</AppShell>;

Example

Discounts

Figma 08's Pattern 5, Discounts: the content of the app shell's <main>. The page header, a notice, three key figures and the status links, then the discounts as a list of cards: each code with its offer, its status in words, its dates and a Meter of how many of its uses are taken. A server composition: it ships no JavaScript.

Discounts

Discounts now stack with free shipping
A code and free shipping can apply to the same order. Edit a discount to turn it off.
Active discounts
3
+50%vs Sep
Uses · 30 days
1,284
+15.2%vs Sep
Discounted sales
$8,920
+8.7%vs Sep
  • AUTUMN20

    20% off all mugs and bowls

    Active
    Uses
  • FREESHIP

    Free shipping over $50

    Active
    Uses
  • WELCOME10

    10% off a first order

    Active
    Uses
  • HOLIDAY25

    25% off gift sets

    Scheduled
    Uses
  • NEWYEAR

    $10 off orders over $60

    Scheduled
    Uses
  • SUMMER15

    15% off tableware

    Expired
    Uses
import type { JSX } from 'react';
import { Plus } from '@nizivo/icons';
import { Alert } from '@nizivo/react/alert';
import { Badge, type BadgeTone } from '@nizivo/react/badge';
import { Button } from '@nizivo/react/button';
import { Card } from '@nizivo/react/card';
import { KpiTile } from '@nizivo/react/kpi-tile';
import { Meter, type MeterTone } from '@nizivo/react/meter';
import { PageHeader } from '@nizivo/react/page-header';
import { TabNav } from '@nizivo/react/tab-nav';
import { Text } from '@nizivo/react/text';

type Status = 'active' | 'scheduled' | 'expired';

interface Discount {
  code: string;
  offer: string;
  status: Status;
  /** When it starts or ends, as the store's own words; the `<time>` keeps the date. */
  when: [label: string, date: string];
  used: number;
  limit: number;
}

const DISCOUNTS: Discount[] = [
  {
    code: 'AUTUMN20',
    offer: '20% off all mugs and bowls',
    status: 'active',
    when: ['Ends Nov 30', '2026-11-30'],
    used: 312,
    limit: 500,
  },
  {
    code: 'FREESHIP',
    offer: 'Free shipping over $50',
    status: 'active',
    when: ['Ends Dec 31', '2026-12-31'],
    used: 846,
    limit: 1000,
  },
  {
    code: 'WELCOME10',
    offer: '10% off a first order',
    status: 'active',
    when: ['Ends Jan 31', '2027-01-31'],
    used: 128,
    limit: 1000,
  },
  {
    code: 'HOLIDAY25',
    offer: '25% off gift sets',
    status: 'scheduled',
    when: ['Starts Dec 1', '2026-12-01'],
    used: 0,
    limit: 300,
  },
  {
    code: 'NEWYEAR',
    offer: '$10 off orders over $60',
    status: 'scheduled',
    when: ['Starts Jan 1', '2027-01-01'],
    used: 0,
    limit: 200,
  },
  {
    code: 'SUMMER15',
    offer: '15% off tableware',
    status: 'expired',
    when: ['Ended Aug 31', '2026-08-31'],
    used: 400,
    limit: 400,
  },
];

const STATUS: Record<Status, [label: string, badge: BadgeTone, meter: MeterTone]> = {
  active: ['Active', 'success', 'brand'],
  scheduled: ['Scheduled', 'info', 'brand'],
  expired: ['Expired', 'neutral', 'neutral'],
};

const TABS = [
  ['all', 'All'],
  ['active', 'Active'],
  ['scheduled', 'Scheduled'],
  ['expired', 'Expired'],
] as const;

export function DiscountsPageExample(): JSX.Element {
  return (
    <div className="nzp-discounts-page">
      <PageHeader
        level={2}
        title="Discounts"
        actions={
          <Button variant="primary" leadingIcon={<Plus />}>
            Create discount
          </Button>
        }
      />
      <Alert title="Discounts now stack with free shipping">
        A code and free shipping can apply to the same order. Edit a discount to turn it off.
      </Alert>
      <section aria-label="Key figures" className="nzp-discounts-page-kpis">
        <KpiTile label="Active discounts" value={3} delta={0.5} comparison="vs Sep" />
        <KpiTile label="Uses · 30 days" value={1284} delta={0.152} comparison="vs Sep" />
        <KpiTile
          label="Discounted sales"
          value={8920}
          format={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
          delta={0.087}
          comparison="vs Sep"
        />
      </section>
      <TabNav.Root aria-label="Discount status">
        {TABS.map(([value, label]) => (
          <TabNav.Link key={value} href={`?status=${value}`} current={value === 'all'}>
            {label}
          </TabNav.Link>
        ))}
      </TabNav.Root>
      <ul className="nzp-discounts-page-list" aria-label="Discounts">
        {DISCOUNTS.map((discount) => {
          const [label, badge, tone] = STATUS[discount.status];
          return (
            <Card.Root key={discount.code} render={<li />} className="nzp-discounts-page-card">
              <Card.Header>
                <Card.Title>
                  <Text render={<span />} variant="mono-m-strong">
                    {discount.code}
                  </Text>
                </Card.Title>
                <Card.Description>{discount.offer}</Card.Description>
                <Card.Action>
                  <Badge tone={badge}>{label}</Badge>
                </Card.Action>
              </Card.Header>
              <Meter
                label="Uses"
                value={discount.used}
                max={discount.limit}
                tone={tone}
                valueText={`${String(discount.used)} of ${String(discount.limit)}`}
              />
              <Card.Footer className="nzp-discounts-page-footer">
                <Text render={<span />} tone="secondary">
                  <time dateTime={discount.when[1]}>{discount.when[0]}</time>
                </Text>
                <Button size="sm" aria-label={`Edit ${discount.code}`}>
                  Edit
                </Button>
              </Card.Footer>
            </Card.Root>
          );
        })}
      </ul>
    </div>
  );
}

Stylesheet

discounts-page.css
@layer nz.tokens, nz.themes, nz.reset, nz.base, nz.components, nz.patterns, nz.utilities;

/* Figma 08 Pattern 5 (7275:16204): the header, the notice, the KPI row (16 apart), the status links,
   then the discount cards three to a row, 24 apart (Figma's Discount Card is a set with no code
   component, so each is a Card). The grids follow the page's own width. */
@layer nz.patterns {
  .nzp-discounts-page {
    display: flex;
    flex-direction: column;
    gap: var(--nz-space-6);
    min-inline-size: 0;
    container-type: inline-size;
  }

  .nzp-discounts-page-kpis {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--nz-space-4);
  }

  .nzp-discounts-page-list {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--nz-space-6);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  @container (width >= 36rem) {
    .nzp-discounts-page-kpis {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .nzp-discounts-page-list {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  @container (width >= 56rem) {
    .nzp-discounts-page-list {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }

  .nzp-discounts-page-footer {
    justify-content: space-between;
    margin-block-start: auto;
  }
}

Notes

  • Heading levels. This site's page already has its <h1>, so the example passes level={2} to PageHeader. In your app, drop level (the title becomes the page's <h1>).
  • A list. The discounts are a <ul> whose items are Cards (render={<li />}), so a screen reader announces how many there are. Each status is a Badge's words, never its colour alone.
  • Usage is a Meter, not a ProgressBar: it measures how much of a fixed limit is used, and valueText reads it as "312 of 500".
  • Not built. Figma's Discount Card has no code component; each is a Card. The notice is not dismissible here: add dismiss={<AlertDismiss … />} to let a reader close it.

On this page