Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

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, a SegmentedControl, and volumes, a Select, as one control group over the cards.

  • The plan cards

    plans, cheapest first, each with an optional ribbon, its price, a full-width action and its features.

  • Other plans

    more holds other plans under the cards, each a PlanComparison.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.

$19per month

Includes

  • Online shop
  • Subscriptions

Roaster

For a roastery that also sells to cafés.

$39per month

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.

$19per month

Includes

  • Online shop
  • Subscriptions
30 days free

Roaster

For a roastery that also sells to cafés.

$39per month

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.

$19per month

Includes

  • Online shop
  • Subscriptions
30 days free

Roaster

For a roastery that also sells to cafés.

$39per month

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.

$19per month

Includes

  • Online shop
  • Subscriptions
30 days free

Roaster

For a roastery that also sells to cafés.

$39per month

Everything in Starter, and

  • Wholesale accounts
  • Price lists
  • Roast planning
30 days free

Estate

For roasteries with more than one site.

$89per month

Everything in Roaster, and

  • Several sites
  • Shared green coffee stock

Other plans

Enterprise

Prices for large roasteries, set with you.

Props

PlanComparison

PropTypeDefaultDescription
plans

required

readonly PlanComparisonPlan[]The plans, cheapest first, priced for the period and volume chosen.
periodsreadonly SegmentedControlOption<string>[]Billing periods, as a SegmentedControl at the start of the control group.
periodstringThe period chosen. Pair with onPeriodChange to control it.
defaultPeriodstringThe period chosen first when uncontrolled. Defaults to the first.
onPeriodChange((period: string) => void)Called when another period is chosen.
periodLabelstring"Billing period"Names the periods' control. Defaults to "Billing period".
volumesreadonly SelectOption[]Volumes, as a Select after the periods in the same control group.
volumestringThe volume chosen. Pair with onVolumeChange to control it.
defaultVolumestringThe volume chosen first when uncontrolled. Defaults to the first.
onVolumeChange((volume: string) => void)Called when another volume is chosen.
volumeLabelstring"Volume"Names the volumes' control. Defaults to "Volume".
moreReactNodeOther plans under the cards: PlanComparison.Options, two columns from 640px.
moreTitlestring"Other plans"The heading over more. Defaults to "Other plans".
optionsLabelstring"Plan options"Names the control group holding the period and volume. Defaults to "Plan options".

It also takes Omit<ComponentPropsWithRef<"div">, "onVolumeChange">.

PlanComparison.Option

PropTypeDefaultDescription
name

required

stringThe plan's name, an h3 in title-md.
description

required

ReactNodeWhat it is, in body-sm.
actionReactNodeIts action, such as a small secondary Button.

It also takes Omit<ComponentPropsWithRef<"section">, "children">.

PlanComparisonPlan

One plan in a `PlanComparison`.

FieldTypeDefaultDescription
id

required

stringIdentifies the plan.
name

required

stringIts name, an h2 in title-md, such as "Plus".
description

required

stringWhat it's for, in a line or two.
price

required

stringIts price, written out for the period and volume chosen, such as "$14".
periodstringWhat the price covers, after it, such as "per month".
priceNoteReactNodeA line under the price, such as "Overages apply" with an InfoButton.
ribbonstringA short offer on a tab above the card, such as "30 days free".
action

required

ReactNodeIts one action, full width, such as a Button variant="primary".
featuresTitle

required

stringThe checklist's heading, such as "Everything in Standard, and".
features

required

readonly string[]What it includes or adds, one line each.

SegmentedControlOption

One option of a `SegmentedControl`.

FieldTypeDefaultDescription
value

required

ValueWhat value and onValueChange carry.
label

required

stringWhat the segment says.
disabledbooleanKeeps the segment from being chosen.
iconComponentType<IconComponentProps>An icon before the label, or in its place with iconOnly.

SelectOption

One choice in a `Select`.

FieldTypeDefaultDescription
value

required

ValueWhat choosing it sets the select's value to.
label

required

stringWhat the list and, once chosen, the trigger show.
disabledbooleanKeeps 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.

KeysWhat they do
Arrow keysChoose the billing period as they move, once Tab has reached it, or move through the volume's open list
Enter or SpaceOpens the volume's list, chooses the option in it, or presses a plan's action
EscapeCloses 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.

See it in the roastery demo's settings.

  • 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.