Plans
Plans side by side, with a billing period and a volume over cards whose rows line up.
The plans pattern compares an app's plans side by side: a billing period and a volume as one control group over plan cards, each with an optional ribbon, its price, a full-width action and what it adds to the plan before. Use PlanComparison on a pricing or billing page.
The parts
The plan options
periods, aSegmentedControl, andvolumes, aSelect, as one control group over the cards.The plan cards
plans, cheapest first, each with an optionalribbon, itsprice, a full-widthactionand itsfeatures.Other plans
moreholds other plans under the cards, each aPlanComparison.Option, two to a row from 640px.
The cards share their rows through subgrid, so prices, actions and lists line up across plans of any length. They sit side by side from 900px of room and stack under that.
PlanComparison
When to use it
Use it for
- Choosing or changing a plan, on a plans page under settings
- Two to four plans that differ by price and by what each adds
Not for
- One plan's details: use DetailPage
- Comparing many features across many plans: use a DataTable
Examples
Choose a plan
Choosing a plan from its card, the plan in use marked on its own button.
Starter
For a roastery selling to its own customers.
Includes
- Online shop
- Subscriptions
Roaster
For a roastery that also sells to cafés.
Everything in Starter, and
- Wholesale accounts
- Price lists
On the starter plan
Plans by period
Plans whose prices follow the billing period, kept in state and chosen in the control group.
Starter
For a roastery selling to its own customers.
Includes
- Online shop
- Subscriptions
Roaster
For a roastery that also sells to cafés.
Everything in Starter, and
- Wholesale accounts
- Price lists
No plan chosen yet
Plans page
A plans page under settings: its header, the period and volume over the plans, the plan in use marked, and other plans under them.
Plans and billing
Pick what the roastery pays for. You can change it any time.
Starter
For a roastery selling to its own customers.
Includes
- Online shop
- Subscriptions
Roaster
For a roastery that also sells to cafés.
Everything in Starter, and
- Wholesale accounts
- Price lists
- Roast planning
Other plans
Enterprise
Prices for roasteries with more than one site, set with you.
On the Starter plan
Variants and states
Three plans
The cards share their rows, so prices, actions and checklists line up.
Starter
For a roastery selling to its own customers.
Includes
- Online shop
- Subscriptions
Roaster
For a roastery that also sells to cafés.
Everything in Starter, and
- Wholesale accounts
- Price lists
- Roast planning
Estate
For roasteries with more than one site.
Everything in Roaster, and
- Several sites
- Shared green coffee stock
Other plans
Enterprise
Prices for large roasteries, set with you.
Props
PlanComparison
| Prop | Type | Default | Description |
|---|---|---|---|
plansrequired | readonly PlanComparisonPlan[] | The plans, cheapest first, priced for the period and volume chosen. | |
periods | readonly SegmentedControlOption<string>[] | Billing periods, as a SegmentedControl at the start of the control group. | |
period | string | The period chosen. Pair with onPeriodChange to control it. | |
defaultPeriod | string | The period chosen first when uncontrolled. Defaults to the first. | |
onPeriodChange | ((period: string) => void) | Called when another period is chosen. | |
periodLabel | string | "Billing period" | Names the periods' control. Defaults to "Billing period". |
volumes | readonly SelectOption[] | Volumes, as a Select after the periods in the same control group. | |
volume | string | The volume chosen. Pair with onVolumeChange to control it. | |
defaultVolume | string | The volume chosen first when uncontrolled. Defaults to the first. | |
onVolumeChange | ((volume: string) => void) | Called when another volume is chosen. | |
volumeLabel | string | "Volume" | Names the volumes' control. Defaults to "Volume". |
more | ReactNode | Other plans under the cards: PlanComparison.Options, two columns from 640px. | |
moreTitle | string | "Other plans" | The heading over more. Defaults to "Other plans". |
optionsLabel | string | "Plan options" | Names the control group holding the period and volume. Defaults to "Plan options". |
It also takes Omit<ComponentPropsWithRef<"div">, "onVolumeChange">.
PlanComparison.Option
| Prop | Type | Default | Description |
|---|---|---|---|
namerequired | string | The plan's name, an h3 in title-md. | |
descriptionrequired | ReactNode | What it is, in body-sm. | |
action | ReactNode | Its action, such as a small secondary Button. |
It also takes Omit<ComponentPropsWithRef<"section">, "children">.
PlanComparisonPlan
One plan in a `PlanComparison`.
| Field | Type | Default | Description |
|---|---|---|---|
idrequired | string | Identifies the plan. | |
namerequired | string | Its name, an h2 in title-md, such as "Plus". | |
descriptionrequired | string | What it's for, in a line or two. | |
pricerequired | string | Its price, written out for the period and volume chosen, such as "$14". | |
period | string | What the price covers, after it, such as "per month". | |
priceNote | ReactNode | A line under the price, such as "Overages apply" with an InfoButton. | |
ribbon | string | A short offer on a tab above the card, such as "30 days free". | |
actionrequired | ReactNode | Its one action, full width, such as a Button variant="primary". | |
featuresTitlerequired | string | The checklist's heading, such as "Everything in Standard, and". | |
featuresrequired | readonly string[] | What it includes or adds, one line each. |
SegmentedControlOption
One option of a `SegmentedControl`.
| Field | Type | Default | Description |
|---|---|---|---|
valuerequired | Value | What value and onValueChange carry. | |
labelrequired | string | What the segment says. | |
disabled | boolean | Keeps the segment from being chosen. | |
icon | ComponentType<IconComponentProps> | An icon before the label, or in its place with iconOnly. |
SelectOption
One choice in a `Select`.
| Field | Type | Default | Description |
|---|---|---|---|
valuerequired | Value | What choosing it sets the select's value to. | |
labelrequired | string | What the list and, once chosen, the trigger show. | |
disabled | boolean | Keeps it from being chosen; arrow keys skip it. |
Accessibility
The control group is a group named Plan options: the periods a radio group named by periodLabel, the volume a select named by volumeLabel. Each plan card is a region named by its h2, its checklist a list under an h3; the ribbon is text above the card. Other plans are a region named by its h2, each option an h3, so the comparison can follow a page's h1.
| Keys | What they do |
|---|---|
| Arrow keys | Choose the billing period as they move, once Tab has reached it, or move through the volume's open list |
| Enter or Space | Opens the volume's list, chooses the option in it, or presses a plan's action |
| Escape | Closes the volume's list and puts focus back on it |
Motion
Its selection indicator slides to the chosen option on `spring.snappy`, inside one track.
With reduced motion: The billing period's pill moves to the chosen period at once.
Layout
It takes its container's width: the period and volume sit together in one outlined group at the start, and the plans sit side by side from 900px of its own width, sharing their rows so prices, actions and checklists line up, and stack under that. Each plan's action fills its card's width, a ribbon is a 28px tab on its card's top edge, the text wraps, and other plans go two to a row from 640px.
Related
See it in the roastery demo's settings.
Related
- SegmentedControl
One choice out of a few, shown side by side in a track, with the selection sliding between them.
- Select
A trigger that shows the chosen value and opens a list to pick another, with an optional footer for custom values.