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

Tabs

Views of one place. The chosen tab sits under a cobalt band that stretches across to it, leading edge first. The arrow keys move and choose at once, as in a view switcher. A row that doesn't fit scrolls sideways and marks its hidden edge.

Loading the live example…

"use client"

import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/keyline/ui/tabs"

const ORDERS = [
  ["DM-4107", "120 cases, copy paper", "Thu 12 Nov"],
  ["DM-4126", "80 cases, copy paper", "Delivered"],
  ["DM-4031", "20 cases, cardstock", "Delivered"],
]

export function ClientAccount() {
  return (
    <Tabs defaultValue="overview">
      <TabsList aria-label="Lackawanna County">
        <TabsTab value="overview">Overview</TabsTab>
        <TabsTab value="orders" count={ORDERS.length}>
          Orders
        </TabsTab>
        <TabsTab value="notes">Notes</TabsTab>
      </TabsList>
      <TabsPanel value="overview" className="grid gap-2 pt-4 text-[14px]">
        <p className="m-0">Lackawanna County has bought from the Scranton branch since 2003, through the purchasing office on North Washington Avenue.</p>
        <p className="m-0 text-kl-ink-2">Account rep: Dwight Schrute. Billing: Angela Martin.</p>
      </TabsPanel>
      <TabsPanel value="orders" className="pt-4">
        <ul className="m-0 grid list-none gap-2 p-0 text-[14px]">
          {ORDERS.map(([id, what, when]) => (
            <li key={id} className="grid grid-cols-[auto_1fr_auto] gap-3">
              <span className="font-kl-mono text-[13px]">{id}</span>
              <span>{what}</span>
              <span className="text-kl-ink-2">{when}</span>
            </li>
          ))}
        </ul>
      </TabsPanel>
      <TabsPanel value="notes" className="pt-4 text-[14px]">
        <p className="m-0">The new purchasing officer prefers calls to email. Deliveries go to the loading dock, not reception.</p>
      </TabsPanel>
    </Tabs>
  )
}

Installation

npx shadcn@latest add @keyline-ui/tabs

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

Usage

import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/keyline/ui/tabs"
<Tabs defaultValue="overview">
  <TabsList aria-label="Lackawanna County">
    <TabsTab value="overview">Overview</TabsTab>
    <TabsTab value="orders" count={3}>Orders</TabsTab>
  </TabsList>
  <TabsPanel value="overview">…</TabsPanel>
  <TabsPanel value="orders">…</TabsPanel>
</Tabs>

Examples

Views without panels

Leave out TabsPanel and control value to switch what one list shows. count puts how many each view holds under its label.

Loading the live example…

"use client"

import { useState } from "react"
import { Tabs, TabsList, TabsTab } from "@/components/keyline/ui/tabs"

const CALLS = [
  { who: "Lackawanna County", rep: "Dwight", done: true },
  { who: "Blue Cross of Pennsylvania", rep: "Jim", done: false },
  { who: "Harper Collins", rep: "Phyllis", done: true },
  { who: "Mount Airy Casino", rep: "Jim", done: false },
  { who: "Penn Paper Supply", rep: "Stanley", done: false },
]

const VIEWS = [
  { value: "all", label: "All", test: () => true },
  { value: "todo", label: "To call", test: (c: (typeof CALLS)[number]) => !c.done },
  { value: "done", label: "Called", test: (c: (typeof CALLS)[number]) => c.done },
]

export function CallViews() {
  const [view, setView] = useState("todo")
  const shown = CALLS.filter(VIEWS.find((v) => v.value === view)!.test)

  return (
    <div className="grid gap-4">
      {/* no panels: the tabs switch a view of one list */}
      <Tabs value={view} onValueChange={(v) => setView(v as string)}>
        <TabsList aria-label="Calls this week">
          {VIEWS.map((v) => (
            <TabsTab key={v.value} value={v.value} count={CALLS.filter(v.test).length}>
              {v.label}
            </TabsTab>
          ))}
        </TabsList>
      </Tabs>
      <ul aria-label="Calls" className="m-0 grid list-none gap-2 p-0 text-[14px]">
        {shown.map((c) => (
          <li key={c.who}>
            {c.who} <span className="text-kl-ink-3">· {c.rep}</span>
          </li>
        ))}
      </ul>
    </div>
  )
}

Props

PropTypeDefaultDescription
value · defaultValuestringDefault —On Tabs: the chosen tab, controlled with onValueChange, or where it starts.
countReactNodeDefault —On TabsTab: how many the view holds, in the count voice.
layout"control" | "cell"Default "control"On TabsList: cell sits inside another frame, as in a table.
activateOnFocusbooleanDefault trueOn TabsList: false makes arrows move focus only, and Enter or Space choose. Use it when a view is slow to load.
aria-labelstringDefault —On TabsList: what the tabs are views of.

Built on Base UI Tabs; its other props pass through. For an app's sections use Nav; for pages of a site, Navigation menu.

Sheet 42 of 73PreviousMarkerNextBreadcrumb
On this page