Skip to content
  1. Keyline UI
  2. Toggle group
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

Toggle group

Independent toggles in one frame, each with its lamp. When the row runs out of room it scrolls inside the frame and shades the edge that hides items.

StandardNarrow
010020030040050060070080090010001100120013001400150016001700180019002000

Loading the live example…

"use client"

import { useState } from "react"
import { ToggleGroup, ToggleGroupItem } from "@/components/keyline/ui/toggle-group"

const ORDERS = [
  { id: "DM-4102", client: "Scranton Business Park", rep: "Dwight" },
  { id: "DM-4087", client: "Lackawanna County", rep: "Jim" },
  { id: "DM-4120", client: "Wilkes-Barre Library", rep: "Phyllis" },
  { id: "DM-4131", client: "Blue Cross of Pennsylvania", rep: "Stanley" },
  { id: "DM-4133", client: "Pennsylvania Power & Light", rep: "Dwight" },
  { id: "DM-4136", client: "Harper Collins, Scranton", rep: "Andy" },
  { id: "DM-4140", client: "Vance Refrigeration", rep: "Phyllis" },
]
const REPS = ["Dwight", "Jim", "Phyllis", "Stanley", "Andy"]

export function OrdersByRep() {
  const [reps, setReps] = useState<string[]>(["Dwight", "Phyllis"])
  // no rep chosen means no filter: every order shows
  const rows = reps.length ? ORDERS.filter((o) => reps.includes(o.rep)) : ORDERS

  return (
    <div className="grid min-w-0 grid-cols-[minmax(0,1fr)] gap-4">
      <ToggleGroup aria-label="Sales rep" value={reps} onValueChange={setReps} className="justify-self-start">
        {REPS.map((r) => (
          <ToggleGroupItem key={r} value={r} count={ORDERS.filter((o) => o.rep === r).length}>
            {r}
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <ul className="m-0 grid list-none gap-1 p-0 text-[14px]" aria-label="Open orders">
        {rows.map((o) => (
          <li key={o.id} className="truncate">
            <span className="font-kl-mono whitespace-nowrap">{o.id}</span> · {o.client}
          </li>
        ))}
      </ul>
      <p role="status" className="m-0 text-[14px] text-kl-ink-2">
        {reps.length ? `${rows.length} open orders for ${reps.join(", ")}.` : `All ${ORDERS.length} open orders.`}
      </p>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/toggle-group

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

Usage

import { ToggleGroup, ToggleGroupItem } from "@/components/keyline/ui/toggle-group"
<ToggleGroup aria-label="Sales rep" value={reps} onValueChange={setReps}>
  <ToggleGroupItem value="Dwight" count={2}>Dwight</ToggleGroupItem>
  <ToggleGroupItem value="Jim" count={1}>Jim</ToggleGroupItem>
</ToggleGroup>

Examples

In a panel

With layout="cell" the group sits in a panel header and shares its keylines. Turn both filters on to see what the panel says when nothing matches.

Loading the live example…

"use client"

import { useState } from "react"
import { Panel, PanelBody, PanelHeader } from "@/components/keyline/ui/panel"
import { ToggleGroup, ToggleGroupItem } from "@/components/keyline/ui/toggle-group"

const STOCK = [
  { item: "20 lb copy paper", cases: 4, low: true, onOrder: false },
  { item: "Cardstock, ivory", cases: 31, low: false, onOrder: false },
  { item: "Legal pads, yellow", cases: 2, low: true, onOrder: false },
  { item: "Toner, HP 26A", cases: 9, low: false, onOrder: true },
]

export function SupplyRoom() {
  const [show, setShow] = useState<string[]>([])
  const rows = STOCK.filter((s) => (!show.includes("low") || s.low) && (!show.includes("order") || s.onOrder))

  return (
    <Panel className="max-w-[520px]">
      <PanelHeader
        title="Supply room"
        actions={
          <ToggleGroup layout="cell" aria-label="Show" value={show} onValueChange={setShow}>
            <ToggleGroupItem value="low">Low stock</ToggleGroupItem>
            <ToggleGroupItem value="order">On order</ToggleGroupItem>
          </ToggleGroup>
        }
      />
      <PanelBody>
        {rows.length ? (
          <ul className="m-0 grid list-none gap-1 p-0 text-[14px]" aria-label="Stock">
            {rows.map((s) => (
              <li key={s.item} className="grid grid-cols-[minmax(0,1fr)_auto] gap-x-4">
                <span className="truncate">{s.item}</span>
                <span className="tabular-nums text-kl-ink-2">{s.cases} cases</span>
              </li>
            ))}
          </ul>
        ) : (
          <p className="m-0 text-[14px] text-kl-ink-2">
            Nothing low is on order yet: {STOCK.filter((s) => s.low).length} items need a reorder. Turn off On order to see them.
          </p>
        )}
      </PanelBody>
    </Panel>
  )
}

Props

PropTypeDefaultDescription
valuestring[]Default —The pressed items, with onValueChange. Or start with defaultValue.
multiplebooleanDefault trueIndependent toggles. For one choice of many, Segmented reads better than false.
layout"control" | "cell"Default "control"cell drops the frame, for a group in a panel header.
countReactNodeDefault —On ToggleGroupItem: a count after the label.

Tab enters the group at one item; arrow keys move between items. Built on Base UI Toggle Group.

Sheet 11 of 73PreviousToggleNextToolbar
On this page