Skip to main content

PaymentMethodCard

Implemented

Saved-payment-method display card: brand label + last4 + expiry, with an optional default badge, Set-default action and Remove action. Compact wallet row — supersedes the deprecated paymentmethod (selection semantics live there, not here).

Categorymolecules
Sourcepatterns/molecules/paymentmethodcard.html
AI HintWallet rows for saved cards (account wallet in CardGrid). For a payment-method SELECTOR do not add radio semantics here — that is a separate molecule. Strings ('Default', 'Set default', 'Remove') are EN hardcoded; i18n keys land at adoption time.

Saved-payment-method display card: uppercase brand label + masked last4 + expiry, with an optional "Default" badge and optional Set-default / Remove actions. Exposes the card as a labelled group (role="group", "Brand ending in 1234") so screen readers announce the card before its actions.

PaymentMethodCard

Input molecule (Family 5 — Tier 1)

A compact wallet row for the account/payment surface. It is a DISPLAY card: it shows one saved payment method and offers per-card management actions. It holds no selection state — no selected prop, no onSelect, no radio semantics.

VisaDefault

•••• 4242

Expires 12/28

Mastercard

•••• 5454

Expires 06/27

American Express

•••• 0005

Expires 03/29

This component supports light and dark mode.

Usage

Render one card per saved payment method, usually inside a CardGrid (the account-page template composes exactly that). Wire both onSetDefault and onRemove — a card without callbacks renders as a pure display row with no actions.

import { PaymentMethodCard, CardGrid } from '@epasslive/design-system'

<CardGrid
  items={paymentMethods}
  keyExtractor={(pm) => pm.id}
  columns={2}
  renderItem={(pm) => (
    <PaymentMethodCard
      brand={pm.brand}
      last4={pm.last4}
      expiryMonth={pm.expiryMonth}
      expiryYear={pm.expiryYear}
      isDefault={pm.isDefault}
      onSetDefault={() => setDefault(pm.id)}
      onRemove={() => remove(pm.id)}
    />
  )}
  emptyMessage="No payment methods on file."
/>

Display card vs. selection card

PaymentMethodCard is a display card, not a selection control. It has no selected state, no onSelect, and no radio/aria-pressed semantics — it shows one saved method and manages it. When checkout needs to let a shopper choose between saved methods, that is a selection molecule’s job (the deprecated paymentmethod pattern carried the radio semantics; a replacement selection row is a separate need). Do not bolt selection onto this card by intercepting card clicks — keep the two concerns on separate components.

Accessibility

The root exposes role="group" with the accessible name "${brand} ending in ${last4}", so the card announces itself before its actions. Actions are native <button type="button"> elements (keyboard-operable by construction) with min-h-[var(--ep-tap-min)] tap targets (WCAG 2.5.5) and visible focus-visible rings — teal for Set-default, error-red for Remove. The Remove button carries the specific label Remove ${brand} ending in ${last4} so screen-reader users know which card will be removed. The brand is text (no logo <img>), so no alt-text is involved. After a successful Set-default, the "Default" badge appears via parent re-render; announce the change at the consumer level (e.g. a polite aria-live region) — the molecule itself does not own a live region.

Dark mode

Every color is token-driven (bg-ep-surface-card, border-ep-border, text-ep-text-heading/-muted/-teal, text-ep-semantic-error), and each token has a light and dark definition, so the card flips automatically under data-theme="dark". The Default badge tint stays AA in both themes (5.70:1 light / 4.75:1 dark).

Responsive

The header row (brand + badge … actions) is flex flex-wrap … gap-2: on narrow widths (a 320px viewport or a 2-column CardGrid) the action cluster wraps below a long brand such as "AMERICAN EXPRESS" instead of colliding with it.

Props

Prop
Type
Default
Description
brand*
string
—

Card brand label (e.g. "Visa", "Mastercard", "American Express"). Rendered with the uppercase transform — pass the proper noun as-is; brands are data, not localized copy.

last4*
string
—

Last four digits of the card number. Rendered masked as "•••• {last4}".

expiryMonth*
number
—

Expiry month, 1–12. Zero-padded when rendered (3 renders as "03").

expiryYear*
number
—

Full expiry year (e.g. 2028). Rendered as its last two digits ("28").

isDefault
boolean
—

When true, renders the "Default" badge (teal tint, AA in both themes) and suppresses the Set-default action — a default card cannot be set default again.

onSetDefault
() => void
—

Set-default action handler. The "Set default" button renders ONLY when this is provided AND isDefault is false.

onRemove
() => void
—

Remove action handler. The button renders ONLY when this is provided, and carries the specific accessible name "Remove {brand} ending in {last4}".

className
string
—

Additional classes appended to the root group element.

Props

Preview

PaymentMethodCard

Prop
Type
Required
Default
Description
brand
string
Required
—

Card brand name, rendered uppercase (e.g. 'Visa').

last4
string
Required
—

Last four digits, rendered as '•••• {last4}'.

expiryMonth
number
Required
—

Expiry month 1-12, zero-padded in the readout.

expiryYear
number
Required
—

Expiry year — the last two digits render in the readout.

isDefault
boolean
Optional
false

Renders the Default badge; when false and onSetDefault is provided, shows the Set-default action instead.

onRemove
() => void
Optional
—

Renders the Remove action; its aria-label names the card ('Remove Visa ending in 4242').

onSetDefault
() => void
Optional
—

Renders the Set-default action when the card is not default.

className
string
Optional
—

Additional CSS classes on the card.

Quick use:<PaymentMethodCard />

Accessibility

ARIA rolegroup

ARIA

1 rule

WCAG 4.1.2

root is role='group' named `{brand} ending in {last4}`; actions are native <button>s — Remove carries an aria-label naming the card

Type

1 rule

NASA Rule 5

display card only — no selected/onSelect/radio semantics (selection belongs to the deprecated paymentmethod or a future selection molecule)

Usage

Import

tsxSnippet
import { PaymentMethodCard } from "@epasslive/design-system"

Example 1

tsxSnippet
<PaymentMethodCard brand="Visa" last4="4242" expiryMonth={12} expiryYear={28} isDefault onRemove={removeCard} />

Example 2

tsxSnippet
<PaymentMethodCard brand="Mastercard" last4="8210" expiryMonth={4} expiryYear={27} onSetDefault={makeDefault} onRemove={removeCard} />

Anti-patterns

— Do not use for payment selection rows — this card displays and actions, it does not select

— Do not reach for the deprecated PaymentMethod docs pair — its import no longer compiles

Design System v4.1.0 | ePassLive