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

Toolbar

A row of commands in one frame. At narrow widths, secondary controls move into a More menu and keep their state.

StandardNarrow
010020030040050060070080090010001100120013001400150016001700180019002000

Loading the live example…

"use client"

import { AlignCenter, AlignLeft, AlignRight, Bold, Italic, Redo2, Underline, Undo2 } from "lucide-react"
import { useState } from "react"
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarMenuContent,
  ToolbarMenuTrigger,
  ToolbarSpacer,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@/components/keyline/ui/toolbar"
import { Menu, MenuItem } from "@/components/keyline/ui/menu"
import { Tooltip } from "@/components/keyline/ui/tooltip"

export function MemoEditor() {
  const [style, setStyle] = useState<string[]>(["bold"])
  const [align, setAlign] = useState<string[]>(["start"])
  const [comments, setComments] = useState(true)
  const [last, setLast] = useState("none")

  return (
    <>
      <Toolbar aria-label="Memo formatting" overflow="menu">
        <ToolbarGroup aria-label="History">
          <Tooltip content="Undo" shortcut="⌘Z">
            <ToolbarButton icon aria-label="Undo" onClick={() => setLast("Undo")}>
              <Undo2 size={16} aria-hidden="true" />
            </ToolbarButton>
          </Tooltip>
          <ToolbarButton icon aria-label="Redo" disabled>
            <Redo2 size={16} aria-hidden="true" />
          </ToolbarButton>
        </ToolbarGroup>
        <ToolbarToggleGroup aria-label="Text style" value={style} onValueChange={setStyle}>
          <ToolbarToggle icon value="bold" aria-label="Bold">
            <Bold size={16} aria-hidden="true" />
          </ToolbarToggle>
          <ToolbarToggle icon value="italic" aria-label="Italic">
            <Italic size={16} aria-hidden="true" />
          </ToolbarToggle>
          <ToolbarToggle icon value="underline" aria-label="Underline">
            <Underline size={16} aria-hidden="true" />
          </ToolbarToggle>
        </ToolbarToggleGroup>
        <ToolbarToggleGroup
          aria-label="Alignment"
          multiple={false}
          value={align}
          onValueChange={(v) => v.length && setAlign(v)}
        >
          <ToolbarToggle icon value="start" aria-label="Align start">
            <AlignLeft size={16} aria-hidden="true" />
          </ToolbarToggle>
          <ToolbarToggle icon value="centre" aria-label="Align centre">
            <AlignCenter size={16} aria-hidden="true" />
          </ToolbarToggle>
          <ToolbarToggle icon value="end" aria-label="Align end">
            <AlignRight size={16} aria-hidden="true" />
          </ToolbarToggle>
        </ToolbarToggleGroup>
        <Menu>
          <ToolbarMenuTrigger>Insert</ToolbarMenuTrigger>
          <ToolbarMenuContent>
            <MenuItem onClick={() => setLast("Insert table")}>Table</MenuItem>
            <MenuItem onClick={() => setLast("Insert checklist")}>Checklist</MenuItem>
            <MenuItem description="From the Scranton letterhead" onClick={() => setLast("Insert signature")}>
              Signature block
            </MenuItem>
          </ToolbarMenuContent>
        </Menu>
        <ToolbarSpacer />
        <ToolbarToggle pressed={comments} onPressedChange={setComments} affix="3">
          Comments
        </ToolbarToggle>
        <ToolbarButton primary onClick={() => setLast("Send")}>
          Send memo
        </ToolbarButton>
      </Toolbar>
      <p role="status">
        Last command: {last} · style {style.join(", ") || "none"} · align {align[0]} · comments {comments ? "on" : "off"}
      </p>
    </>
  )
}

Installation

npx shadcn@latest add @keyline-ui/toolbar

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

Usage

import {
  Toolbar,
  ToolbarButton,
  ToolbarToggle,
} from "@/components/keyline/ui/toolbar"
<Toolbar aria-label="Order actions" overflow="menu">
  <ToolbarButton primary>Refresh</ToolbarButton>
  <ToolbarToggle>Overdue only</ToolbarToggle>
</Toolbar>

Examples

In a panel

With layout="cell" the toolbar is the top row of a Panel: a search input, a menu of checkbox and radio items, a separator and a link, on one seam with the content below.

Loading the live example…

"use client"

import { Search } from "lucide-react"
import { useState } from "react"
import {
  Toolbar,
  ToolbarButton,
  ToolbarInput,
  ToolbarLink,
  ToolbarMenuContent,
  ToolbarMenuTrigger,
  ToolbarSeparator,
} from "@/components/keyline/ui/toolbar"
import {
  Menu,
  MenuCheckboxItem,
  MenuGroup,
  MenuGroupLabel,
  MenuRadioGroup,
  MenuRadioItem,
  MenuSeparator,
} from "@/components/keyline/ui/menu"
import { Panel, PanelBody } from "@/components/keyline/ui/panel"

const LABELS = { client: "Client", rep: "Sales rep", branch: "Branch" }

export function OrdersPanel() {
  const [columns, setColumns] = useState({ client: true, rep: true, branch: false })
  const [rows, setRows] = useState("comfortable")
  const shown = (Object.keys(columns) as (keyof typeof columns)[])
    .filter((key) => columns[key])
    .map((key) => LABELS[key])

  return (
    <Panel>
      <Toolbar layout="cell" overflow="menu" aria-label="Orders">
        <ToolbarInput label="Search orders" placeholder="Search orders" glyph={<Search size={16} aria-hidden="true" />} />
        <Menu>
          <ToolbarMenuTrigger>Columns</ToolbarMenuTrigger>
          <ToolbarMenuContent>
            <MenuGroup>
              <MenuGroupLabel>Shown</MenuGroupLabel>
              <MenuCheckboxItem checked={columns.client} onCheckedChange={(c) => setColumns({ ...columns, client: c })}>
                Client
              </MenuCheckboxItem>
              <MenuCheckboxItem checked={columns.rep} onCheckedChange={(c) => setColumns({ ...columns, rep: c })}>
                Sales rep
              </MenuCheckboxItem>
              <MenuCheckboxItem checked={columns.branch} onCheckedChange={(c) => setColumns({ ...columns, branch: c })}>
                Branch
              </MenuCheckboxItem>
            </MenuGroup>
            <MenuSeparator />
            <MenuRadioGroup value={rows} onValueChange={setRows}>
              <MenuGroupLabel>Rows</MenuGroupLabel>
              <MenuRadioItem value="comfortable">Comfortable</MenuRadioItem>
              <MenuRadioItem value="compact">Compact</MenuRadioItem>
            </MenuRadioGroup>
          </ToolbarMenuContent>
        </Menu>
        <ToolbarSeparator />
        <ToolbarButton>Export</ToolbarButton>
        <ToolbarLink href="#props">Help</ToolbarLink>
      </Toolbar>
      <PanelBody>
        <p role="status">
          Columns: {shown.join(", ") || "none"} · rows {rows}
        </p>
      </PanelBody>
    </Panel>
  )
}

Vertical

orientation="vertical" makes a tool palette: an exclusive choice of tool, then zoom. Up and Down arrows move between items.

Loading the live example…

"use client"

import { Minus, MousePointer2, Move, Plus, Ruler } from "lucide-react"
import { useState } from "react"
import { Toolbar, ToolbarButton, ToolbarToggle, ToolbarToggleGroup } from "@/components/keyline/ui/toolbar"

export function FloorPlanTools() {
  const [tool, setTool] = useState<string[]>(["pointer"])
  const [zoom, setZoom] = useState(100)

  return (
    <div className="flex items-start gap-3">
      <Toolbar orientation="vertical" aria-label="Warehouse floor plan tools">
        <ToolbarToggleGroup
          aria-label="Tool"
          multiple={false}
          value={tool}
          onValueChange={(v) => v.length && setTool(v)}
        >
          <ToolbarToggle icon value="pointer" aria-label="Select">
            <MousePointer2 size={16} aria-hidden="true" />
          </ToolbarToggle>
          <ToolbarToggle icon value="pan" aria-label="Pan">
            <Move size={16} aria-hidden="true" />
          </ToolbarToggle>
          <ToolbarToggle icon value="measure" aria-label="Measure">
            <Ruler size={16} aria-hidden="true" />
          </ToolbarToggle>
        </ToolbarToggleGroup>
        <ToolbarButton icon aria-label="Zoom in" disabled={zoom >= 200} onClick={() => setZoom((z) => z + 25)}>
          <Plus size={16} aria-hidden="true" />
        </ToolbarButton>
        <ToolbarButton icon aria-label="Zoom out" disabled={zoom <= 50} onClick={() => setZoom((z) => z - 25)}>
          <Minus size={16} aria-hidden="true" />
        </ToolbarButton>
      </Toolbar>
      <p role="status">
        Tool: {tool[0]} · zoom {zoom}%
      </p>
    </div>
  )
}

Scroll

The default overflow. Items that don’t fit stay in the row, which scrolls inside its frame and fades the hidden edge. Arrow keys bring the focused item into view.

StandardNarrow
010020030040050060070080090010001100120013001400150016001700180019002000

Loading the live example…

"use client"

import { Toolbar, ToolbarButton, ToolbarSeparator } from "@/components/keyline/ui/toolbar"

export function OrderCommands() {
  return (
    <Toolbar aria-label="Order commands">
      <ToolbarButton>Assign sales rep</ToolbarButton>
      <ToolbarButton>Reschedule delivery window</ToolbarButton>
      <ToolbarButton affix={<kbd>⌘P</kbd>}>Print invoices</ToolbarButton>
      <ToolbarSeparator />
      <ToolbarButton>Cancel order</ToolbarButton>
    </Toolbar>
  )
}

Props

PropTypeDefaultDescription
overflow"scroll" | "menu"Default "scroll"menu moves the items that don’t fit into a More menu, with their state. The primary button stays in the row.
layout"control" | "cell"Default "control"cell drops the toolbar’s own frame, for use inside a TableFrame or Panel.
overflowLabelstringDefault "More"Accessible name of the overflow item; its menu says how many items it holds.
overflowTrigger"icon" | "label"Default "icon"label shows the overflow label as text with a caret, for a row whose items may all move into it.
orientation"horizontal" | "vertical"Default "horizontal"Direction of the row.

Items: ToolbarButton, ToolbarToggle, ToolbarToggleGroup, ToolbarSeparator, ToolbarMenu, ToolbarLink, ToolbarInput. Built on Base UI Toolbar.

Sheet 12 of 73PreviousToggle groupNextMenu
On this page