Skip to content
  1. Keyline UI
  2. Segmented
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Segmented

One choice of a few, on one line in one frame. The ink band stretches to the chosen cell, leading edge first. When the row runs out of room it scrolls inside the frame.

StandardNarrow
010020030040050060070080090010001100120013001400150016001700180019002000

Loading the live example…

"use client"

import { useState } from "react"
import { Segmented, SegmentedItem } from "@/components/keyline/ui/segmented"

const PERIODS = [
  { value: "today", label: "Today", reams: 340, vs: "Tuesdays usually do 290" },
  { value: "week", label: "This week", reams: 1_810, vs: "112% of target" },
  { value: "month", label: "This month", reams: 6_420, vs: "Stamford's best month was 6,100" },
  { value: "quarter", label: "This quarter", reams: 19_880, vs: "Ahead of Nashua and Utica" },
  { value: "year", label: "Year to date", reams: 71_240, vs: "Top branch in the northeast" },
]

export function SalesByPeriod() {
  const [period, setPeriod] = useState("week")
  const p = PERIODS.find((x) => x.value === period)!

  return (
    <div className="grid gap-4">
      <Segmented aria-label="Period" value={period} onValueChange={setPeriod} className="justify-self-start">
        {PERIODS.map((x) => (
          <SegmentedItem key={x.value} value={x.value}>
            {x.label}
          </SegmentedItem>
        ))}
      </Segmented>
      <p role="status" className="m-0 grid gap-1">
        <span className="text-[28px] leading-none font-semibold tabular-nums">{p.reams.toLocaleString("en-US")} reams</span>
        <span className="text-[14px] text-kl-ink-2">
          Scranton, {p.label.toLowerCase()}. {p.vs}.
        </span>
      </p>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/segmented

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { Segmented, SegmentedItem } from "@/components/keyline/ui/segmented"
<Segmented aria-label="Period" value={period} onValueChange={setPeriod}>
  <SegmentedItem value="week">This week</SegmentedItem>
  <SegmentedItem value="month">This month</SegmentedItem>
</Segmented>

Examples

In a panel

size="compact" with layout="cell" fits a panel header and changes what the panel shows.

Loading the live example…

"use client"

import { useState } from "react"
import { Panel, PanelBody, PanelHeader } from "@/components/keyline/ui/panel"
import { Segmented, SegmentedItem } from "@/components/keyline/ui/segmented"

const BOARD = [
  { rep: "Dwight Schrute", sales: 41, calls: 212 },
  { rep: "Jim Halpert", sales: 33, calls: 148 },
  { rep: "Phyllis Vance", sales: 28, calls: 131 },
  { rep: "Stanley Hudson", sales: 26, calls: 64 },
]

export function Leaderboard() {
  const [by, setBy] = useState<"sales" | "calls">("sales")
  const rows = [...BOARD].sort((a, b) => b[by] - a[by])

  return (
    <Panel className="max-w-[440px]">
      <PanelHeader
        title="This month"
        actions={
          <Segmented layout="cell" size="compact" aria-label="Rank by" value={by} onValueChange={(v) => setBy(v as "sales" | "calls")}>
            <SegmentedItem value="sales">Sales</SegmentedItem>
            <SegmentedItem value="calls">Calls</SegmentedItem>
          </Segmented>
        }
      />
      <PanelBody>
        <ol className="m-0 grid list-none gap-1 p-0 text-[14px]" aria-label={`Ranked by ${by}`}>
          {rows.map((r, i) => (
            <li key={r.rep}>
              <span className="grid grid-cols-[1.5rem_minmax(0,1fr)_auto] gap-x-2">
                <span className="font-kl-mono text-kl-ink-3">{i + 1}</span>
                <span className="truncate">{r.rep}</span>
                <span className="tabular-nums text-kl-ink-2">
                  {r[by]} {by}
                </span>
              </span>
            </li>
          ))}
        </ol>
      </PanelBody>
    </Panel>
  )
}

Props

PropTypeDefaultDescription
valuestringDefault —The chosen item, with onValueChange. Or start with defaultValue.
size"default" | "compact"Default "default"Compact fits a panel header or a toolbar.
layout"control" | "cell"Default "control"cell drops the frame, to sit in a panel header.
namestringDefault —Submits the chosen value with a form.

It is a radio group: Tab enters at the chosen cell and arrow keys move and choose. Name it with aria-label. Built on Base UI Radio Group.

Sheet 22 of 73PreviousSliderNextSelect
On this page