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-part
attribute 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'
export const Basic = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
{frameworks.map((framework) => (
<RadioGroup.Item key={framework} value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { Index } from 'solid-js'
export const Basic = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<Index each={frameworks}>
{(framework) => (
<RadioGroup.Item value={framework()}>
<RadioGroup.ItemText>{framework()}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</Index>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue', 'Svelte'])
</script>
<template>
<RadioGroup.Root>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :value="framework">
<RadioGroup.ItemText>{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script lang="ts">
import { RadioGroup } from '@ark-ui/svelte/radio-group'
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
</script>
<RadioGroup.Root>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
{#each frameworks as framework}
<RadioGroup.Item value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<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'
export const Disabled = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root disabled>
<RadioGroup.Label>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item key={framework} value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { Index } from 'solid-js'
export const Disabled = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root disabled>
<RadioGroup.Label>Framework</RadioGroup.Label>
<Index each={frameworks}>
{(framework) => (
<RadioGroup.Item value={framework()}>
<RadioGroup.ItemText>{framework()}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</Index>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue', 'Svelte'])
</script>
<template>
<RadioGroup.Root disabled>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :value="framework">
<RadioGroup.ItemText>{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script>
import { RadioGroup } from '@ark-ui/svelte/radio-group'
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
</script>
<RadioGroup.Root disabled>
<RadioGroup.Label>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<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'
export const InitialValue = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root defaultValue="Solid">
<RadioGroup.Label>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item key={framework} value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { Index } from 'solid-js'
export const InitialValue = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root value="Solid">
<RadioGroup.Label>Framework</RadioGroup.Label>
<Index each={frameworks}>
{(framework) => (
<RadioGroup.Item value={framework()}>
<RadioGroup.ItemText>{framework()}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</Index>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue', 'Svelte'])
</script>
<template>
<RadioGroup.Root model-value="Solid">
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :value="framework">
<RadioGroup.ItemText>{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script>
import { RadioGroup } from '@ark-ui/svelte/radio-group'
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
</script>
<RadioGroup.Root defaultValue="Solid">
<RadioGroup.Label>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.Root>
Listening for changes
When the radio group value changes, the onValueChange
callback is invoked.
import { RadioGroup } from '@ark-ui/react/radio-group'
export const OnEvent = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root onValueChange={(details) => console.log(details.value)}>
<RadioGroup.Label>Framework</RadioGroup.Label>
{frameworks.map((framework) => (
<RadioGroup.Item key={framework} value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.Root>
)
}
import { RadioGroup } from '@ark-ui/solid/radio-group'
import { Index } from 'solid-js'
export const OnEvent = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
return (
<RadioGroup.Root onValueChange={(details) => console.log(details.value)}>
<RadioGroup.Label>Framework</RadioGroup.Label>
<Index each={frameworks}>
{(framework) => (
<RadioGroup.Item value={framework()}>
<RadioGroup.ItemText>{framework()}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</Index>
</RadioGroup.Root>
)
}
<script setup lang="ts">
import { RadioGroup } from '@ark-ui/vue/radio-group'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue', 'Svelte'])
</script>
<template>
<RadioGroup.Root @value-change="(details) => console.log(details.value)">
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :value="framework">
<RadioGroup.ItemText>{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.Root>
</template>
<script>
import { RadioGroup } from '@ark-ui/svelte/radio-group'
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
</script>
<RadioGroup.Root onValueChange={(details) => console.log(details.value)}>
<RadioGroup.Label>Framework</RadioGroup.Label>
{#each frameworks as framework}
<RadioGroup.Item value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<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'
export const RootProvider = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
const radioGroup = useRadioGroup()
return (
<>
<button onClick={() => radioGroup.focus()}>Focus</button>
<RadioGroup.RootProvider value={radioGroup}>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
{frameworks.map((framework) => (
<RadioGroup.Item key={framework} value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
))}
</RadioGroup.RootProvider>
</>
)
}
import { RadioGroup, useRadioGroup } from '@ark-ui/solid/radio-group'
import { Index } from 'solid-js'
export const RootProvider = () => {
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
const radioGroup = useRadioGroup()
return (
<>
<button onClick={() => radioGroup().focus()}>Focus</button>
<RadioGroup.RootProvider value={radioGroup}>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<Index each={frameworks}>
{(framework) => (
<RadioGroup.Item value={framework()}>
<RadioGroup.ItemText>{framework()}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
)}
</Index>
</RadioGroup.RootProvider>
</>
)
}
<script setup lang="ts">
import { RadioGroup, useRadioGroup } from '@ark-ui/vue/radio-group'
import { ref } from 'vue'
const frameworks = ref(['React', 'Solid', 'Vue', 'Svelte'])
const radioGroup = useRadioGroup()
</script>
<template>
<button @click="radioGroup.focus()">Focus</button>
<RadioGroup.RootProvider :value="radioGroup">
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
<RadioGroup.Item v-for="framework in frameworks" :key="framework" :value="framework">
<RadioGroup.ItemText>{{ framework }}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
</RadioGroup.RootProvider>
</template>
<script>
import { RadioGroup, useRadioGroup } from '@ark-ui/svelte/radio-group'
const frameworks = ['React', 'Solid', 'Vue', 'Svelte']
const id = $props.id()
const radioGroup = useRadioGroup({ id })
</script>
<button onclick={() => radioGroup().focus()}>Focus</button>
<RadioGroup.RootProvider value={radioGroup}>
<RadioGroup.Label>Framework</RadioGroup.Label>
<RadioGroup.Indicator />
{#each frameworks as framework}
<RadioGroup.Item value={framework}>
<RadioGroup.ItemText>{framework}</RadioGroup.ItemText>
<RadioGroup.ItemControl />
<RadioGroup.ItemHiddenInput />
</RadioGroup.Item>
{/each}
</RadioGroup.RootProvider>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
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
Root
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
defaultValue | string The initial value of the checked radio when rendered. Use when you don't need to control the value of the radio group. | |
disabled | boolean If `true`, the radio group will be disabled | |
form | string The associate form of the underlying input. | |
id | string The 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 | string The name of the input fields in the radio (Useful for form submission). | |
onValueChange | (details: ValueChangeDetails) => void Function called once a radio is checked | |
orientation | 'horizontal' | 'vertical' Orientation of the radio group | |
readOnly | boolean Whether the checkbox is read-only | |
value | string The 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 | boolean Use 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] | indicator |
[data-disabled] | Present when disabled |
[data-orientation] | The orientation of the indicator |
ItemControl
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use 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 | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
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. |