Staaarter

Billing Card Brand Selection

A card brand selection grid for payment setup and checkout flows. Renders each supported network (Visa, Mastercard, Amex, Discover, Apple Pay, Google Pay) as a selectable card with an icon and label, built as real buttons in an accessible radio group so selection works with keyboard and screen readers. Individual options can be disabled, for example a network you don't support yet.

By Staaarter Team
Billing
Updated March 19, 2026
Docs
Live preview

Docs

Installation

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

npx shadcn add https://staaarter.com/r/billing-card-brand-selection.json

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

Usage

The file also exports billing30Demo, 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 { Billing30, billing30Demo } from "@/components/blocks/billing/billing30";

export default function Page() {
  return <Billing30 {...billing30Demo} />;
}

Props

PropTypeDefaultDescription
headingstringnoneOptional heading above the grid.
descriptionstringnoneOptional supporting line under the heading.
brandsCardBrandItem[]nonePayment options rendered as selectable cards.
defaultBrandIdstringbrands[0].idWhich option is selected on first render.
classNamestringnoneExtra classes for the outer wrapper.

Types

type CardBrandOption =
  | "visa"
  | "mastercard"
  | "amex"
  | "discover"
  | "apple-pay"
  | "google-pay"
  | "other";

interface CardBrandItem {
  id: string;
  brand: CardBrandOption;
  label: string;
  helperText?: string;
  disabled?: boolean;
}

Behavior notes

  • Selection is local state seeded from defaultBrandId (falling back to the first item), clicking any enabled card updates it and moves the selected ring and icon color to that card.
  • Icons are looked up internally from a Record<CardBrandOption, LucideIcon>, never accepted as a prop, since passing a component reference from a server-rendered page into this client component would break at runtime.
  • The icons are generic Lucide glyphs (a card icon for the four card networks, a phone icon for Apple Pay, a wallet icon for Google Pay), not official brand marks. Swap BRAND_ICONS for real brand SVGs before shipping to production if you need pixel-accurate logos.
  • Cards with disabled: true render at reduced opacity, ignore clicks, and can't become the selected option even if they're listed as defaultBrandId.
  • The grid is role=radiogroup with each card as role=radio and aria-checked, so arrow-key and screen reader behavior matches a native radio group despite being built from plain buttons.

Frequently asked questions