Radio group primitives with style wrappers
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
export default function RadioGroupDemoExample() {
return (
<RadioGroup defaultValue="comfortable" class="grid gap-3">
{[
['default', 'Default'],
['comfortable', 'Comfortable'],
['compact', 'Compact'],
].map(([value, label]) => (
<label class="flex items-center gap-3">
<RadioGroupItem value={value} />
{label}
</label>
))}
</RadioGroup>
)
}
Installation
npx @fictjs/shadcn@latest add radio-groupInstall the runtime dependencies:
npm install @fictjs/radix-uiCopy the Fict shadcn component into your project.
Source
components/ui/radio-group.tsx
src/components/ui/radio-group.tsx
import { RadioGroup as RadioGroupPrimitive } from '@fictjs/radix-ui'
import { cn } from '@/lib/cn'
type GroupProps = {
class?: string
[key: string]: unknown
}
type ItemProps = {
class?: string
indicatorClass?: string
children?: unknown
[key: string]: unknown
}
export function RadioGroup(props: GroupProps) {
const { class: className, ...rest } = props
return <RadioGroupPrimitive.Root class={cn('grid gap-2', className)} {...rest} />
}
export function RadioGroupItem(props: ItemProps) {
const { class: className, indicatorClass, children, ...rest } = props
return (
<RadioGroupPrimitive.Item
class={cn(
'aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...rest}
>
<RadioGroupPrimitive.Indicator class={cn('flex items-center justify-center', indicatorClass)}>
{children ?? <span class='block h-2.5 w-2.5 rounded-full bg-current' />}
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
Usage
Examples
Description
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
const options = [
['default', 'Default', 'Standard spacing for most use cases.'],
['comfortable', 'Comfortable', 'More space between elements.'],
['compact', 'Compact', 'Minimal spacing for dense layouts.'],
]
export default function RadioGroupDescriptionExample() {
return (
<RadioGroup defaultValue="comfortable" class="grid gap-4">
{options.map(([value, label, description]) => (
<label class="flex items-start gap-3">
<RadioGroupItem value={value} />
<span>
<strong class="block">{label}</strong>
<small class="text-muted-foreground">{description}</small>
</span>
</label>
))}
</RadioGroup>
)
}
Choice Card
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
const plans = [
['plus', 'Plus', 'For individuals and small teams.'],
['pro', 'Pro', 'For growing businesses.'],
['enterprise', 'Enterprise', 'For large teams and enterprises.'],
]
export default function RadioGroupChoiceCardExample() {
return (
<RadioGroup defaultValue="plus" class="grid w-full max-w-sm gap-3">
{plans.map(([value, title, description]) => (
<label class="flex items-center justify-between gap-4 rounded-lg border p-4">
<span>
<strong class="block">{title}</strong>
<small class="text-muted-foreground">{description}</small>
</span>
<RadioGroupItem value={value} />
</label>
))}
</RadioGroup>
)
}
Fieldset
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
export default function RadioGroupFieldsetExample() {
return (
<fieldset class="w-full max-w-xs">
<legend class="font-medium">Subscription Plan</legend>
<p class="mb-4 text-sm text-muted-foreground">
Yearly and lifetime plans offer significant savings.
</p>
<RadioGroup defaultValue="monthly" class="grid gap-3">
{[
['monthly', 'Monthly ($9.99/month)'],
['yearly', 'Yearly ($99.99/year)'],
['lifetime', 'Lifetime ($299.99)'],
].map(([value, label]) => (
<label class="flex items-center gap-3">
<RadioGroupItem value={value} />
{label}
</label>
))}
</RadioGroup>
</fieldset>
)
}
Disabled
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
export default function RadioGroupDisabledExample() {
return (
<RadioGroup defaultValue="option2" class="grid gap-3">
<label class="flex items-center gap-3 opacity-50">
<RadioGroupItem value="option1" disabled />
Disabled
</label>
<label class="flex items-center gap-3">
<RadioGroupItem value="option2" />
Option 2
</label>
<label class="flex items-center gap-3">
<RadioGroupItem value="option3" />
Option 3
</label>
</RadioGroup>
)
}
Invalid
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
export default function RadioGroupInvalidExample() {
return (
<fieldset class="w-full max-w-xs">
<legend class="font-medium">Notification Preferences</legend>
<p class="mb-4 text-sm text-muted-foreground">
Choose how you want to receive notifications.
</p>
<RadioGroup defaultValue="email" class="grid gap-3">
{[
['email', 'Email only'],
['sms', 'SMS only'],
['both', 'Both Email & SMS'],
].map(([value, label]) => (
<label class="flex items-center gap-3 text-destructive">
<RadioGroupItem value={value} aria-invalid="true" />
{label}
</label>
))}
</RadioGroup>
</fieldset>
)
}
Rtl
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
...import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
const translations = {
ar: {
dir: 'rtl',
options: [
['default', 'افتراضي', 'تباعد قياسي لمعظم حالات الاستخدام.'],
['comfortable', 'مريح', 'مساحة أكبر بين العناصر.'],
['compact', 'مضغوط', 'تباعد أدنى للتخطيطات الكثيفة.'],
],
},
he: {
dir: 'rtl',
options: [
['default', 'ברירת מחדל', 'ריווח סטנדרטי לרוב מקרי השימוש.'],
['comfortable', 'נוח', 'יותר מקום בין האלמנטים.'],
['compact', 'קומפקטי', 'ריווח מינימלי לפריסות צפופות.'],
],
},
en: {
dir: 'ltr',
options: [
['default', 'Default', 'Standard spacing for most use cases.'],
['comfortable', 'Comfortable', 'More space between elements.'],
['compact', 'Compact', 'Minimal spacing for dense layouts.'],
],
},
} as const
export default function RadioGroupRtlExample() {
let language = $state<keyof typeof translations>('ar')
const text = () => translations[language]
return (
<div class="grid gap-4">
<select
value={language}
onChange={event => {
language = event.currentTarget.value as keyof typeof translations
}}
>
<option value="ar">Arabic (العربية)</option>
<option value="he">Hebrew (עברית)</option>
<option value="en">English</option>
</select>
<RadioGroup defaultValue="comfortable" class="grid gap-4" dir={text().dir}>
{text().options.map(([value, label, description]) => (
<label class="flex items-start gap-3">
<RadioGroupItem value={value} dir={text().dir} />
<span>
<strong class="block">{label}</strong>
<small>{description}</small>
</span>
</label>
))}
</RadioGroup>
</div>
)
}