Radio Group
Allows single selection from multiple options.
Anatomy
To set up the radio group correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-partattribute to help identify them in the DOM.
Examples
Learn how to use the RadioGroup component in your project. Let's take a look at the most basic example:
import { RadioGroup } from '@ark-ui/react/radio-group'
import styles from 'styles/radio-group.module.css'
export const Basic = () => {
const frameworks = ['React', 'Solid', 'Vue']
return (
<RadioGroup.Root className={styles.Root} defaultValue="React">
<RadioGroup.Label className={styles.Label}>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item className={styles.Item} key={framework} value={framework}>
<RadioGroup.ItemControl className={styles.ItemControl} />
<RadioGroup.ItemText className={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { For } from 'solid-js'
import styles from 'styles/radio-group.module.css'
export const Basic = () => {
const frameworks = ['React', 'Solid', 'Vue']
return (
<RadioGroup.Root class={styles.Root} defaultValue="React">
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
<For each={frameworks}>
{(framework) => (
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</For>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
</script>
<template>
<RadioGroup.Root :class="styles.Root" default-value="React">
<RadioGroup.Label :class="styles.Label">Framework</RadioGroup.Label>
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :class="styles.Item" :value="framework">
<RadioGroup.ItemControl :class="styles.ItemControl" />
<RadioGroup.ItemText :class="styles.ItemText">{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script lang="ts">
import { RadioGroup } from '@ark-ui/svelte/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
</script>
<RadioGroup.Root class={styles.Root} defaultValue="React">
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.Root>
Disabling the radio group
To make a radio group disabled, set the disabled prop to true.
import { RadioGroup } from '@ark-ui/react/radio-group'
import styles from 'styles/radio-group.module.css'
export const Disabled = () => {
const frameworks = ['React', 'Solid', 'Vue']
return (
<RadioGroup.Root className={styles.Root} defaultValue="React" disabled>
<RadioGroup.Label className={styles.Label}>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item className={styles.Item} key={framework} value={framework}>
<RadioGroup.ItemControl className={styles.ItemControl} />
<RadioGroup.ItemText className={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { For } from 'solid-js'
import styles from 'styles/radio-group.module.css'
export const Disabled = () => {
const frameworks = ['React', 'Solid', 'Vue']
return (
<RadioGroup.Root class={styles.Root} defaultValue="React" disabled>
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
<For each={frameworks}>
{(framework) => (
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</For>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
</script>
<template>
<RadioGroup.Root :class="styles.Root" default-value="React" disabled>
<RadioGroup.Label :class="styles.Label">Framework</RadioGroup.Label>
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :class="styles.Item" :value="framework">
<RadioGroup.ItemControl :class="styles.ItemControl" />
<RadioGroup.ItemText :class="styles.ItemText">{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script lang="ts">
import { RadioGroup } from '@ark-ui/svelte/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
</script>
<RadioGroup.Root class={styles.Root} defaultValue="React" disabled>
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.Root>
Setting the initial value
To set the radio group's initial value, set the defaultValue prop to the value of the radio item to be selected by
default.
import { RadioGroup } from '@ark-ui/react/radio-group'
import styles from 'styles/radio-group.module.css'
export const InitialValue = () => {
const frameworks = ['React', 'Solid', 'Vue']
return (
<RadioGroup.Root className={styles.Root} defaultValue="Solid">
<RadioGroup.Label className={styles.Label}>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item className={styles.Item} key={framework} value={framework}>
<RadioGroup.ItemControl className={styles.ItemControl} />
<RadioGroup.ItemText className={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { For } from 'solid-js'
import styles from 'styles/radio-group.module.css'
export const InitialValue = () => {
const frameworks = ['React', 'Solid', 'Vue']
return (
<RadioGroup.Root class={styles.Root} defaultValue="Solid">
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
<For each={frameworks}>
{(framework) => (
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</For>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
</script>
<template>
<RadioGroup.Root :class="styles.Root" defaultValue="Solid">
<RadioGroup.Label :class="styles.Label">Framework</RadioGroup.Label>
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :class="styles.Item" :value="framework">
<RadioGroup.ItemControl :class="styles.ItemControl" />
<RadioGroup.ItemText :class="styles.ItemText">{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script lang="ts">
import { RadioGroup } from '@ark-ui/svelte/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
</script>
<RadioGroup.Root class={styles.Root} defaultValue="Solid">
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.Root>
Controlled Radio Group
For a controlled Radio Group, the state is managed using the value prop, and updates when the onValueChange event
handler is called:
import { RadioGroup } from '@ark-ui/react/radio-group'
import { useState } from 'react'
import styles from 'styles/radio-group.module.css'
export const Controlled = () => {
const frameworks = ['React', 'Solid', 'Vue']
const [value, setValue] = useState<string | null>(null)
return (
<RadioGroup.Root className={styles.Root} value={value} onValueChange={(e) => setValue(e.value)}>
<RadioGroup.Label className={styles.Label}>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item className={styles.Item} key={framework} value={framework}>
<RadioGroup.ItemControl className={styles.ItemControl} />
<RadioGroup.ItemText className={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { For, createSignal } from 'solid-js'
import styles from 'styles/radio-group.module.css'
export const Controlled = () => {
const frameworks = ['React', 'Solid', 'Vue']
const [value, setValue] = createSignal<string | null>(null)
return (
<RadioGroup.Root class={styles.Root} value={value()} onValueChange={(e) => setValue(e.value)}>
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
<For each={frameworks}>
{(framework) => (
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</For>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import { ref } from 'vue'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
const value = ref<string | null>(null)
</script>
<template>
<RadioGroup.Root :class="styles.Root" v-model="value">
<RadioGroup.Label :class="styles.Label">Framework</RadioGroup.Label>
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :class="styles.Item" :value="framework">
<RadioGroup.ItemControl :class="styles.ItemControl" />
<RadioGroup.ItemText :class="styles.ItemText">{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script lang="ts">
import { RadioGroup } from '@ark-ui/svelte/radio-group'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
let value = $state<string | null>(null)
</script>
<RadioGroup.Root class={styles.Root} bind:value>
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.Root>
Using the Root Provider
The RootProvider component provides a context for the radio-group. It accepts the value of the useRadio-group hook.
You can leverage it to access the component state and methods from outside the radio-group.
import { RadioGroup, useRadioGroup } from '@ark-ui/react/radio-group'
import button from 'styles/button.module.css'
import styles from 'styles/radio-group.module.css'
export const RootProvider = () => {
const frameworks = ['React', 'Solid', 'Vue']
const radioGroup = useRadioGroup({ defaultValue: 'React' })
return (
<div className="stack">
<RadioGroup.RootProvider className={styles.Root} value={radioGroup}>
<RadioGroup.Label className={styles.Label}>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item className={styles.Item} key={framework} value={framework}>
<RadioGroup.ItemControl className={styles.ItemControl} />
<RadioGroup.ItemText className={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.RootProvider>
<button className={button.Root} onClick={() => radioGroup.setValue('Solid')}>
Set to Solid
</button>
</div>
)
}
import { RadioGroup, useRadioGroup } from '@ark-ui/solid/radio-group'
import { For } from 'solid-js'
import button from 'styles/button.module.css'
import styles from 'styles/radio-group.module.css'
export const RootProvider = () => {
const frameworks = ['React', 'Solid', 'Vue']
const radioGroup = useRadioGroup({ defaultValue: 'React' })
return (
<div class="stack">
<RadioGroup.RootProvider class={styles.Root} value={radioGroup}>
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
<For each={frameworks}>
{(framework) => (
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</For>
</RadioGroup.RootProvider>
<button class={button.Root} onClick={() => radioGroup().setValue('Solid')}>
Set to Solid
</button>
</div>
)
}
<script setup lang="ts">
import { RadioGroup, useRadioGroup } from '@ark-ui/vue/radio-group'
import button from 'styles/button.module.css'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
const radioGroup = useRadioGroup({ defaultValue: 'React' })
</script>
<template>
<div class="stack">
<RadioGroup.RootProvider :class="styles.Root" :value="radioGroup">
<RadioGroup.Label :class="styles.Label">Framework</RadioGroup.Label>
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :class="styles.Item" :value="framework">
<RadioGroup.ItemControl :class="styles.ItemControl" />
<RadioGroup.ItemText :class="styles.ItemText">{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.RootProvider>
<button :class="button.Root" @click="radioGroup.setValue('Solid')">Set to Solid</button>
</div>
</template>
<script lang="ts">
import { RadioGroup, useRadioGroup } from '@ark-ui/svelte/radio-group'
import button from 'styles/button.module.css'
import styles from 'styles/radio-group.module.css'
const frameworks = ['React', 'Solid', 'Vue']
const id = $props.id()
const radioGroup = useRadioGroup({ id, defaultValue: 'React' })
</script>
<div class="stack">
<RadioGroup.RootProvider class={styles.Root} value={radioGroup}>
<RadioGroup.Label class={styles.Label}>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item class={styles.Item} value={framework}>
<RadioGroup.ItemControl class={styles.ItemControl} />
<RadioGroup.ItemText class={styles.ItemText}>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.RootProvider>
<button class={button.Root} onclick={() => radioGroup().setValue('Solid')}>Set to Solid</button>
</div>
If you're using the
RootProvidercomponent, you don't need to use theRootcomponent.
Guides
Using asChild
The RadioGroup.Item component renders as a label element by default. This ensures proper form semantics and
accessibility, as radio groups are form controls that require labels to provide meaningful context for users.
When using the asChild prop, you must render a label element as the direct child of RadioGroup.Item to
maintain valid HTML structure and accessibility compliance.
// INCORRECT usage ❌
<RadioGroup.Item asChild>
<div>
<RadioGroup.ItemHiddenInput />
<RadioGroup.ItemText>
<RadioGroup.ItemControl />
</RadioGroup.ItemText>
</div>
</RadioGroup.Item>
// CORRECT usage ✅
<RadioGroup.Item asChild>
<label>
<RadioGroup.ItemHiddenInput />
<RadioGroup.ItemText>
<RadioGroup.ItemControl />
</RadioGroup.ItemText>
</label>
</RadioGroup.Item>
Why ItemHiddenInput is required
The RadioGroup.ItemHiddenInput component renders a hidden HTML input element that enables proper form submission and
integration with native form behaviors. This component is essential for the radio group to function correctly as it:
- Provides the underlying input element that browsers use for form submission
- Enables integration with form libraries and validation systems
- Ensures the radio group works with native form reset functionality
// INCORRECT usage ❌
<RadioGroup.Item>
<RadioGroup.ItemText>
<RadioGroup.ItemControl />
</RadioGroup.ItemText>
</RadioGroup.Item>
// CORRECT usage ✅
<RadioGroup.Item>
<RadioGroup.ItemHiddenInput />
<RadioGroup.ItemText>
<RadioGroup.ItemControl />
</RadioGroup.ItemText>
</RadioGroup.Item>
API Reference
Props
Root
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
defaultValue | stringThe initial value of the checked radio when rendered. Use when you don't need to control the value of the radio group. | |
disabled | booleanIf `true`, the radio group will be disabled | |
form | stringThe associate form of the underlying input. | |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
root: string
label: string
indicator: string
item: (value: string) => string
itemLabel: (value: string) => string
itemControl: (value: string) => string
itemHiddenInput: (value: string) => string
}>The ids of the elements in the radio. Useful for composition. | |
name | stringThe name of the input fields in the radio (Useful for form submission). | |
onValueChange | (details: ValueChangeDetails) => voidFunction called once a radio is checked | |
orientation | 'horizontal' | 'vertical'Orientation of the radio group | |
readOnly | booleanWhether the checkbox is read-only | |
value | stringThe controlled value of the radio group |
| Data Attribute | Value |
|---|---|
[data-scope] | radio-group |
[data-part] | root |
[data-orientation] | The orientation of the radio-group |
[data-disabled] | Present when disabled |
Indicator
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--transition-property | The transition property value for the Indicator |
--left | The left position value |
--top | The top position value |
--width | The width of the element |
--height | The height of the element |
| Data Attribute | Value |
|---|---|
[data-scope] | radio-group |
[data-part] | indicator |
[data-disabled] | Present when disabled |
[data-orientation] | The orientation of the indicator |
ItemControl
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | radio-group |
[data-part] | item-control |
[data-active] | Present when active or pressed |
ItemHiddenInput
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Item
| Prop | Default | Type |
|---|---|---|
value | string | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
disabled | boolean | |
invalid | boolean |
ItemText
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Label
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | radio-group |
[data-part] | label |
[data-orientation] | The orientation of the label |
[data-disabled] | Present when disabled |
RootProvider
| Prop | Default | Type |
|---|---|---|
value | UseRadioGroupReturn | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Context
These are the properties available when using RadioGroup.Context, useRadioGroupContext hook or useRadioGroup hook.
API
| Property | Type |
|---|---|
value | stringThe current value of the radio group |
setValue | (value: string) => voidFunction to set the value of the radio group |
clearValue | VoidFunctionFunction to clear the value of the radio group |
focus | VoidFunctionFunction to focus the radio group |
getItemState | (props: ItemProps) => ItemStateReturns the state details of a radio input |
Accessibility
Complies with the Radio WAI-ARIA design pattern.
Keyboard Support
| Key | Description |
|---|---|
Tab | Moves focus to either the checked radio item or the first radio item in the group. |
Space | When focus is on an unchecked radio item, checks it. |
ArrowDown | Moves focus and checks the next radio item in the group. |
ArrowRight | Moves focus and checks the next radio item in the group. |
ArrowUp | Moves focus to the previous radio item in the group. |
ArrowLeft | Moves focus to the previous radio item in the group. |