Plan and billing page
A settings page inside the app shell, with a section nav beside the current plan, its usage on Meters, the payment method, the invoices table and billing emails.
From Figma 08 Overview Dashboard, Pattern 7 · Settings · Plan & billing (7275:16412). Uses
PageHeader, Split,
NavItem, Card,
Badge, Text,
NumberValue, Button,
Meter, Table,
Fieldset and Switch.
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}>
<PlanBillingPageExample />
</AppShell>;Example
Plan and billing
Figma 08's Pattern 7, Settings · Plan & billing: the content of the app shell's <main>. The page header, then the settings sections as a nav column beside the page: the current plan, its usage on Meters, the payment method, the invoices table and the billing emails. Only the Switches are client components.
Settings
Plan
Renews on Nov 14, 2026
Grow
$29.00 a month, billed monthly
Usage
What the Grow plan allows, this billing period.
Payment method
Invoices
Billing emails
import type { JSX } from 'react';
import { Badge } from '@nizivo/react/badge';
import { Button } from '@nizivo/react/button';
import { Card } from '@nizivo/react/card';
import { Fieldset } from '@nizivo/react/fieldset';
import { Meter } from '@nizivo/react/meter';
import { NavItem } from '@nizivo/react/nav-item';
import { NumberValue } from '@nizivo/react/number-value';
import { PageHeader } from '@nizivo/react/page-header';
import { Split } from '@nizivo/react/split';
import { Switch } from '@nizivo/react/switch';
import { Table } from '@nizivo/react/table';
import { Text } from '@nizivo/react/text';
/** Money in the plan's currency: Intl formats it, never string concatenation. */
const USD = { style: 'currency', currency: 'USD' } as const;
const SECTIONS = [
['general', 'General'],
['billing', 'Plan & billing'],
['payments', 'Payments'],
['shipping', 'Shipping'],
['staff', 'Staff'],
['notifications', 'Notifications'],
['domains', 'Domains'],
] as const;
const USAGE = [
['Products', 248, 500, '248 of 500'],
['Staff accounts', 3, 5, '3 of 5'],
['Orders this month', 1284, 2000, '1,284 of 2,000'],
['File storage', 3.1, 5, '3.1 GB of 5 GB'],
] as const;
const INVOICES = [
['INV-0042', 'Oct 14, 2026', '2026-10-14', 29],
['INV-0037', 'Sep 14, 2026', '2026-09-14', 29],
['INV-0031', 'Aug 14, 2026', '2026-08-14', 29],
['INV-0026', 'Jul 14, 2026', '2026-07-14', 19],
] as const;
export function PlanBillingPageExample(): JSX.Element {
return (
<div className="nzp-plan-billing-page">
<PageHeader level={2} title="Settings" />
<Split.Root aside="narrow">
<Split.Aside render={<nav aria-label="Settings sections" />}>
<ul className="nzp-plan-billing-page-nav">
{SECTIONS.map(([value, label]) => (
<li key={value}>
<NavItem href={`/settings/${value}`} current={value === 'billing'}>
{label}
</NavItem>
</li>
))}
</ul>
</Split.Aside>
<Split.Main className="nzp-plan-billing-page-main">
<Card.Root render={<section aria-labelledby="nzp-billing-plan" />}>
<Card.Header>
<Card.Title id="nzp-billing-plan">Plan</Card.Title>
<Card.Description>Renews on Nov 14, 2026</Card.Description>
<Card.Action>
<Badge tone="brand">Current</Badge>
</Card.Action>
</Card.Header>
<div className="nzp-plan-billing-page-plan">
<Text render={<p />} variant="heading-h3">
Grow
</Text>
<Text render={<p />} tone="secondary">
<Text render={<span />} variant="numeric-l" tone="primary">
<NumberValue value={29} format={USD} />
</Text>{' '}
a month, billed monthly
</Text>
</div>
<Card.Footer>
<Button variant="primary">Change plan</Button>
<Button variant="tertiary">Cancel plan</Button>
</Card.Footer>
</Card.Root>
<Card.Root render={<section aria-labelledby="nzp-billing-usage" />}>
<Card.Header>
<Card.Title id="nzp-billing-usage">Usage</Card.Title>
<Card.Description>What the Grow plan allows, this billing period.</Card.Description>
</Card.Header>
<div className="nzp-plan-billing-page-usage">
{USAGE.map(([label, value, max, text]) => (
<Meter
key={label}
label={label}
value={value}
max={max}
high={max * 0.8}
optimum={0}
valueText={text}
/>
))}
</div>
</Card.Root>
<Card.Root render={<section aria-labelledby="nzp-billing-payment" />}>
<Card.Header>
<Card.Title id="nzp-billing-payment">Payment method</Card.Title>
<Card.Action>
<Button size="sm">Add method</Button>
</Card.Action>
</Card.Header>
<div className="nzp-plan-billing-page-method">
<span className="nzp-plan-billing-page-card-mark" aria-hidden="true" />
<span className="nzp-plan-billing-page-card-text">
<Text render={<span />} variant="label-m-strong">
Visa ending in 4242
</Text>
<Text render={<span />} tone="secondary">
Expires 08/2028
</Text>
</span>
<Badge tone="neutral">Default</Badge>
<Button size="sm" variant="tertiary" aria-label="Replace Visa ending in 4242">
Replace
</Button>
</div>
</Card.Root>
<Card.Root render={<section aria-labelledby="nzp-billing-invoices" />}>
<Card.Header>
<Card.Title id="nzp-billing-invoices">Invoices</Card.Title>
</Card.Header>
<Table.Container>
<Table.Root aria-labelledby="nzp-billing-invoices">
<Table.Header>
<Table.Row>
<Table.Head>Invoice</Table.Head>
<Table.Head>Date</Table.Head>
<Table.Head numeric>Amount</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>File</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{INVOICES.map(([id, date, iso, amount]) => (
<Table.Row key={id}>
<Table.Head scope="row">
<Text render={<span />} variant="mono-m">
{id}
</Text>
</Table.Head>
<Table.Cell>
<time dateTime={iso}>{date}</time>
</Table.Cell>
<Table.Cell numeric>
<NumberValue value={amount} format={USD} />
</Table.Cell>
<Table.Cell>
<Badge tone="success">Paid</Badge>
</Table.Cell>
<Table.Cell>
<a
className="nzp-plan-billing-page-link"
href={`/settings/billing/invoices/${id}.pdf`}
aria-label={`Download invoice ${id}`}
>
Download
</a>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Table.Container>
</Card.Root>
<Card.Root render={<section aria-labelledby="nzp-billing-emails" />}>
<Card.Header>
<Card.Title id="nzp-billing-emails">Billing emails</Card.Title>
</Card.Header>
<Fieldset legend="Send to the billing contact">
<Switch name="billing-emails" value="invoices" defaultChecked>
Invoices
</Switch>
<Switch name="billing-emails" value="failed" defaultChecked>
Failed payments
</Switch>
<Switch name="billing-emails" value="usage">
Usage reports
</Switch>
</Fieldset>
</Card.Root>
</Split.Main>
</Split.Root>
</div>
);
}
Stylesheet
@layer nz.tokens, nz.themes, nz.reset, nz.base, nz.components, nz.patterns, nz.utilities;
/* Figma 08 Pattern 7 (7275:16412): the header, then Split's narrow nav column (Figma's 232 px
settings menu, its items 4 apart) beside the page's cards, 24 apart. The usage meters sit two to a
row once their card is 30rem wide (a container query on the main column). */
@layer nz.patterns {
.nzp-plan-billing-page {
display: flex;
flex-direction: column;
gap: var(--nz-space-6);
min-inline-size: 0;
}
.nzp-plan-billing-page-nav {
display: flex;
flex-direction: column;
gap: var(--nz-space-1);
margin: 0;
padding: 0;
list-style: none;
}
.nzp-plan-billing-page-main {
display: flex;
flex-direction: column;
gap: var(--nz-space-6);
container-type: inline-size;
}
.nzp-plan-billing-page-plan {
display: flex;
flex-direction: column;
gap: var(--nz-space-1);
}
.nzp-plan-billing-page-plan > p {
margin: 0;
}
.nzp-plan-billing-page-usage {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--nz-space-5) var(--nz-space-6);
}
@container (width >= 30rem) {
.nzp-plan-billing-page-usage {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.nzp-plan-billing-page-method {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--nz-space-3);
}
.nzp-plan-billing-page-card-mark {
flex: none;
inline-size: 3rem;
block-size: 2rem;
border-radius: var(--nz-radius-sm);
background: var(--nz-bg-muted);
}
/* Figma's "View all" text link: Label/M in text/brand */
.nzp-plan-billing-page-link {
color: var(--nz-text-brand);
font: var(--nz-font-label-m);
letter-spacing: var(--nz-tracking-label-m);
}
.nzp-plan-billing-page-card-text {
display: flex;
flex: 1 1 10rem;
flex-direction: column;
gap: var(--nz-space-0-5);
}
}
Notes
- Heading levels. This site's page already has its
<h1>, so the example passeslevel={2}to PageHeader. In your app, droplevel(the title becomes the page's<h1>). - Section nav. A
<nav>of NavItems in Split's narrow column, the current sectionaria-current="page". Split stacks the columns when the row is too narrow for both, as it is in this site's preview; Figma has no small-screen design for it. - Usage is a Meter, with
highat 80 % of each limit andoptimumat zero, so a limit that is nearly used shows the warning colour, andvalueTextreads "248 of 500". - Not built. Figma shows the three plan tiers as Plan Cards and the payment methods as selectable Payment Method cards; neither has a component, so the example shows the current plan and one method. Figma's "Billing emails" title is H3 while the other card titles are H4; here they all are Card titles.
Analytics page
A report inside the app shell, with a range control, key figures with sparklines, a sales trend, goals, sales by channel, orders per day and the top products.
Onboarding page
A new store's home inside the app shell, with a setup checklist and its progress, an empty state for orders and next-step cards. It ships no JavaScript.