Saved Card
- Stable
- WCAG 2.2 evidence
- RTL
A saved payment method as a choice: a mini card, a title and subtitle, and optional "Default" and status
badges, as one option of a RadioGroup. The whole row is one <label> around a native radio, so a click or tap anywhere
selects it (WCAG 2.5.8); the group is one Tab stop, arrows move and select, Space selects (2.1.1). The title is the accessible
name; the subtitle and the badges are the description (1.3.1, 4.1.2). Selected shows a 6 px ring in the circle and a thicker
brand border, not only a colour change (1.4.1); keyboard focus draws the ring around the row (2.4.7). An expired card is disabled and says why in a badge. A frozen card stays selectable: set
isDisabled when your flow cannot use it. Never put more than the last four digits of a number in the title or subtitle. Must be rendered inside a RadioGroup (use type="card" for the card spacing). Consumer duties: the group label, wording of title and subtitle, and what an
expired or frozen card offers instead.
import { SavedCard } from "@nexera-ui/react";- 8
- examples
- 18
- props
- 2
- live controls
- 1
- platform
- 14
- WCAG criteria
- 2
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { SavedCard } from "@nexera-ui/react";
<SavedCard />Examples 7
The same examples as Storybook, rendered live. Open Code to copy one.
In group
Cards of one group: a selected default card, an expired card (disabled, with its reason in a badge) and a frozen one.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function InGroup() {
return (
<RadioGroup type="card" label="Pay with" defaultValue="corporate" name="payment-method">
<SavedCard
value="corporate"
title="Corporate card ending 4417"
subtitle="Expires 09/29 · Ayesha Khan"
defaultBadge="Default"
/>
<SavedCard
value="travel"
title="Travel card ending 8804"
subtitle="Expires 02/28 · Ayesha Khan"
/>
<SavedCard
value="old"
title="Card ending 1209"
subtitle="Expired 03/26 · Ayesha Khan"
status="expired"
/>
<SavedCard
value="frozen"
title="Card ending 3355"
subtitle="Expires 11/27 · Ayesha Khan"
status="frozen"
/>
</RadioGroup>
);
}
Expired
Figma State=Expired: disabled, with the reason as a danger badge and a danger subtitle.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function Expired() {
return (
<RadioGroup type="card" aria-label="Pay with">
<SavedCard
value="corporate"
title="Corporate card ending 4417"
subtitle="Expires 09/29 · Ayesha Khan"
/>
</RadioGroup>
);
}
Frozen
Figma State=Frozen: selectable (set isDisabled if your flow cannot use a frozen card), with an info badge.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function Frozen() {
return (
<RadioGroup type="card" aria-label="Pay with">
<SavedCard
value="corporate"
title="Corporate card ending 4417"
subtitle="Expires 09/29 · Ayesha Khan"
/>
</RadioGroup>
);
}
Invalid
An invalid group: every row takes the danger stroke and the group shows the error with an icon.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function Invalid() {
return (
<RadioGroup type="card" label="Pay with" isInvalid errorMessage="Choose a card to pay with.">
<SavedCard value="corporate" title="Corporate card ending 4417" subtitle="Expires 09/29" />
<SavedCard value="travel" title="Travel card ending 8804" subtitle="Expires 02/28" />
</RadioGroup>
);
}
Long text
Title and subtitle wrap; the badges move under the text when the row is too narrow for both.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function LongText() {
return (
<RadioGroup type="card" aria-label="Pay with">
<SavedCard
value="corporate"
title="Corporate travel and expenses card ending 4417 for field staff"
subtitle="Expires 09/29 · Ayesha Khan-Malik · billing address in Lahore, Pakistan"
defaultBadge="Default"
status="frozen"
/>
</RadioGroup>
);
}
Right to left
Right-to-left (Arabic): the circle and thumbnail start at the right, the badges end at the left.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function RightToLeft() {
return (
<RadioGroup type="card" aria-label="الدفع بواسطة" defaultValue="corporate">
<SavedCard
value="corporate"
title="بطاقة الشركة تنتهي بـ 4417"
subtitle="تنتهي في 09/29 · عائشة خان"
defaultBadge="افتراضية"
/>
<SavedCard
value="old"
title="بطاقة تنتهي بـ 1209"
subtitle="انتهت في 03/26"
status="expired"
statusLabel="منتهية"
/>
</RadioGroup>
);
}
Narrow container
In a narrow container the row keeps its radio and thumbnail and the text and badges wrap.
import { RadioGroup, SavedCard } from "@nexera-ui/react";
export function NarrowContainer() {
return (
<RadioGroup type="card" aria-label="Pay with" defaultValue="corporate">
<SavedCard
value="corporate"
title="Corporate card ending 4417"
subtitle="Expires 09/29 · Ayesha Khan"
defaultBadge="Default"
/>
</RadioGroup>
);
}
Props 18
Press "Try it" on a card to load that prop into the playground.
18 props shown
title*NexeraVisibleContentVisible title. It is the accessible name, so write the masked number as "card ending 4417", never as bullets; never include more than the last four digits. Wraps; never truncated.
subtitleNexeraReactNodeSupporting line. Read as the description. Plain text: it sits inside the clickable row, so it must not contain links or buttons.
statusNexera"expired" | "frozen"Why the card cannot be used as it is. expired disables the row (it cannot be
chosen) and shows a danger badge; frozen shows an info badge and leaves the row selectable. The reason is text in the
badge, so it is never colour alone (WCAG 1.4.1, 3.3.1). Selected is not a status: it comes from the enclosing RadioGroup.
statusLabelNexeraReactNodeText of the status badge. Translate it for your locale.
defaultBadgeNexeraReactNodeMarks the default card: the text of the badge, for example
"Default". Shown when provided; read as part of the description. Named defaultBadge because default is a reserved word.
classNameNexerastringExtra classes for the root, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style of the root.
value*React AriastringThe value of the radio button, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#Value).
isDisabledReact AriabooleanWhether the radio button is disabled or not. Shows that a selection exists, but is not available in that circumstance.
autoFocusReact AriabooleanWhether the element should receive focus on render.
idReact AriastringThe element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
aria-describedbyReact AriastringIdentifies the element (or elements) that describes the object.
onPressReact Aria(e: PressEvent) => voidHandler that is called when the press is released over the target.
onPressStartReact Aria(e: PressEvent) => voidHandler that is called when a press interaction starts.
onPressEndReact Aria(e: PressEvent) => voidHandler that is called when a press interaction ends, either over the target or when the pointer leaves the target.
onPressChangeReact Aria(isPressed: boolean) => voidHandler that is called when the press state changes.
onPressUpReact Aria(e: PressEvent) => voidHandler that is called when a press is released over the target, regardless of whether it started on the target or not.
inputRefReact AriaRef<HTMLInputElement | null>A ref for the HTML input element.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
8 direct · 6 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.3Contrast (Minimum)Level AA · supporting test
- 1.4.4Resize TextLevel AA · supporting test
- 1.4.10ReflowLevel AA · supporting test
- 1.4.11Non-text ContrastLevel AA · supporting test
- 1.4.12Text SpacingLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 2.5.3Label in NameLevel A · tested directly
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 3.3.1Error IdentificationLevel A · tested directly
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-hovereddata-invaliddata-readonlydata-selected
<SavedCard className="data-disabled:opacity-90 shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.