Segmented Navigation
A row of slim ticks that opens into a labelled pill under the cursor, with neighbours swelling like a dock.
Preview
Selected: Link 1
Inspired by Till Janek's Segmented Navigation.
Setup
Install with the shadcn CLI
One command adds every file, the npm packages and any Kinetik components it builds on. Needs a components.json; the CLI offers to create one if your project has none.
Or install manually
- 1
Run in your terminal:
$npm install clsx tailwind-merge @radix-ui/react-slot class-variance-authority framer-motion - 2
Copy
lib/utils.tsfrom the Code tab. Skip if you already havecn(). - 3
This component uses Button. Copy
components/ui/button/from the Code tab too, at the same path so the import resolves. Skip any you already have. - 4
Copy the component from the Code tab and create
components/ui/segmented-nav.tsxin your project. - 5
Import and render:
Exampleimport { SegmentedNav } from "@/components/ui/segmented-nav" <SegmentedNav items={[{ label: "Intro" }, { label: "Setup" }]} />
Needs framer-motion and the Button component, whose default style the open pill uses.
Usage
Each item is a tick until you point at it. It then opens into a pill with its label, and the ticks around it grow and brighten the closer they are. A dozen or more segments fit in the width of a few buttons.
import { useState } from "react"
import { SegmentedNav } from "@/components/ui/segmented-nav"
const steps = ["Account", "Profile", "Plan", "Billing", "Team", "Done"].map((label) => ({ label }))
export function Steps() {
const [step, setStep] = useState(0)
return <SegmentedNav items={steps} value={step} onValueChange={setStep} showActive />
}
Use it for navigation, steps, progress or categories. Give an item an href and it renders as
a link, with aria-current="page" on the selected one.
<SegmentedNav
label="Chapters"
items={[
{ label: "Intro", href: "#intro" },
{ label: "Install", href: "#install" },
{ label: "Usage", href: "#usage" },
]}
/>
Resting state
By default every segment rests as a tick, with the selected one a little brighter. Set
showActive to keep the selected segment open and labelled when nothing is hovered, which
suits steps and progress.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
items | { label: string; href?: string }[] | — | One segment each. An href renders a link. |
value | number | — | Selected index. Leave unset for uncontrolled use. |
defaultValue | number | 0 | Starting index when uncontrolled. |
onValueChange | (index: number) => void | — | Called when a segment is clicked. |
showActive | boolean | false | Keep the selected segment open while idle. |
spread | number | 3 | Neighbours on each side that grow toward the open one. |
label | string | "Segmented navigation" | Accessible name for the nav landmark. |
className | string | — | Applied to the nav. |
Accessibility
- Renders a
navlandmark; every segment has its label as its accessible name, even while collapsed - One tab stop: Left and Right arrows, Home and End move between segments, Enter or Space selects
- Focusing a segment with the keyboard opens it the same way hovering does
- Respects reduced motion by snapping instead of springing