Radio

Radio lets users select one option from a group of choices.

Basics

Use standalone Radio components for custom layouts or pass an options array to RadioGroup for quick single-selection forms.

Loading demo…

import { useState } from 'react'; import { Block, Radio, RadioGroup, Text } from '@plocks/ui'; const TEAMS = ['Falcons', 'Tigers', 'Sharks'] as const; export function Demo() { const [favoriteTeam, setFavoriteTeam] = useState<string>('Tigers'); const [ticketType, setTicketType] = useState<string>('reserved'); return ( <Block> <Block> <Text variant="small" c="muted"> Standalone radios </Text> <Block> {TEAMS.map((team) => ( <Radio key={team} value={team} checked={favoriteTeam === team} onChange={setFavoriteTeam} label={team} /> ))} </Block> </Block> <Block> <Text variant="small" c="muted"> Grouped selection </Text> <RadioGroup value={ticketType} onChange={setTicketType} options={[ { label: 'General admission', value: 'general' }, { label: 'Reserved seating', value: 'reserved' }, { label: 'VIP hospitality', value: 'vip' } ]} /> </Block> </Block> ); }

Variants

The variant prop on RadioGroup selects how the group is laid out and how the selected option is communicated. default keeps the classic dot indicators; card renders each option as a bordered surface (useful when options have descriptions); segmented joins the options into a single iOS-style control; chip lays them out as wrap-friendly pills (good for filter UIs).

Loading demo…

import { useState } from 'react'; import { Block, RadioGroup, Text } from '@plocks/ui'; const PLAN_OPTIONS = [ { label: 'Starter', value: 'starter', description: 'Up to 3 projects, community support' }, { label: 'Growth', value: 'growth', description: 'Unlimited projects, priority email support' }, { label: 'Scale', value: 'scale', description: 'Dedicated success manager + SSO' }, ]; const FREQUENCY_OPTIONS = [ { label: 'Daily', value: 'daily' }, { label: 'Weekly', value: 'weekly' }, { label: 'Monthly', value: 'monthly' }, ]; const FILTER_OPTIONS = [ { label: 'All', value: 'all' }, { label: 'Active', value: 'active' }, { label: 'Archived', value: 'archived' }, { label: 'Trashed', value: 'trashed' }, ]; export function Demo() { const [defaultValue, setDefaultValue] = useState('weekly'); const [planValue, setPlanValue] = useState('growth'); const [frequencyValue, setFrequencyValue] = useState('weekly'); const [filterValue, setFilterValue] = useState('active'); return ( <Block> <Block> <Text variant="small" c="muted">default</Text> <RadioGroup variant="default" value={defaultValue} onChange={setDefaultValue} options={FREQUENCY_OPTIONS} /> </Block> <Block> <Text variant="small" c="muted">card</Text> <RadioGroup variant="card" value={planValue} onChange={setPlanValue} options={PLAN_OPTIONS} /> </Block> <Block> <Text variant="small" c="muted">segmented</Text> <RadioGroup variant="segmented" value={frequencyValue} onChange={setFrequencyValue} options={FREQUENCY_OPTIONS} /> </Block> <Block> <Text variant="small" c="muted">chip</Text> <RadioGroup variant="chip" value={filterValue} onChange={setFilterValue} options={FILTER_OPTIONS} /> </Block> </Block> ); }

Theming

Combine the size, color, and validation props to align radios with your UI tokens and state requirements.

Loading demo…

import { useState } from 'react'; import { Block, Radio, RadioGroup, Text } from '@plocks/ui'; const COLOR_OPTIONS = ['primary', 'secondary', 'success', 'error'] as const; export function Demo() { const [sizeValue, setSizeValue] = useState<string>('club'); const [colorValue, setColorValue] = useState<typeof COLOR_OPTIONS[number]>('primary'); return ( <Block> <Block> <Text variant="small" c="muted"> Size tokens </Text> <RadioGroup size="sm" value={sizeValue} onChange={setSizeValue} options={[ { label: 'Club', value: 'club' }, { label: 'Suite', value: 'suite' }, { label: 'Field level', value: 'field' } ]} /> </Block> <Block> <Text variant="small" c="muted"> Semantic colors </Text> <Block> {COLOR_OPTIONS.map((tone) => ( <Radio key={tone} value={tone} checked={colorValue === tone} onChange={(value) => setColorValue(value as typeof COLOR_OPTIONS[number])} label={`${tone.charAt(0).toUpperCase()}${tone.slice(1)} tickets`} color={tone} /> ))} </Block> </Block> <Block> <Text variant="small" c="muted"> Common states </Text> <Radio value="available" checked label="Available" /> <Radio value="disabled" disabled label="Disabled" /> <Radio value="error" error="Select a seat" label="Needs attention" /> </Block> </Block> ); }

Orientations

Toggle orientation between horizontal and vertical to adapt radio groups to the available space.

Loading demo…

import { useState } from 'react'; import { Block, RadioGroup, Text } from '@plocks/ui'; export function Demo() { const [favoriteSport, setFavoriteSport] = useState<string>('soccer'); const [skillLevel, setSkillLevel] = useState<string>('intermediate'); return ( <Block> <Block> <Text variant="small" c="muted"> Horizontal layout </Text> <RadioGroup orientation="horizontal" value={favoriteSport} onChange={setFavoriteSport} options={[ { label: 'Soccer', value: 'soccer' }, { label: 'Basketball', value: 'basketball' }, { label: 'Tennis', value: 'tennis' }, { label: 'Volleyball', value: 'volleyball' } ]} /> </Block> <Block> <Text variant="small" c="muted"> Vertical layout </Text> <RadioGroup orientation="vertical" value={skillLevel} onChange={setSkillLevel} options={[ { label: 'Beginner', value: 'beginner' }, { label: 'Intermediate', value: 'intermediate' }, { label: 'Advanced', value: 'advanced' }, { label: 'Expert', value: 'expert' } ]} /> </Block> </Block> ); }

Forms

Pair RadioGroup with required and error messaging to validate selections before submitting a form workflow.

Loading demo…

import { useState } from 'react'; import { Block, Button, RadioGroup, Text } from '@plocks/ui'; const PLANS = [ { label: 'Starter — $9/mo', value: 'starter', description: 'Streamline a single project' }, { label: 'Team — $19/mo', value: 'team', description: 'Collaborate with up to 10 teammates' }, { label: 'Club — $39/mo', value: 'club', description: 'Unlock advanced analytics' } ]; const BILLING = [ { label: 'Monthly', value: 'monthly' }, { label: 'Annual (save 20%)', value: 'annual' } ]; export function Demo() { const [plan, setPlan] = useState<string>(''); const [billingCycle, setBillingCycle] = useState<string>('monthly'); const [planError, setPlanError] = useState<string | undefined>(); const [confirmation, setConfirmation] = useState<string | null>(null); const handleSubmit = () => { if (!plan) { setPlanError('Select a plan to continue'); setConfirmation(null); return; } setPlanError(undefined); setConfirmation(`Subscribed to the ${plan} plan with ${billingCycle} billing.`); }; return ( <Block> <RadioGroup label="Select a membership" options={PLANS} value={plan} onChange={(next) => { setPlan(next); setPlanError(undefined); }} error={planError} required /> <RadioGroup label="Billing cadence" orientation="horizontal" options={BILLING} value={billingCycle} onChange={setBillingCycle} /> <Button onPress={handleSubmit}>Confirm subscription</Button> {confirmation && ( <Text variant="small" c="success"> {confirmation} </Text> )} </Block> ); }
Scan to open this page on your phoneplocks.dev/ui/Radio