Skip to content

Plan Card

The current subscription plan: name, price and interval, status with the right text, renewal or end date, feature list, a usage hint, and the actions Change plan and Cancel subscription (confirmed, with the end date). A past due plan shows an alert. PlanPicker, exported beside it, lets people choose another plan. Your callbacks do the work. UI permission is not security: the server must check again.

Blockv0.1.0experimentalWCAG 2.2 AAView spec

A ready-made piece, built from the library's own parts.

A block puts several components together into something you would otherwise assemble by hand. It never fetches data: you give it the data, or answer its callbacks. It is copied into your project like any component, with the parts it uses, so you can change anything.

Basic

Full screen (opens in a new tab)

Team plan

Active

$49 per month

Renews on March 3, 2027

  • Up to 10 seats
  • 5 GB of storage
  • Email support

8 of 10 seats used

Statuses

Full screen (opens in a new tab)

Team plan

Active

$49 per month

Renews on March 3, 2027

  • Up to 10 seats
  • 5 GB of storage

Team plan

Trial

$49 per month

Trial ends on February 17, 2027

  • Up to 10 seats
  • 5 GB of storage

Team plan

Past due

$49 per month

Payment failed. Renewal was due on March 3, 2027

  • Up to 10 seats
  • 5 GB of storage

Team plan

Canceled

$49 per month

Ends on March 3, 2027

  • Up to 10 seats
  • 5 GB of storage

States

Full screen (opens in a new tab)

You are not on a plan yet

Choose a plan to get started.

Couldn't load your plan

Your plan is not affected. Try again in a moment.

Permissions

Full screen (opens in a new tab)

Team plan

Active

$49 per month

Renews on March 3, 2027

  • Up to 10 seats
  • 5 GB of storage
Only the billing owner can change the plan.Only the billing owner can cancel.

ClassNames

Full screen (opens in a new tab)

Team plan

Active

$49 per month

Renews on March 3, 2027

  • Up to 10 seats
  • Email support

Plan picker

Full screen (opens in a new tab)
Save up to 17% with yearly billing

This is your current plan.

Billing page

Full screen (opens in a new tab)

Billing

Your plan, what you use and how you pay.

Team plan

Active

$49 per month

Renews on March 3, 2027

  • Up to 10 seats
  • 5 GB of storage
  • Email support

Usage

Resets on 1 March.
  • Seats8 of 10 seats

    Near the limit

  • Storage4.2 of 5 GB

    Near the limit

  • API calls6,200 of 10,000

Credit ending in 4242

Expires 08/2029 · Lena Fischer

Default

Invoices

6 results

InvoiceDateAmountStatusDownload
INV-2041Feb 3, 2027$49Open
INV-2012Jan 3, 2027$49Paid
INV-1983Dec 3, 2026$49Paid
INV-1954Nov 3, 2026$49Paid
INV-1925Oct 3, 2026$19Refunded

Showing 1 to 5 of 6

Installation

npx rdloom add plan-card

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/plan-card.json

Works, but without upgrade tracking.

Usage

import { PlanCard } from "@rdloom/react";

<div className="flex w-full justify-center">
  <div className="w-full max-w-md">
    <PlanCard
      plan={{
        name: "Team plan",
        price: 49,
        interval: "month",
        features: ["Up to 10 seats", "5 GB of storage", "Email support"],
      }}
      periodEnd="2027-03-03"
      usageHint="8 of 10 seats used"
      onChangePlan={() => wait(400)}
      onCancel={() => wait(600)}
    />
  </div>
</div>

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
plan

The plan: its name, the price per interval (in whole units, e.g. 49 for $49), an ISO currency code (default USD), an optional description and the features it includes.

{ name: string; price: number; interval: "month" | "year"; currency?: string; description?: string; features?: string[] }none
status

Where the subscription stands. Each status has its own badge text and date line: active "Renews on", trialing "Trial ends on", past due "Payment failed", canceled "Ends on".

"active" | "trialing" | "past_due" | "canceled""active"
periodEnd

When the current period ends: the renewal date, the trial end or the end of access. An ISO date string or a Date.

Date | stringnone
usageHint

One line about use, e.g. "8 of 10 seats used". For a meter use UsageMeter beside the card.

stringnone
locale

The locale for the price and the dates.

string"en-US"
state

The data state: loading shows a skeleton, empty says there is no plan yet and offers Choose a plan, error shows a message with Try again.

DataStatenone
onRetry

Adds a Try again button to the error state.

() => voidnone
onChangePlan

Starts changing the plan; yours, async. Usually opens a PlanPicker.

() => void | Promise<unknown>none
onCancel

Cancels the subscription after the person confirmed in the dialog, which states the end date; yours, async.

() => void | Promise<unknown>none
onUpdatePayment

Adds an Update payment method button to the past due alert.

() => void | Promise<unknown>none
permissions

What the app allows: changePlan and cancel. Hidden renders nothing; disabled keeps the control reachable (aria-disabled) with the reason shown and read, and nothing runs. This only changes what people see: the server must check again.

Permissions<"changePlan" | "cancel">none
classNames

Extra class names for single parts, so you can restyle one part without editing the file. Keys: root, header, name, status, price, renewal, features, feature, usage, alert, actions, changeButton, cancelButton, updateButton.

Partial<Record<"root" | "header" | "name" | "status" | "price" | "renewal" | "features" | "feature" | "usage" | "alert" | "actions" | "changeButton" | "cancelButton" | "updateButton", string>>none

Accessibility

Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves through Change plan and Cancel subscription
  • Cancel subscription opens a confirmation: Escape cancels, Enter confirms

Screen readers announce

  • "Team plan, group"
  • "Past due"
  • "Renews on 3 March 2027"
  • "Cancel subscription, button"; the dialog says "You keep access until 3 March 2027"

What your code must do

  • The card is a group named by the plan name
  • The status is a badge with words (Active, Trial, Past due, Canceled), never only a color
  • A past due plan shows an alert that says what to do
  • The features are a list; each tick is decorative
  • The cancel confirmation states the date access ends
  • A disabled action stays focusable and the reason is its description

Block contract

Data
The current plan, its status and period end, and async callbacks for changing, cancelling and fixing payment.
Data states
loading, empty, error, ready
Permissions
changePlan, cancel
Events
onChangePlan, onCancel, onUpdatePayment, onRetry
You can replace
classNames for each part; status and periodEnd; usageHint and locale; PlanPicker for choosing a plan

Guidelines

Use it when

  • Showing the plan a workspace is on with its renewal and the way to change or end it
  • A billing page next to UsageMeter and PaymentMethodCard

Avoid it when

  • Comparing plans before choosing: use PlanPicker
  • Listing invoices: use DataTable

Don't

  • Treating the permissions prop as protection: UI permission is not security, so the server must check again
  • Cancelling from inside the component: onCancel is yours, and so is the end date it shows
  • Showing the status by color alone: the badge text carries it

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-border-default
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-action-primary
  • --rd-color-feedback-success
  • --rd-color-feedback-warning
  • --rd-color-feedback-danger
  • --rd-elevation-raised
  • --rd-radius-overlay