Radio Card
Radio Card is a bordered, card-sized option for choosing exactly one of a few choices. Use it when each option needs a description or visual.
Beta until the focus indicator (C5) is resolved and design confirms the code-only focused card and the disabled text colours (C49, FC-20), and the code-only RadioGroup wrapper. Everything else on this page is implemented and tested.
Preview
Change the controls to try every supported combination. The cards are a controlled RadioGroup: click anywhere on a card to select it.
<RadioGroup label="Learning path" value={path} onValueChange={setPath}>
<RadioCard
value="guided"
label="Guided path"
sublabel="(Recommended)"
badge={<Badge size="sm" color="blue" appearance="light">New</Badge>}
description="Lessons unlock in order, with a quiz after each unit."
leading={<KeyIcon icon={<RiKey2Line />} size={40} />}
/>
<RadioCard value="free" label="Free explore" description="Open any lesson in any order." />
</RadioGroup>Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages and aren't published yet (D4, D12). Inside the Nuton monorepo, add them as workspace dependencies.
pnpm add @nuton/ui @nuton/tokens @remixicon/reactnpm install @nuton/ui @nuton/tokens @remixicon/reactyarn add @nuton/ui @nuton/tokens @remixicon/reactimport '@nuton/tokens/tokens.css'
import '@nuton/ui/styles.css'Usage
import { RadioCard, RadioGroup } from '@nuton/ui'
<RadioGroup label="Choose a plan" name="plan" defaultValue="pro">
<RadioCard value="free" label="Free" description="3 courses, community support." />
<RadioCard value="pro" label="Pro" sublabel="($8/month)" description="Unlimited courses and AI tutor feedback." />
</RadioGroup>- Use cards when each option needs a description, price or visual. For short options, use Radio.
- Always put cards in a
RadioGroup. It shares thename, holds thevalue, names the set with its legend and gives native arrow-key movement. Cards stretch to the group's width (Figma 360px). - The whole card is the click target. Each card needs a
value. - Preselect the most common or safest option unless the choice must be deliberate.
- The leading visual is decorative. Use a 40px KeyIcon, Avatar or a logo your app supplies.
Variants and sizes
Radio Card has one size. Figma's Type property only changes the leading visual, so code takes any 40px element in `leading`.
Types (leading visual)
<RadioGroup label="Leading visual" defaultValue="basic">
<RadioCard value="basic" label="Basic" description="No leading visual." />
<RadioCard value="icon" label="Left icon" description="A 40px key icon." leading={<KeyIcon icon={<RiKey2Line />} size={40} />} />
<RadioCard value="avatar" label="Avatar" description="A 40px avatar." leading={<Avatar name="Amara Okafor" size={40} />} />
<RadioCard value="logo" label="Card provider, brand or company" description="Pass your own logo." leading={<img src={logo} alt="" />} />
</RadioGroup>Card Provider, Brand and Company show third-party logos in Figma. Nuton ships no brand assets (D12), so pass your own <img alt=""> or SVG; it is sized to the 40px slot.
Layout
The default row puts the text between the leading visual and the radio. layout="stacked" puts the visual and the radio on one row with the text below, which suits short choices in a two-column grid, such as the onboarding questions.
<RadioGroup label="Tutor" defaultValue="professor" orientation="horizontal">
<RadioCard layout="stacked" value="professor" label="The Professor" description="Formal, structured, academic." leading={<KeyIcon icon={<RiGraduationCapLine />} size={40} />} />
<RadioCard layout="stacked" value="coach" label="The Coach" description="Encouraging and confidence building." leading={<KeyIcon icon={<RiFocus3Line />} size={40} />} />
</RadioGroup>Stacked cards draw the selected state with a 2px primary stroke, as Figma's Active variant does (3045:53929). Row cards keep the 1px stroke. Both are code-only choices pending design (C83).
Text options
<RadioCard
value="guided"
label="Guided path"
sublabel="(Recommended)"
badge={<Badge size="sm" color="blue" appearance="light">New</Badge>}
description="Lessons unlock in order, with a quiz after each unit."
/>| Part | Measurement |
|---|---|
| Card | Fills its group (Figma 360 × 72), radius 12, padding 16, gap 14, 1px stroke |
| Leading slot | 40 × 40 |
| Text column | Gap 4; title row gap 4, items centred |
| Label · sublabel · description | Label/Small · Paragraph/X Small · Paragraph/X Small |
| Control | 20px Radio at the trailing edge, top-aligned |
| Between cards | 12 (RadioGroup gap, code-only) |
| Stacked card | Figma 262 × 126: padding 16, gap 14, vertical; a 40px head row (leading at the start, radio at the end), text below; Active 2px stroke |
States and interactions
State tokens
Set on .nu-choice-card, shared with Checkbox Card: --nu-choice-card-bg (Bg/White), -bg-hover (Bg/Weak-50), -stroke (Stroke/Soft-200), -stroke-checked (State/Primary/Base), -shadow (regular-shadow/x-small) and -focus-shadow (buttons/primary-focus).
Figma discrepancies (pending a decision)
- Radio Card binds the remote 01-Tokens library for its white background, Soft-200 stroke and Weak-50 hover. Code maps them to the local tokens of the same name (C19).
- FC-20: disabled sublabel and description are Sub-600 here but Soft-400 on Checkbox Card. Code follows each component; proposal: align both.
- Disabled badges switch to Gray Light with a Soft-200 stroke in Figma. Pass
<Badge color="gray" appearance="stroke">when the card is disabled. - The radio uses the darker unselected ring approved in C48. Figma defines no group; the 12px gap and legend come from the code-only RadioGroup.
Composition examples
Plan picker
A controlled group with prices in the sublabel and a badge for the popular plan.
API reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | string | — | This option's value, compared with the RadioGroup's value. |
labelrequired | ReactNode | — | Figma Label. The accessible name; a click anywhere on the card selects it. |
sublabel | ReactNode | — | Figma Sublabel, e.g. "($8/month)". Part of the accessible name. |
badge | ReactNode | — | Figma Badge. Rendered outside the label, so not part of the name. |
description | ReactNode | — | Figma Description. Linked with aria-describedby. |
leading | ReactNode | — | Figma Type: a decorative 40px visual (KeyIcon, Avatar or your logo). |
layout | "row" | "stacked" | "row" | Row: visual, text and radio side by side. Stacked: visual and radio on top, text below (Figma 3045:53929). |
disabled | boolean | — | Disables this card. Inherits the group's disabled when omitted. |
invalid | boolean | — | Marks the option invalid. Inherits from the RadioGroup when omitted. |
className | string | — | Class for the card. |
…input props | InputHTMLAttributes | — | id, onChange and other attributes go to the native radio. The ref points at the input. Outside a RadioGroup, pass name and checked yourself. |
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves focus into the group, to the selected card |
| ↑ / ↓ | Selects the previous or next card |
| Space | Selects the focused card |
Screen readers
Native radios in a fieldset named by the legend. Each is named by its label and sublabel and described by its description; the badge and leading visual are not part of the name.
Focus
Focus stays on the native input; the whole card shows the primary stroke and halo.
Contrast and colour
Label 17.93:1, description 6.69:1. The selected stroke is State/Primary/Base, and the radio fill also shows the state, so it doesn't rely on the border colour alone.
Touch and pointer
The whole card, at least 52px tall, is the target, well above 24 × 24 (WCAG 2.5.8).
You must: put cards in a RadioGroup with a label, and give each card a unique, meaningful label.
Figma mapping
| Figma property | Figma values | Code |
|---|---|---|
| Type | Basic · Left Icon · Avatar · Card Provider · Brand · Company | leading (none for Basic) |
| Radio Card [1.1] 3045:53929: Property 1 | Default · Active (vertical) | layout="stacked"; Active is the group's value |
| State | Default · Hover · Active · Disabled | Interactive states, the group's value and disabled |
| Label · Sublabel · Description | Text · boolean + text · text | label · sublabel · description |
| Badge | Boolean | badge (pass a Badge) |
| Provider · Brand · Company | Instance swap | Your logo in leading |
| (none) | No group component | RadioGroup (code-only) |
Guidelines and related components
Use Radio Card for
- Two to five options that each need a description, price or visual
- Plans, learning paths or sign-in methods
Don't use Radio Card for
- Many or short options (use Radio or Select)
- Choosing several options (use Checkbox Card)