Radio
A control element that allows the user to choose only one of a predefined set of mutually exclusive options. The singular property of a radio button makes it distinct from checkboxes, where the user can select and unselect any number of items.
import {Radio, RadioGroup} from "@qualcomm-ui/react/radio"Examples
Simple
Basic radio with label using the simple API.
<RadioGroup.Root defaultValue="html">
<RadioGroup.Label>Language</RadioGroup.Label>
<RadioGroup.Items>
<Radio label="HTML" value="html" />
<Radio label="CSS" value="css" />
<Radio label="TypeScript" value="ts" />
</RadioGroup.Items>
</RadioGroup.Root>Composite
Build with the composite API for granular control. This API requires you to provide each subcomponent, but gives you full control over the structure and layout.
<RadioGroup.Root defaultValue="html" name="language">
<RadioGroup.Label>Language</RadioGroup.Label>
<RadioGroup.Items>
<Radio.Root value="html">
<Radio.Control />
<Radio.HiddenInput />
<Radio.Label>HTML</Radio.Label>
</Radio.Root>
<Radio.Root value="css">
<Radio.Control />
<Radio.HiddenInput />
<Radio.Label>CSS</Radio.Label>
</Radio.Root>
<Radio.Root value="ts">
<Radio.Control />
<Radio.HiddenInput />
<Radio.Label>TypeScript</Radio.Label>
</Radio.Root>
</RadioGroup.Items>
</RadioGroup.Root>Layout
Use the composite API when you need to modify the layout of the radio buttons.
<RadioGroup.Root defaultValue="html" name="language">
<RadioGroup.Label>Language</RadioGroup.Label>
<RadioGroup.Items>
<Radio.Root value="html">
<Radio.HiddenInput />
<Radio.Label>HTML</Radio.Label>
<Radio.Control />
</Radio.Root>
<Radio.Root value="css">
<Radio.HiddenInput />
<Radio.Label>CSS</Radio.Label>
<Radio.Control />
</Radio.Root>
<Radio.Root value="ts">
<Radio.HiddenInput />
<Radio.Label>TypeScript</Radio.Label>
<Radio.Control />
</Radio.Root>
</RadioGroup.Items>
</RadioGroup.Root>Disabled
When disabled is true, the radio group becomes non-interactive and is typically rendered with reduced opacity to indicate its unavailable state.
<RadioGroup.Root defaultValue="html" disabled name="language">
<RadioGroup.Items>
<Radio label="HTML" value="html" />
<Radio label="CSS" value="css" />
<Radio label="TypeScript" value="ts" />
</RadioGroup.Items>
</RadioGroup.Root>Controlled State
Set the initial value using the defaultValue prop, or use value and onValueChange to control the value manually. These props follow our controlled state pattern.
import {type ReactElement, useState} from "react"
import {Radio, RadioGroup} from "@qualcomm-ui/react/radio"
export default function RadioControlledDemo(): ReactElement {
const [value, setValue] = useState<string | null>("html")
return (
<form>
<RadioGroup.Root
name="language"
onValueChange={(nextValue) => {
console.log("radio value change:", nextValue)
setValue(nextValue)
}}
value={value}
>
<RadioGroup.Items>
<Radio label="HTML" value="html" />
<Radio label="CSS" value="css" />
<Radio label="TypeScript" value="ts" />
</RadioGroup.Items>
</RadioGroup.Root>
</form>
)
}
Orientation
Controls the layout direction of radio buttons within the group.
<RadioGroup.Root
defaultValue="html"
name="language"
orientation="horizontal"
>
<RadioGroup.Label>Language</RadioGroup.Label>
<RadioGroup.Items>
<Radio label="HTML" value="html" />
<Radio label="CSS" value="css" />
<Radio label="TypeScript" value="ts" />
</RadioGroup.Items>
</RadioGroup.Root>Sizes
The radio supports three sizes: sm, md (default), and lg.
<RadioGroup.Root defaultValue="html">
<RadioGroup.Items>
<Radio label="small (sm)" size="sm" value="sm" />
<Radio label="medium (md)" size="md" value="md" />
<Radio label="large (lg)" size="lg" value="lg" />
</RadioGroup.Items>
</RadioGroup.Root>Forms
Choose the form library that fits your needs—we've built examples with React Hook Form and Tanstack Form to get you started.
React Hook Form
Use React Hook Form to handle the radio state and validation. ArkType works great for schema validation if you need it.
import type {ReactElement} from "react"
import {arktypeResolver} from "@hookform/resolvers/arktype"
import {type} from "arktype"
import {Controller, useForm} from "react-hook-form"
import {Button} from "@qualcomm-ui/react/button"
import {Radio, RadioGroup} from "@qualcomm-ui/react/radio"
interface FormData {
framework: string
language: string
}
const FormSchema = type({
framework: "string>0",
language: "string>0",
})
export default function RadioReactHookFormDemo(): ReactElement {
const {control, handleSubmit} = useForm<FormData>({
defaultValues: {
framework: "",
language: "",
},
resolver: arktypeResolver(FormSchema),
})
return (
<form
className="flex w-56 flex-col gap-4"
onSubmit={(e) => {
void handleSubmit((data) => console.log(data))(e)
}}
>
<Controller
control={control}
name="language"
render={({
field: {name, onChange, value, ...fieldProps},
fieldState: {error},
}) => {
return (
<RadioGroup.Root
invalid={!!error}
name={name}
onValueChange={onChange}
value={value}
{...fieldProps}
>
<RadioGroup.Label>Programming Language</RadioGroup.Label>
<RadioGroup.Items>
<Radio label="JavaScript" value="javascript" />
<Radio label="TypeScript" value="typescript" />
<Radio label="Python" value="python" />
</RadioGroup.Items>
<RadioGroup.ErrorText>{error?.message}</RadioGroup.ErrorText>
</RadioGroup.Root>
)
}}
/>
<Controller
control={control}
name="framework"
render={({
field: {name, onChange, value, ...fieldProps},
fieldState: {error},
}) => {
return (
<RadioGroup.Root
invalid={!!error}
name={name}
onValueChange={onChange}
value={value}
{...fieldProps}
>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Items>
<Radio label="React" value="react" />
<Radio label="Vue" value="vue" />
<Radio label="Angular" value="angular" />
</RadioGroup.Items>
<RadioGroup.ErrorText>{error?.message}</RadioGroup.ErrorText>
</RadioGroup.Root>
)
}}
/>
<Button
className="mt-1"
emphasis="primary"
size="sm"
type="submit"
variant="fill"
>
Submit
</Button>
</form>
)
}
Tanstack Form
Tanstack Form handles radio validation with its built-in validators.
import type {ReactElement} from "react"
import {useForm} from "@tanstack/react-form"
import {Button} from "@qualcomm-ui/react/button"
import {Radio, RadioGroup} from "@qualcomm-ui/react/radio"
interface FormData {
framework: string | null
language: string | null
}
const defaultFormData: FormData = {
framework: "",
language: "",
}
const requiredMessage = "Please select an option"
export default function RadioTanstackFormDemo(): ReactElement {
const form = useForm({
defaultValues: defaultFormData,
onSubmit: async ({value}) => {
console.log(value)
},
})
return (
<form
className="flex w-56 flex-col gap-4"
onSubmit={(event) => {
event.preventDefault()
event.stopPropagation()
void form.handleSubmit()
}}
>
<form.Field
name="language"
validators={{
onChange: (field) => (field.value ? undefined : requiredMessage),
onSubmit: (field) => (field.value ? undefined : requiredMessage),
}}
>
{({handleChange, name, state}) => {
const fieldError =
state.meta.errorMap["onChange"] || state.meta.errorMap["onSubmit"]
return (
<RadioGroup.Root
invalid={!!fieldError}
name={name}
onValueChange={handleChange}
value={state.value}
>
<RadioGroup.Label>Programming Language</RadioGroup.Label>
<RadioGroup.Items>
<Radio label="JavaScript" value="javascript" />
<Radio label="TypeScript" value="typescript" />
<Radio label="Python" value="python" />
</RadioGroup.Items>
<RadioGroup.ErrorText>{fieldError}</RadioGroup.ErrorText>
</RadioGroup.Root>
)
}}
</form.Field>
<form.Field
name="framework"
validators={{
onChange: (field) => (field.value ? undefined : requiredMessage),
onSubmit: (field) => (field.value ? undefined : requiredMessage),
}}
>
{({handleChange, name, state}) => {
const fieldError =
state.meta.errorMap["onChange"] || state.meta.errorMap["onSubmit"]
return (
<RadioGroup.Root
invalid={!!fieldError}
name={name}
onValueChange={handleChange}
value={state.value}
>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Items>
<Radio label="React" value="react" />
<Radio label="Vue" value="vue" />
<Radio label="Angular" value="angular" />
</RadioGroup.Items>
<RadioGroup.ErrorText>{fieldError}</RadioGroup.ErrorText>
</RadioGroup.Root>
)
}}
</form.Field>
<Button
className="mt-1"
emphasis="primary"
size="sm"
type="submit"
variant="fill"
>
Submit
</Button>
</form>
)
}
Tanstack form also supports ArkType.
Guidelines
RadioGroup
The Radio is only intended to be used as direct descendants of the <RadioGroup.Root> component.
/* Won't work alone ❌ */
<Radio label="Label" value="value" />
/* Works as expected ✅ */
<RadioGroup.Root>
<Radio label="Label" value="value" />
</RadioGroup.Root>Composite Components
The Radio's composite components are only intended to be used as direct descendants of the <Radio.Root> component.
/* Won't work alone ❌ */
<Radio.HiddenInput />
<Radio.Control />
/* Works as expected ✅ */
<Radio.Root>
<Radio.HiddenInput />
<Radio.Control />
</Radio.Root>API
<RadioGroup.Root>
| Prop | Type | Default |
|---|---|---|
The initial value of the checked radio when rendered.
Use when you don't need to control the value of the radio group. | string | |
The document's text/writing direction. | 'ltr' | 'rtl' | 'ltr' |
Whether the input is disabled. When true, prevents user interaction and
applies visual styling to indicate the disabled state. | boolean | |
The id of the form that the input belongs to. | string | |
A root node to correctly resolve document in custom environments. i.e.,
Iframes, Electron. | () => | |
Controls the visual error state of the input. When true, applies semantic error
styling to indicate validation failure. | boolean | |
The name of the input field. Useful for form submission. | string | |
Function called once a radio is checked | ( | |
Orientation of the radio group | | 'horizontal' | 'vertical' |
Whether the input is read-only. When true, prevents user interaction while
keeping the input focusable and visible. | boolean | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement | |
Whether the input is required. When true, the input must have a value for form
validation to pass. | boolean | |
The size of the radio and its elements. Governs properties like label font
size, control size, and indicator size. | | 'sm' | 'md' |
The controlled value of the radio group | string |
string'ltr' | 'rtl'
booleanstring() =>
| Node
| ShadowRoot
| Document
booleanstring(
value: string,
) => void
| 'horizontal'
| 'vertical'
boolean| ReactElement
| ((
props: object,
) => ReactElement)
boolean| 'sm'
| 'md'
| 'lg'
string<Radio.Root>
| Prop | Type | Default |
|---|---|---|
string | ||
boolean | ||
id attribute. If
omitted, a unique identifier will be automatically generated for accessibility. | string | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement | |
The size of the radio and its elements. Governs properties like label font
size, control size, and indicator size. | | 'sm' | 'md' |
stringbooleanstring| ReactElement
| ((
props: object,
) => ReactElement)
| 'sm'
| 'md'
| 'lg'
<Radio>
The Radio component supports all props from Radio.Root, plus the additional props listed below.
| Prop | Type |
|---|---|
Props applied to the control element. | RadioControlProps |
Props applied to the hidden input element. | RadioHiddenInputProps |
Optional label describing the element. Recommended. This element is
automatically associated with the component's input element for
accessibility. | |
Props applied to the label element. | RadioLabelProps |
RadioControlPropsRadioHiddenInputPropsRadioLabelProps