Segmented Navigation

A row of slim ticks that opens into a labelled pill under the cursor, with neighbours swelling like a dock.

Preview

Inspired by Till Janek's Segmented Navigation.

Setup

Install with the shadcn CLI

$npx shadcn@latest add https://kinetikui.vercel.app/r/segmented-nav.json

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. 1

    Run in your terminal:

    $npm install clsx tailwind-merge @radix-ui/react-slot class-variance-authority framer-motion
  2. 2

    Copy lib/utils.ts from the Code tab. Skip if you already have cn().

  3. 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. 4

    Copy the component from the Code tab and create components/ui/segmented-nav.tsx in your project.

  5. 5

    Import and render:

    Example
    import { 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

PropTypeDefaultNotes
items{ label: string; href?: string }[]—One segment each. An href renders a link.
valuenumber—Selected index. Leave unset for uncontrolled use.
defaultValuenumber0Starting index when uncontrolled.
onValueChange(index: number) => void—Called when a segment is clicked.
showActivebooleanfalseKeep the selected segment open while idle.
spreadnumber3Neighbours on each side that grow toward the open one.
labelstring"Segmented navigation"Accessible name for the nav landmark.
classNamestring—Applied to the nav.

Accessibility

  • Renders a nav landmark; 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