static/app/components/core/radio/radio.mdx
import {useState} from 'react';
import {Flex} from '@sentry/scraps/layout'; import {Radio} from '@sentry/scraps/radio';
import * as Storybook from 'sentry/stories';
export const documentation = import('!!type-loader!@sentry/scraps/radio');
<Radio> is a radio button component for selecting a single option from a set of mutually exclusive choices. Radio buttons should always be used in groups where only one option can be selected at a time.
Use <Radio> for forms and settings where users must choose exactly one option from multiple choices. For independent binary choices, use <Checkbox> or <Switch> instead.
<label>
<Radio checked={value === 'option1'} onChange={() => setValue('option1')} />
Option 1
</label>
<Radio> comes in two sizes: md (default) and sm (small).
export function SizesDemo() { const [md, setMd] = useState(true); const [sm, setSm] = useState(true); return ( <Storybook.SideBySide> <Flex as="label" align="center" gap="sm"> Medium (default) <Radio checked={md} onClick={() => setMd(!md)} /> </Flex> <Flex as="label" align="center" gap="sm"> Small <Radio size="sm" checked={sm} onClick={() => setSm(!sm)} /> </Flex> </Storybook.SideBySide> ); }
<Storybook.Demo> <SizesDemo /> </Storybook.Demo>
<Radio checked={checked} onChange={handleChange} />
<Radio size="sm" checked={checked} onChange={handleChange} />
Radio buttons support checked, unchecked, and disabled states.
export function StatesDemo() { const [checked, setChecked] = useState(true); const [unchecked, setUnchecked] = useState(false); return ( <Storybook.SideBySide> <Flex as="label" align="center" gap="sm"> Checked <Radio checked={checked} onClick={() => setChecked(!checked)} /> </Flex> <Flex as="label" align="center" gap="sm"> Unchecked <Radio checked={unchecked} onClick={() => setUnchecked(!unchecked)} /> </Flex> <Flex as="label" align="center" gap="sm"> Disabled (unchecked) <Radio checked={false} disabled onClick={() => {}} /> </Flex> <Flex as="label" align="center" gap="sm"> Disabled (checked) <Radio checked disabled onClick={() => {}} /> </Flex> </Storybook.SideBySide> ); }
<Storybook.Demo> <StatesDemo /> </Storybook.Demo>
<Radio checked={true} onChange={handleChange} />
<Radio checked={false} onChange={handleChange} />
<Radio checked={false} disabled />
<Radio checked={true} disabled />
Radio buttons should always be used in groups with a shared name attribute. Manage the selected value in state and update it when any radio changes.
export function GroupDemo() { const [selected, setSelected] = useState('option2'); return ( <div role="radiogroup" aria-label="Choose an option"> <Flex as="label" align="center" gap="sm"> <Radio name="example" checked={selected === 'option1'} onChange={() => setSelected('option1')} /> Option 1 </Flex> <Flex as="label" align="center" gap="sm"> <Radio name="example" checked={selected === 'option2'} onChange={() => setSelected('option2')} /> Option 2 </Flex> <Flex as="label" align="center" gap="sm"> <Radio name="example" checked={selected === 'option3'} onChange={() => setSelected('option3')} /> Option 3 </Flex> <p>Selected: {selected}</p> </div> ); }
<Storybook.Demo> <GroupDemo /> </Storybook.Demo>
const [selected, setSelected] = useState('option2');
<div role="radiogroup" aria-label="Choose an option">
<label>
<Radio
name="group"
checked={selected === 'option1'}
onChange={() => setSelected('option1')}
/>
Option 1
</label>
<label>
<Radio
name="group"
checked={selected === 'option2'}
onChange={() => setSelected('option2')}
/>
Option 2
</label>
</div>;
Use radio groups for mutually exclusive form options:
<fieldset>
<legend>Deployment Environment</legend>
<label>
<Radio name="environment" checked={env === 'prod'} onChange={() => setEnv('prod')} />
Production
</label>
<label>
<Radio
name="environment"
checked={env === 'staging'}
onChange={() => setEnv('staging')}
/>
Staging
</label>
<label>
<Radio name="environment" checked={env === 'dev'} onChange={() => setEnv('dev')} />
Development
</label>
</fieldset>
Use for settings where only one option can be active:
<div>
<h4>Notification Frequency</h4>
<label>
<Radio checked={freq === 'realtime'} onChange={() => setFreq('realtime')} />
Real-time
</label>
<label>
<Radio checked={freq === 'daily'} onChange={() => setFreq('daily')} />
Daily digest
</label>
<label>
<Radio checked={freq === 'weekly'} onChange={() => setFreq('weekly')} />
Weekly digest
</label>
</div>
<Radio> follows the WAI-ARIA Radio pattern and meets WCAG 2.2 AA standards:
The component automatically provides proper native radio button behavior.
Labels (WCAG 3.3.2)
<label> element wrapping both the radio and textaria-label if no visible label// Good: Wrapping label
<label>
<Radio checked={selected === 'a'} onChange={handleChange} />
Option A
</label>
// Good: aria-label
<Radio checked={selected === 'a'} onChange={handleChange} aria-label="Option A" />
Radio Groups
role="radiogroup" on the containeraria-label or aria-labelledby for the groupname attribute for all radios in a group<div role="radiogroup" aria-label="Choose a plan">
<label>
<Radio name="plan" checked={plan === 'basic'} onChange={() => setPlan('basic')} />
Basic
</label>
<label>
<Radio name="plan" checked={plan === 'pro'} onChange={() => setPlan('pro')} />
Pro
</label>
</div>
Keyboard Navigation
Required Fields
required attributeFor more information, see the WAI-ARIA Radio practices.