Deck
Browse the catalog

Build / Organisms

PlanCard

  • organism
  • play-fn

One plan in a row of them: what it costs, what it includes, and the one button that changes anything.

The plan you are on is marked rather than removed, and its action says what it actually does — you do not "choose" the plan you already have. That mark is a badge and a border, never the border alone: which plan you are on is the single most important thing on the screen and it does not get to depend on seeing a colour.

Features are a list of lines with no nesting on purpose. A plan comparison that grows sub-bullets has stopped comparing and started explaining, and the explaining belongs on a pricing page rather than in a settings panel.

import { PlanCard } from '@meetcard/deck'

Example

<PlanCard name="Pro" description="For professionals who network seriously." price="$8" period="/mo" features={['Unlimited cards', 'CRM integrations']} action={<Button size="sm">Choose Pro</Button>} />

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

A plan you could move to.

Default
Source
const Default = () => <div style={{ maxWidth: 320 }}>
    <PlanCard
        name="Pro"
        description="For professionals who network seriously."
        price="$8"
        period="/mo"
        features={[
          'Unlimited cards',
          'Unlimited connections',
          'CRM integrations',
          'Custom domain',
          'Advanced analytics',
        ]}
        action={<Button size="sm">Choose Pro</Button>} />
</div>;

The plan you are on. Marked with a badge as well as a border, and its action says what it does — you do not "choose" the plan you already have.

Current
Source
const Current = () => <div style={{ maxWidth: 320 }}>
    <PlanCard
        name="Team"
        description="For teams networking at events together."
        price="$9"
        period="/mo per seat"
        features={[
          'Everything in Pro',
          '500 connections per seat',
          '10 events per month',
          'Team analytics',
          'Shared company branding',
        ]}
        action={(<Button size="sm" variant="secondary">Manage plan
                </Button>)}
        note="Starting at $9/mo per seat, 3-seat minimum."
        current />
</div>;

The free one. A price of nothing is still a price and still lines up.

Free
Source
const Free = () => <div style={{ maxWidth: 320 }}>
    <PlanCard
        name="Solo"
        description="For getting started with your digital business card."
        price="$0"
        period="forever"
        features={[
          '1 card',
          '100 connections',
          'Unlimited card exchanges',
          'Basic analytics',
        ]}
        action={(<Button size="sm" variant="secondary">Choose Solo
                </Button>)} />
</div>;

Props

PropTypeDefaultDescription
featuresrequiredReactNode[]—What you get. One line each; no sub-lists.
namerequiredstring——
pricerequiredstring—The figure alone — "$8", "$0". Kept apart from period so a column of plans lines its prices up on the same baseline whatever follows them.
actionReactNode—The action — "Choose Pro", "Manage plan".
currentbooleanfalseMarks the plan you are already on.
descriptionReactNode——
level2 | 3 | 43Heading level for the plan's name. Default 3.
noteReactNode—The small print under the price — a seat minimum, a trial length.
periodReactNode—What the figure is per: "/mo", "/mo per seat", "forever".

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.