Skip to main content
View Zag.js on Github
Join the Discord server

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.



  • Syncs with disabled state of fieldset
  • Syncs with form reset events
  • Can programmatically set segmented control value
  • Can programmatically focus and blur segmented control items


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

This command will install the framework agnostic radio group logic and the reactive utilities for your framework of choice.


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-part attribute to help identify them in the DOM.


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 id to the useMachine hook. 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 [state, send] = useMachine(radio.machine({ id: "1" })) const api = radio.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> <div {...api.indicatorProps} /> { => ( <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> ) }

Disabling the segmented control

To make a segmented control disabled, set the context's disabled property to true

const [state, send] = 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 [state, send] = useMachine( radio.machine({ readOnly: true, }), )

Setting the initial value

To set the segmented control's initial value, set the context's value property to the value of the radio item to be selected by default

const [state, send] = useMachine( radio.machine({ value: "apple", }), )

Listening for changes

When the segmented control value changes, the onValueChange callback is invoked.

const [state, send] = 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 [state, send] = 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.


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 value of the checked radio
  • 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 disabled
  • 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.
  • 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 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

Edit this page on GitHub

On this page