Staaarter

Billing Plan Selector

A compact plan selector with a monthly/yearly billing toggle and selectable plan cards. Prices recalculate for the chosen interval and, when a seat count is provided, each card also shows the per-seat total. Built as real buttons in a radio group so selection works with keyboard and screen readers, not just pointer clicks.

By Staaarter Team
Billing
Updated June 3, 2026
Docs
Live preview

Docs

Installation

A real shadcn registry command, not a copy-paste stand-in: it fetches this block plus its badge, button dependencies and any missing npm packages, and installs them straight into your project.

npx shadcn add https://staaarter.com/r/billing-plan-selector.json

Prefer not to use the CLI? Copy the source from the Code toggle above into src/components/blocks/billing/billing9.tsx instead.

Usage

The file also exports billing9Demo, the exact props behind the preview above. Spread it to get a working section in one line, then replace it with your own data.

import { Billing9, billing9Demo } from "@/components/blocks/billing/billing9";

export default function Page() {
  return <Billing9 {...billing9Demo} />;
}

Props

PropTypeDefaultDescription
headingstringnoneOptional heading above the toggle.
descriptionstringnoneOptional supporting text below the heading.
plansPlanOption[]nonePlans rendered as selectable cards. Two plans render in a 2-column grid, three in a 3-column grid.
defaultPlanIdstringplans[0].idWhich plan is selected on first render.
defaultInterval"monthly" | "yearly""monthly"Which billing interval is active on first render.
seatCountnumbernoneWhen set, shows a per-seat total (price times seatCount) under each plan's price.
ctaLabelstring"Select plan"Label shown on unselected plan cards; selected cards show "Selected" instead.
classNamestringnoneExtra classes for the outer wrapper.

Types

interface PlanOption {
  id: string;
  name: string;
  monthlyPrice: number;
  yearlyPrice: number;
  description?: string;
  features: string[];
  badge?: string;
}

Behavior notes

  • Billing interval and selected plan are both local state; switching to yearly recalculates each card's displayed price as yearlyPrice / 12.
  • When seatCount is set, each card also shows the per-seat price multiplied by the seat count.
  • Plan cards are real buttons in a radiogroup, so selection works with keyboard navigation and screen readers, not just pointer clicks.
  • The bottom Continue button label reflects whichever plan is currently selected.

Frequently asked questions