Skip to content

Payment Method Card

One payment method: a generic card mark, the last four digits, the expiry with an expires soon notice, a default badge, and the actions Update, Remove (confirmed) and Make default. With no method it offers Add a payment method. It never holds a full card number; 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)

Credit ending in 4242

Expires 08/2029 · Lena Fischer

Default

Expiring

Full screen (opens in a new tab)

Credit ending in 1881

Expires 11/2026

Expires soon, 11/2026. Update it to avoid a failed payment.

Debit ending in 0005

Expires 08/2026

Expired on 08/2026. Update it to keep paying.

Empty and states

Full screen (opens in a new tab)

No payment method yet

Add a card so your plan can renew without a gap.

Couldn't load your payment method

Permissions

Full screen (opens in a new tab)

Credit ending in 4242

Expires 08/2029

A workspace needs one payment method. Add another first.

ClassNames

Full screen (opens in a new tab)

Corporate ending in 7777

Expires 03/2030

Default

Installation

npx rdloom add payment-method-card

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

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

Works, but without upgrade tracking.

Usage

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

<div className="flex w-full justify-center">
  <div className="w-full max-w-md">
    <PaymentMethodCard
      method={method}
      onUpdate={() => wait(400)}
      onMakeDefault={() => wait(400)}
      onRemove={async () => {
        await wait(500);
        setMethod(undefined);
      }}
      onAdd={async () => {
        await wait(400);
        setMethod(card);
      }}
    />
  </div>
</div>

API Reference

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

PropTypeDefault
method

The method to show. brand is a short word for the chip (e.g. "Credit"); only the last four digits are ever shown. Leave out for the empty state.

{ id: string; brand?: string; last4: string; expMonth: number; expYear: number; holder?: string; isDefault?: boolean }none
expiresSoonDays

How many days before the end of the expiry month the card counts as expiring soon.

number60
now

The current date for the expiry check. Defaults to today; pass one for a fixed result in tests and server rendering.

Datenone
state

The data state: loading shows a skeleton, empty (or no method) offers Add a payment method, error shows a message with Try again.

DataStatenone
onRetry

Adds a Try again button to the error state.

() => voidnone
onAdd

Starts adding a method from the empty state; yours, async.

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

Starts updating the method (usually opens your payment form); yours, async.

(method: { id: string; brand?: string; last4: string; expMonth: number; expYear: number; holder?: string; isDefault?: boolean }) => void | Promise<unknown>none
onRemove

Removes the method after the person confirmed; yours, async.

(method: { id: string; brand?: string; last4: string; expMonth: number; expYear: number; holder?: string; isDefault?: boolean }) => void | Promise<unknown>none
onMakeDefault

Makes this the default method; yours, async. The button is not shown on the default method.

(method: { id: string; brand?: string; last4: string; expMonth: number; expYear: number; holder?: string; isDefault?: boolean }) => void | Promise<unknown>none
permissions

What the app allows: add, update, remove and makeDefault. 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<"add" | "update" | "remove" | "makeDefault">none
classNames

Extra class names for single parts, so you can restyle one part without editing the file. Keys: root, brand, details, number, expiry, badge, notice, actions, updateButton, removeButton, defaultButton, addButton, status.

Partial<Record<"root" | "brand" | "details" | "number" | "expiry" | "badge" | "notice" | "actions" | "updateButton" | "removeButton" | "defaultButton" | "addButton" | "status", 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 Update, Make default and Remove
  • Remove opens a confirmation: Escape cancels

Screen readers announce

  • "Payment method, ending in 4242, group"
  • "Default"
  • "Expires soon, 08/2026"
  • "Payment method removed"

What your code must do

  • The card is a group named "Payment method" followed by the last four digits
  • The brand mark is decorative; the digits are said as text, e.g. "ending in 4242"
  • Expires soon and Expired are words with an icon, never only a color
  • Default is a badge with text
  • Removing needs a confirmation and is announced when done
  • After a removal focus moves to the card so it is not lost
  • A disabled action stays focusable and the reason is its description

Block contract

Data
One payment method (brand word, last four digits, expiry, default flag) and async callbacks to add, update, remove and make default.
Data states
loading, empty, error, ready
Permissions
add, update, remove, makeDefault
Events
onAdd, onUpdate, onRemove, onMakeDefault, onRetry
You can replace
classNames for each part; expiresSoonDays; brand word per method; now for a fixed date

Guidelines

Use it when

  • Showing the card or method a workspace pays with on a billing page
  • A list of methods: render one PaymentMethodCard each

Avoid it when

  • Collecting card details: use your payment provider's own fields, never your own inputs

Don't

  • Treating the permissions prop as protection: UI permission is not security, so the server must check again
  • Passing a full card number: only the last four digits belong in the browser
  • Showing a card network logo you do not have the right to: the brand chip is plain text on purpose

Design tokens

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

  • --rd-color-surface-default
  • --rd-color-surface-subtle
  • --rd-color-border-default
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-feedback-warning
  • --rd-color-feedback-danger
  • --rd-elevation-raised
  • --rd-radius-overlay
  • --rd-radius-control