Segmented

One choice out of a short, fixed set, laid out as a single track with a thumb that slides between the options. A radio group that looks like a switch.

Code

antuikit's Segmented is Ant Design's Segmented, with its option value type kept, item corners that stay concentric with the track at any theme radius, and option tooltips in the kit's small bubble. It adds no prop, and every prop passes through.

Install

npx antuikit@latest init
npx antuikit@latest add segmented
segmented.json also installs Tooltip, for the option tooltips. It needs no icon pack, since an option's icon takes whatever node you pass. Run the first command too, for AntuikitProvider.

Import

One component, no compound parts. An option is a plain object in options, not a child element, so there is no Segmented.Item to import.
import { Segmented } from "@/components/ui/segmented"
<Segmented options={["Table", "Board", "Calendar"]} />

Options

options takes bare strings or numbers, or option objects. Most of what you can set is on the option object: label and icon take nodes, which is how you render custom content per option.
1
{/* Bare values: the string is both the label and the value */}
2
<Segmented options={["Daily", "Weekly", "Monthly"]} />
3
4
{/* Objects, when the label and the value differ */}
5
<Segmented
6
options={[
7
{ label: "Daily", value: "d" },
8
{ label: "Weekly", value: "w" },
9
{ label: "Locked", value: "l", disabled: true },
10
]}
11
/>
disabled on the control takes the whole track out of the tab order. On an option it takes that one option out and leaves the rest usable. The selected option stays highlighted either way, so a disabled control still shows its setting.

Typed values

Segmented is generic over its option values, and the kit keeps that. Add as const to the options array and onChange is typed with those values, so a useState setter fits with no cast.
1
const [view, setView] = useState<"list" | "grid">("list")
2
3
{/* onChange is (value: "list" | "grid") => void, no cast needed */}
4
<Segmented options={["list", "grid"] as const} value={view} onChange={setView} />

Shape, size and block

  • size is small, medium or large.
  • shape is the default rounded rectangle or round, a pill.
  • block fills the parent and divides it equally. Set the width on the parent.
  • orientation="vertical" replaces the older vertical boolean and wins when both are set. Neither warns.
1
<Segmented options={VIEWS} size="small" />
2
<Segmented options={VIEWS} shape="round" />
3
<Segmented options={VIEWS} block />
4
<Segmented options={VIEWS} orientation="vertical" />

The concentric corner

An item's radius is the track's radius minus the track padding, at every size. Ant Design picks a fixed radius per size, which only lines up with the track at its default theme, so the kit works it out instead. Change borderRadius in your theme and the items follow the track.
  • shape="round" is left alone, since it is already a full pill.
  • The sliding thumb gets the same radius as the item, so the highlight keeps its shape as it moves.

Icon-only options

Each option is a real <input type="radio"> inside a <label>, so dropping the word drops the radio's accessible name with it, and nothing warns. Keep the word and hide it inside icon, not in label.
1
// Leave label unset: an empty label still adds a gap
2
// and pushes the glyph off center.
3
{
4
value: "light",
5
tooltip: "Light",
6
icon: (
7
<>
8
<Icon name="sun" />
9
<span className="sr-only">Light</span>
10
</>
11
),
12
}
The word names the radio because a <label> is named by its text, and icon renders inside it. An option's title does not name it.
Option tooltips have no arrow unless you pass arrow: true, and one bubble slides along the row. Once any option has a tooltip, a Tooltip of your own inside a label joins that bubble too.

Styling

classNames styles one part (root, item, label, icon), so use it first. className lands on the root and goes through cn().
  • The thumb has no part key, so reach it with a [&_.ant-segmented-thumb] selector.
  • Style the thumb and the selected item together. The thumb slides between options and the item takes over once it lands, so styling one alone changes the highlight mid-slide.
  • in-[.ant-segmented-item-selected]: in a label node styles it while its option is selected, with no state of your own.
1
{/* A flat thumb: no elevation, a neutral fill */}
2
<Segmented
3
options={VIEWS}
4
className="bg-transparent
5
[&_.ant-segmented-item-selected]:bg-fill-content
6
[&_.ant-segmented-item-selected]:shadow-none
7
[&_.ant-segmented-thumb]:bg-fill-content
8
[&_.ant-segmented-thumb]:shadow-none"
9
/>
10
11
{/* A solid track in theme classes, so it inverts in dark mode */}
12
<Segmented
13
options={VIEWS}
14
classNames={{
15
root: "bg-bg-solid",
16
item: "text-bg-base has-checked:text-text",
17
}}
18
/>

API Reference

Segmented
Segmented's props. Rows marked antuikit in the Version column are added by the kit.
SegmentedItemType
The fields of an object entry in options.