Forms
Radio cards
A single choice where each option needs a few words to decide: plans, delivery, sizes. The selection glides from card to card.
Installation
npx sinaui add radio-cardsCopy src/lib/ui/RadioCards.svelte, with Icon.svelte, glide.ts, motion.ts, spring.ts and tokens.css. No dependencies.
Examples
Side by side
Cards wrap to fit. One can't be chosen right now, and says why.
<script lang="ts">
import RadioCards, { type RadioCard } from '#lib/ui/RadioCards.svelte';
const plans: RadioCard[] = [
{ value: 'reader', label: 'Reader', description: 'Two loans at a time', detail: 'Free' },
{
value: 'scholar',
label: 'Scholar',
description: 'Ten loans and a copying desk',
detail: '12 dinars'
},
{
value: 'circle',
label: 'Study circle',
description: 'Eight members, one room',
detail: '40 dinars'
},
{
value: 'patron',
label: 'Patron',
description: 'Your name on a shelf',
detail: '100 dinars',
disabled: true,
reason: 'Opens again after Ramadan'
}
];
let plan = $state('scholar');
</script>
<div class="demo">
<RadioCards legend="Membership" options={plans} bind:value={plan} name="plan" />
</div>
<style>
.demo {
inline-size: min(40rem, 100%);
}
</style>One per row, with icons
layout="list" puts the detail at the end of each row.
<script lang="ts">
import { BoatIcon, CamelIcon, HorseIcon } from '@hugeicons/core-free-icons';
import RadioCards, { type RadioCard } from '#lib/ui/RadioCards.svelte';
const routes: RadioCard[] = [
{
value: 'caravan',
label: 'By caravan',
description: 'Baghdad to Damascus, 30 days',
detail: '4 dinars',
icon: CamelIcon
},
{
value: 'post',
label: 'By the barid post',
description: 'Relay riders, 8 days',
detail: '15 dinars',
icon: HorseIcon
},
{
value: 'river',
label: 'Down the Tigris',
description: 'To Basra only, 6 days',
detail: '6 dinars',
icon: BoatIcon
}
];
let route = $state('caravan');
</script>
<div class="demo">
<RadioCards legend="Send the manuscript" options={routes} bind:value={route} layout="list" />
</div>
<style>
.demo {
inline-size: min(30rem, 100%);
}
</style>Props
| Prop | Type | Default |
|---|---|---|
legend | string (required) | |
options | { value, label, description?, detail?, icon?, disabled?, reason? }[] | |
value | string (bindable) | |
layout | 'grid' | 'list' | 'grid' |
name | string | a unique id |
required, disabled | boolean | false |
hint, error | string |
Keyboard
| Key | Does |
|---|---|
| Tab | Moves into the group, onto the chosen card |
| ↑ ↓ ← → | Chooses the previous or next card |
| Space | Chooses the focused card |
Accessibility
- Real radios in a fieldset: the legend is read with each card, and forms send the value.
- Each card's description (or the reason it's unavailable) is read after its name.
- The chosen card shows a filled mark as well as its color.
- With reduced motion, the selection moves without gliding.