Dots

Card Design Picker

A grid of card design swatches on a radio group, with the chosen design's name next to the label.

Card design
"use client"

import * as React from "react"

The CardDesignPicker component lets people choose a plastic for their card from a grid of BankCard swatches. It is a React Aria radio group: one design is selected at a time, and its name shows next to the label. Use it when ordering a card or changing its design, with a full card above that previews the choice.

Installation

pnpm dlx shadcn@latest add @dots/card-design-picker

Usage

import { BankCard, findCardSkin } from "@/components/ui/bank-card"
import { CardDesignPicker } from "@/components/ui/card-design-picker"
const [design, setDesign] = React.useState("midnight")
 
<BankCard skin={findCardSkin(design)} mark={<IssuerLogo />} />
<CardDesignPicker value={design} onChange={setDesign} mark={<IssuerLogo />} />

The value is the skin id. Use findCardSkin to turn it back into a skin for the card.

Features

  • Built on React Aria RadioGroup: arrow keys, form name and disabled state work as on any radio group
  • Controlled with value and onChange, or uncontrolled with defaultValue
  • All 24 built-in designs by default, or any list of built-ins and your own skins
  • The chosen design's name next to the label
  • Your issuer mark on every swatch
  • Any number of columns

Fewer designs

Pass skins to offer only some designs, in your order. Set columns to fit the grid to its space.

Virtual card color
import { CardDesignPicker } from "@/components/ui/card-design-picker"

/** A neutral issuer mark for the demo. Pass your own logo in a product. */

Your own designs

Mix your own CardSkin objects with built-in ids in skins. Every id must be unique. Pass the same list to findCardSkin so the preview finds your skins too.

Card design
"use client"

import * as React from "react"

A skin's art is a function, and functions cannot pass from a server component to this client component. Define your skins in a client module, or in the same client component as the picker.

Hidden label

Pass label={null} with an aria-label when a heading above already says what the picker is for. The chosen name still shows.

<CardDesignPicker label={null} aria-label="Card design" defaultValue="onyx" />

Accessibility

CardDesignPicker is a React Aria RadioGroup of Radios, so it is one tab stop, the arrow keys move the selection, and it works with a pointer and touch.

  • The group is named by its label, "Card design" by default, or by aria-label when the label is hidden.
  • Each swatch is named by its skin's name, such as "Midnight". The swatch drawing itself is hidden from assistive tech.
  • The chosen name next to the label is hidden from assistive tech, because the checked radio already announces it.
  • The selected swatch is marked by a frame, not by colour alone.

API Reference

CardDesignPicker

Accepts React Aria RadioGroup props, such as value, defaultValue, onChange, isDisabled, isRequired, name and aria-label, except children and style.

PropTypeDefaultDescription
skinsReadonlyArray<CardSkinId | CardSkin>all built-insThe designs to offer, in order. Ids must be unique.
valuestring-The selected skin id, when controlled.
defaultValuestring-The initially selected skin id, when uncontrolled.
onChange(value: string) => void-Called with the skin id when the selection changes.
labelReact.ReactNode"Card design"Visible group label. Pass null with an aria-label to hide it.
markReact.ReactNode-Issuer mark drawn on every swatch.
columnsnumber6Swatches per row.
classNamestring-Additional classes to apply to the root.