Radio Group
A radio group allows users to make a single choice from a select number of option
Fruits
Features
- Syncs with disabledstate of fieldset
- Syncs with form resetevents
- Can programmatically set radio group value
- Can programmatically focus and blur radio items
Installation
To use the radio machine in your project, run the following command in your command line:
npm install @zag-js/radio-group @zag-js/react # or yarn add @zag-js/radio-group @zag-js/react
npm install @zag-js/radio-group @zag-js/solid # or yarn add @zag-js/radio-group @zag-js/solid
npm install @zag-js/radio-group @zag-js/vue # or yarn add @zag-js/radio-group @zag-js/vue
npm install @zag-js/radio-group @zag-js/svelte # or yarn add @zag-js/radio-group @zag-js/svelte
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.
Usage
First, import the radio group package into your project
import * as radio from "@zag-js/radio-group"
The radio package exports two key functions:
- machine— The state machine logic for the radio widget.
- connect— The function that translates the machine's state to JSX attributes and event handlers.
You'll also need to provide a unique
idto theuseMachinehook. This is used to ensure that every part has a unique identifier.
Next, import the required hooks and functions for your framework and use the radio machine in your project 🔥
import * as radio from "@zag-js/radio-group" import { useMachine, normalizeProps } from "@zag-js/react" const items = [ { id: "apple", label: "Apples" }, { id: "orange", label: "Oranges" }, { id: "mango", label: "Mangoes" }, { id: "grape", label: "Grapes" }, ] function Radio() { const service = useMachine(radio.machine, { id: "1" }) const api = radio.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <h3 {...api.getLabelProps()}>Fruits</h3> {items.map((opt) => ( <label key={opt.id} {...api.getItemProps({ value: opt.id })}> <span {...api.getItemTextProps({ value: opt.id })}>{opt.label}</span> <input {...api.getItemHiddenInputProps({ value: opt.id })} /> <div {...api.getItemControlProps({ value: opt.id })} /> </label> ))} </div> ) }
import * as radio from "@zag-js/radio-group" import { normalizeProps, useMachine } from "@zag-js/solid" import { createMemo, createUniqueId } from "solid-js" const items = [ { id: "apple", label: "Apples" }, { id: "orange", label: "Oranges" }, { id: "mango", label: "Mangoes" }, { id: "grape", label: "Grapes" }, ] function Radio() { const service = useMachine(radio.machine, { id: createUniqueId() }) const api = createMemo(() => radio.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <h3 {...api().getLabelProps()}>Fruits</h3> {items.map((opt) => ( <label {...api().getItemProps({ value: opt.id })}> <span {...api().getItemTextProps({ value: opt.id })}> {opt.label} </span> <input {...api().getItemHiddenInputProps({ value: opt.id })} /> <div {...api().getItemControlProps({ value: opt.id })} /> </label> ))} </div> ) }
<script setup> import * as radio from "@zag-js/radio-group" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const items = [ { id: "apple", label: "Apples" }, { id: "orange", label: "Oranges" }, { id: "mango", label: "Mangoes" }, { id: "grape", label: "Grapes" }, ] const service = useMachine(radio.machine, { id: "1" }) const api = computed(() => radio.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <h3 v-bind="api.getLabelProps()">Fruits</h3> <div v-for="opt in items" :key="opt.id"> <label v-bind="api.getItemProps({ value: opt.id })"> <span v-bind="api.getItemTextProps({ value: opt.id })" >{{ opt.label }}</span > <input v-bind="api.getItemHiddenInputProps({ value: opt.id })" /> <div v-bind="api.getItemControlProps({ value: opt.id })" /> </label> </div> </div> </template>
<script lang="ts"> import * as radio from "@zag-js/radio-group" import { useMachine, normalizeProps } from "@zag-js/svelte" const items = [ { id: "apple", label: "Apples" }, { id: "orange", label: "Oranges" }, { id: "mango", label: "Mangoes" }, { id: "grape", label: "Grapes" }, ] const id = $props.id() const service = useMachine(radio.machine, { id, name: "fruit", }) const api = $derived(radio.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <h3 {...api.getLabelProps()}>Fruits</h3> {#each items as opt} <label {...api.getItemProps({ value: opt.id })}> <span {...api.getItemTextProps({ value: opt.id })}>{opt.label}</span> <input {...api.getItemHiddenInputProps({ value: opt.id })} /> <div {...api.getItemControlProps({ value: opt.id })}></div> </label> {/each} </div>
Disabling the radio group
To make a radio group disabled, set the context's disabled property to true
const service = useMachine(radio.machine, { disabled: true, })
Setting the initial value
Use the defaultValue property to set the radio group's initial value.
const service = useMachine(radio.machine, { defaultValue: "apple", })
Listening for changes
When the radio group value changes, the onValueChange callback is invoked.
const service = useMachine(radio.machine, { onValueChange(details) { // details => { value: string } console.log("radio value is:", details.value) }, })
Usage within forms
To use radio group within forms, use the exposed inputProps from the connect
function and ensure you pass name value to the machine's context. It will
render a hidden input and ensure the value changes get propagated to the form
correctly.
const service = useMachine(radio.machine, { name: "fruits", })
Styling guide
Earlier, we mentioned that each radio part has a data-part attribute added to
them to select and style them in the DOM.
Checked State
When the radio input is checked, the data-state attribute is added to the
[data-part="radio"][data-state="checked|unchecked"] { /* styles for radio checked or unchecked state */ } [data-part="radio-control"][data-state="checked|unchecked"] { /* styles for radio checked or unchecked state */ } [data-part="radio-label"][data-state="checked|unchecked"] { /* styles for radio checked or unchecked state */ }
Focused State
When the radio input is focused, the data-focus attribute is added to the
root, control and label parts.
[data-part="radio"][data-focus] { /* styles for radio focus state */ } [data-part="radio-control"][data-focus] { /* styles for radio control focus state */ } [data-part="radio-label"][data-focus] { /* styles for radio label focus state */ }
Disabled State
When the radio is disabled, the data-disabled attribute is added to the root,
control and label parts.
[data-part="radio"][data-disabled] { /* styles for radio disabled state */ } [data-part="radio-control"][data-disabled] { /* styles for radio control disabled state */ } [data-part="radio-label"][data-disabled] { /* styles for radio label disabled state */ }
Invalid State
When the radio is invalid, the data-invalid attribute is added to the root,
control and label parts.
[data-part="radio"][data-invalid] { /* styles for radio invalid state */ } [data-part="radio-control"][data-invalid] { /* styles for radio control invalid state */ } [data-part="radio-label"][data-invalid] { /* styles for radio label invalid state */ }
Methods and Properties
Machine Context
The radio group machine exposes the following context properties:
- 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.
- value- stringThe controlled value of the radio group
- defaultValue- stringThe initial value of the checked radio when rendered. Use when you don't need to control the value of the radio group.
- name- stringThe name of the input fields in the radio (Useful for form submission).
- form- stringThe associate form of the underlying input.
- disabled- booleanIf `true`, the radio group will be disabled
- readOnly- booleanWhether the checkbox is read-only
- onValueChange- (details: ValueChangeDetails) => voidFunction called once a radio is checked
- orientation- "horizontal" | "vertical"Orientation of the radio group
- dir- "ltr" | "rtl"The document's text/writing direction.
- id- stringThe unique identifier of the machine.
- getRootNode- () => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
Machine API
The radio group api exposes the following methods:
- value- stringThe current value of the radio group
- setValue- (value: string) => voidFunction to set the value of the radio group
- clearValue- () => voidFunction to clear the value of the radio group
- focus- () => voidFunction to focus the radio group
- getItemState- (props: ItemProps) => ItemStateReturns the state details of a radio input
Accessibility
Adheres to the Radio Group WAI-ARIA design pattern
Keyboard Interactions
- TabMoves focus to either the checked radio item or the first radio item in the group.
- SpaceWhen focus is on an unchecked radio item, checks it.
- ArrowDownMoves focus and checks the next radio item in the group.
- ArrowRightMoves focus and checks the next radio item in the group.
- ArrowUpMoves focus to the previous radio item in the group.
- ArrowLeftMoves focus to the previous radio item in the group.
Edit this page on GitHub