Choice card
A card that is a radio or a checkbox, so a plan picker, a payment method or an add-on list submits like a form control.
Last updated
Show codeHide code
<ChoiceCard name="plan" value="pro" checked title="Pro" description="Unlimited projects, priority support." />Add
npx @ava-ui/apogee add choice-card
Depends on utils (uid). Zero JS.
Usage
---
import ChoiceCard from '@/components/apogee/ChoiceCard/ChoiceCard.astro'
---
<form>
<ChoiceCard name="plan" value="free" title="Free" description="One project." />
<ChoiceCard name="plan" value="pro" title="Pro" description="Unlimited projects." checked />
</form>
Anatomy
A <label> is the card. Inside, in order: the hidden native input (sr-only),
the optional start slot, the title and description column, the optional
end slot, and the selection mark (a radio dot or a check) at indicatorPosition.
Because the control is native, arrows move between radios that share a name,
Space toggles a checkbox, and the form posts name=value.
API
@composable — exposed to Content Editors in AVA. Any other native <label>
attribute passes through.
| Prop | Type | Default | Description |
|---|---|---|---|
type |
'radio' | 'checkbox' |
'radio' |
The native control. Radios that share name are one group. |
name |
string |
— | Required. Form field name. |
value |
string |
— | Required. Posted when selected. |
checked |
boolean |
false |
Initial state. |
disabled |
boolean |
false |
Disables the control and dims the card. |
required |
boolean |
false |
Native required on the input. |
title |
string |
— | Title text; the default slot replaces it. |
description |
string |
— | Supporting text under the title, read as the control’s description. The description slot replaces it. |
indicator |
'radio' | 'check' | 'none' |
matches type |
The selection mark. |
indicatorPosition |
'start' | 'end' |
'end' |
Which side the mark sits on. |
size |
'small' | 'medium' |
'medium' |
Padding and gap. |
id |
string |
generated | Id of the input. |
class |
string |
— | Extra classes on the card, appended last. |
Slots: start (icon, avatar, logo), default (title), description, end (price, badge).
Variants
Every type, as a form:
Show codeHide code
<form class="grid gap-3 sm:grid-cols-3">
<ChoiceCard name="plan" value="free" title="Free" description="One project, community support." />
<ChoiceCard name="plan" value="pro" title="Pro" description="Unlimited projects, priority support." checked />
<ChoiceCard name="plan" value="team" title="Team" description="Everything in Pro, shared billing." />
</form>
<form class="grid gap-3">
<ChoiceCard type="checkbox" name="addon" value="backups" title="Daily backups" description="Kept for 30 days." />
<ChoiceCard type="checkbox" name="addon" value="sso" title="Single sign-on" description="SAML and OIDC." checked />
</form>Every indicator and indicatorPosition:
Show codeHide code
<ChoiceCard name="i" value="a" indicator="radio" indicatorPosition="start" title="Radio at the start" checked />
<ChoiceCard name="i" value="b" indicator="check" title="Check at the end" />
<ChoiceCard name="i" value="c" indicator="none" title="No mark; the ring is the state" />Every size:
Show codeHide code
<ChoiceCard name="s" value="a" size="small" title="Small" description="Tighter padding." checked />
<ChoiceCard name="s" value="b" size="medium" title="Medium" description="The default." />States
Show codeHide code
<ChoiceCard name="st" value="a" title="Resting" />
<ChoiceCard name="st" value="b" title="Selected" checked />
<ChoiceCard name="st" value="c" title="Disabled" disabled />
<ChoiceCard name="st" value="d" title="Disabled, selected" disabled checked />- Hover — the neutral faded fill.
- Selected — primary border plus a 1px inset ring; the mark fills.
- Focus — the shared focus outline on the card when the hidden input has keyboard focus.
- Disabled — disabled tokens,
cursor-not-allowed, the mark dims.
Examples
With an icon, a price and a badge:
Show codeHide code
<ChoiceCard name="tier" value="startup" checked>
<FeaturedIcon slot="start" color="primary" size="small"><Zap /></FeaturedIcon>
Startup
<span slot="description">Up to 10 seats</span>
<span slot="end" class="font-semibold text-neutral">$49<span class="text-xs text-neutral-faded">/mo</span></span>
</ChoiceCard>Picking a person:
Show codeHide code
<ChoiceCard name="assignee" value="ana" indicator="check">
<Avatar slot="start" name="Ana Lima" size="small" />
Ana Lima
<span slot="description">Design lead</span>
</ChoiceCard>Accessibility
- The card is a
<label>for a real<input>; the name is the title text, anddescriptionis wired througharia-describedby. - Keyboard: Tab reaches the group (the checked radio, or the first), arrows move and select between radios with the same
name, Space toggles a checkbox. - The focus ring draws on the card (
:has(:focus-visible)), so it is visible although the input is visually hidden. - Group radios in a
<fieldset>with a<legend>when the set needs a name of its own.
Tokens
| Token | Used for |
|---|---|
--border-neutral / --border-primary |
Card border, selected border and inset ring |
--background-page / --background-neutral-faded-highlighted |
Card fill, hover |
--background-primary / --on-primary |
The filled mark and its dot or check |
--background-disabled / --foreground-disabled / --border-disabled |
Disabled |
--outline-color-focus |
Focus ring |
Do / Don’t
- Do use it when each option needs more than a label: a description, a price, an icon.
- Do keep every card in a group the same height; put them in a grid.
- Don’t mix radio and checkbox cards in one group.
- Don’t put links or buttons inside; a click anywhere selects the card.
Content
- Title: two or three words, a noun (“Pro”, “Daily backups”).
- Description: one short sentence or a fragment; no trailing period on fragments.