Segmented Control
A Segmented control allows users to make a single selection from multiple exclusive options, providing a visually distinct and intuitive way of interacting with radio inputs.
Features
- Syncs with
disabledstate of fieldset - Syncs with form
resetevents - Can programmatically set segmented control value
- Can programmatically focus and blur segmented control items
Installation
To use segmented control add the radio machine to 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 segmented control 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 = [ { label: "React", value: "react" }, { label: "Angular", value: "ng" }, { label: "Vue", value: "vue" }, { label: "Svelte", value: "svelte" }, ] function Radio() { const service = useMachine(radio.machine, { id: "1" }) const api = radio.connect(service, normalizeProps) return ( <div {...api.getRootProps()}> <div {...api.getIndicatorProps()} /> {items.map((opt) => ( <label key={opt.value} {...api.getItemProps({ value: opt.value })}> <span {...api.getItemTextProps({ value: opt.value })}> {opt.label} </span> <input {...api.getItemHiddenInputProps({ value: opt.value })} /> </label> ))} </div> ) }
import * as radio from "@zag-js/radio-group" import { normalizeProps, useMachine } from "@zag-js/solid" import { Index, createMemo, createUniqueId } from "solid-js" const items = [ { label: "React", value: "react" }, { label: "Angular", value: "ng" }, { label: "Vue", value: "vue" }, { label: "Svelte", value: "svelte" }, ] function Radio() { const service = useMachine(radio.machine, { id: createUniqueId() }) const api = createMemo(() => radio.connect(service, normalizeProps)) return ( <div {...api().getRootProps()}> <div {...api().getIndicatorProps()} /> <Index each={items}> {(item) => ( <label {...api().getItemProps({ value: item().value })}> <span {...api().getItemTextProps({ value: item().value })}> {item().label} </span> <input {...api().getItemHiddenInputProps({ value: item().value })} /> </label> )} </Index> </div> ) }
<script setup> import * as radio from "@zag-js/radio-group" import { normalizeProps, useMachine } from "@zag-js/vue" import { computed } from "vue" const items = [ { label: "React", value: "react" }, { label: "Angular", value: "ng" }, { label: "Vue", value: "vue" }, { label: "Svelte", value: "svelte" }, ] const service = useMachine(radio.machine, { id: "1" }) const api = computed(() => radio.connect(service, normalizeProps)) </script> <template> <div v-bind="api.getRootProps()"> <div v-bind="api.getIndicatorProps()" /> <div v-for="opt in items" :key="opt.value"> <label v-bind="api.getItemProps({ value: opt.value })"> <span v-bind="api.getItemTextProps({ value: opt.value })" >{{ opt.label }}</span > <input v-bind="api.getItemHiddenInputProps({ value: opt.value })" /> </label> </div> </div> </template>
<script lang="ts"> import * as radio from "@zag-js/radio-group" import { normalizeProps, useMachine } from "@zag-js/svelte" const items = [ { label: "React", value: "react" }, { label: "Angular", value: "ng" }, { label: "Vue", value: "vue" }, { label: "Svelte", value: "svelte" }, ] const id = $props.id() const service = useMachine(radio.machine, { id, name: "fruit", orientation: "horizontal", }) const api = $derived(radio.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <div {...api.getIndicatorProps()}></div> {#each items as opt} <label {...api.getItemProps({ value: opt.value })}> <span {...api.getItemTextProps({ value: opt.value })}> {opt.label} </span> <input {...api.getItemHiddenInputProps({ value: opt.value })} /> </label> {/each} </div>
Disabling the segmented control
To make a segmented control disabled, set the context's disabled property to
true
const service = useMachine(radio.machine, { disabled: true, })
Making the segmented control readonly
To make a segmented control readonly, set the context's readOnly property to
true
const service = useMachine(radio.machine, { readOnly: true, })
Setting the initial value
Use the defaultValue property to set the segmented control's initial value.
const service = useMachine(radio.machine, { defaultValue: "apple", })
Listening for changes
When the segmented control value changes, the onValueChange callback is
invoked.
const service = useMachine(radio.machine, { onValueChange(details) { // details => { value: string } console.log("segmented control value is:", details.value) }, })
Usage within forms
To use segmented control 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 segmented control part has a data-part
attribute added to them to select and style them in the DOM.
Indicator
Style the segmented control Indicator through the indicator part.
[data-part="indicator"] { /* styles for indicator */ }
Focused State
When the radio input is focused, the data-focus attribute is added to the root
and label parts.
[data-part="radio"][data-focus] { /* styles for radio 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
and label parts.
[data-part="radio"][data-disabled] { /* styles for radio 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 and
label parts.
[data-part="radio"][data-invalid] { /* styles for radio 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:
idsPartial<{ 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.valuestringThe controlled value of the radio groupdefaultValuestringThe initial value of the checked radio when rendered. Use when you don't need to control the value of the radio group.namestringThe name of the input fields in the radio (Useful for form submission).formstringThe associate form of the underlying input.disabledbooleanIf `true`, the radio group will be disabledreadOnlybooleanWhether the checkbox is read-onlyonValueChange(details: ValueChangeDetails) => voidFunction called once a radio is checkedorientation"horizontal" | "vertical"Orientation of the radio groupdir"ltr" | "rtl"The document's text/writing direction.idstringThe 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:
valuestringThe current value of the radio groupsetValue(value: string) => voidFunction to set the value of the radio groupclearValueVoidFunctionFunction to clear the value of the radio groupfocusVoidFunctionFunction to focus the radio groupgetItemState(props: ItemProps) => ItemStateReturns the state details of a radio input
Data Attributes
Edit this page on GitHub