import {
AlignCenterIcon,
AlignLeftIcon,
AlignRightIcon,
BoldIcon,
ItalicIcon,
LinkIcon,
UnderlineIcon,
} from "lucide-react";
import {
Toolbar,
ToolbarButton,
ToolbarSeparator,
ToolbarToggle,
ToolbarToggleGroup,
} from "@/components/ui/toolbar";
export default function ToolbarBasic() {
return (
<Toolbar aria-label="Formatting">
<ToolbarToggleGroup multiple defaultValue={["bold"]} aria-label="Text style">
<ToolbarToggle value="bold" aria-label="Bold">
<BoldIcon />
</ToolbarToggle>
<ToolbarToggle value="italic" aria-label="Italic">
<ItalicIcon />
</ToolbarToggle>
<ToolbarToggle value="underline" aria-label="Underline">
<UnderlineIcon />
</ToolbarToggle>
</ToolbarToggleGroup>
<ToolbarSeparator />
<ToolbarToggleGroup defaultValue={["left"]} aria-label="Alignment">
<ToolbarToggle value="left" aria-label="Align left">
<AlignLeftIcon />
</ToolbarToggle>
<ToolbarToggle value="center" aria-label="Align center">
<AlignCenterIcon />
</ToolbarToggle>
<ToolbarToggle value="right" aria-label="Align right">
<AlignRightIcon />
</ToolbarToggle>
</ToolbarToggleGroup>
<ToolbarSeparator />
<ToolbarButton size="icon" aria-label="Add link">
<LinkIcon />
</ToolbarButton>
</Toolbar>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add toolbar
pnpm dlx shadcn@latest add @jarvis/toolbar
Usage
import { BoldIcon, ItalicIcon, LinkIcon } from "lucide-react";
import {
Toolbar,
ToolbarButton,
ToolbarSeparator,
ToolbarToggle,
ToolbarToggleGroup,
} from "@/components/ui/toolbar";
export function FormattingToolbar() {
return (
<Toolbar aria-label="Formatting">
<ToolbarToggleGroup multiple aria-label="Text style">
<ToolbarToggle value="bold" aria-label="Bold">
<BoldIcon />
</ToolbarToggle>
<ToolbarToggle value="italic" aria-label="Italic">
<ItalicIcon />
</ToolbarToggle>
</ToolbarToggleGroup>
<ToolbarSeparator />
<ToolbarButton size="icon" aria-label="Add link">
<LinkIcon />
</ToolbarButton>
</Toolbar>
);
}Use a toolbar for controls that act on the same thing, such as the selection in an editor. Tab
reaches it once, and the arrow keys move between its controls. Give it an aria-label.
Group toggles in a ToolbarToggleGroup, with multiple to allow more than one. ToolbarGroup
groups other controls, and ToolbarSeparator divides groups.
For buttons that each take a Tab stop, use a Button Group.
Composition
Examples
With tooltips
Pass a ToolbarButton to a TooltipTrigger's render prop.
import { CopyIcon, Redo2Icon, ScissorsIcon, Undo2Icon } from "lucide-react";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { Toolbar, ToolbarButton, ToolbarSeparator } from "@/components/ui/toolbar";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
const history = [
{ label: "Undo", keys: ["⌘", "Z"], icon: Undo2Icon },
{ label: "Redo", keys: ["⇧", "⌘", "Z"], icon: Redo2Icon },
];
const clipboard = [
{ label: "Cut", keys: ["⌘", "X"], icon: ScissorsIcon },
{ label: "Copy", keys: ["⌘", "C"], icon: CopyIcon },
];
function Action({ label, keys, icon: Icon }: (typeof history)[number]) {
return (
<Tooltip>
<TooltipTrigger render={<ToolbarButton size="icon" aria-label={label} />}>
<Icon />
</TooltipTrigger>
<TooltipContent>
{label}
<KbdGroup>
{keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</KbdGroup>
</TooltipContent>
</Tooltip>
);
}
export default function ToolbarWithTooltips() {
return (
<Toolbar aria-label="Edit">
{history.map((action) => (
<Action key={action.label} {...action} />
))}
<ToolbarSeparator />
{clipboard.map((action) => (
<Action key={action.label} {...action} />
))}
</Toolbar>
);
}With a menu
Render a menu's trigger through a ToolbarButton's render prop to keep it in the arrow key order.
ToolbarLink adds a link.
import {
ChevronDownIcon,
ImageIcon,
MessageSquarePlusIcon,
MinusIcon,
TableIcon,
} from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator } from "@/components/ui/toolbar";
import { preventNavigation } from "@/examples/data";
export default function ToolbarWithMenu() {
return (
<Toolbar aria-label="Document" className="w-full max-w-md">
<DropdownMenu>
<ToolbarButton render={<DropdownMenuTrigger />} className="pr-1.5">
Insert
<ChevronDownIcon />
</ToolbarButton>
<DropdownMenuContent>
<DropdownMenuItem>
<ImageIcon />
Image
</DropdownMenuItem>
<DropdownMenuItem>
<TableIcon />
Table
</DropdownMenuItem>
<DropdownMenuItem>
<MinusIcon />
Divider
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<ToolbarSeparator />
<ToolbarButton>
<MessageSquarePlusIcon />
Comment
</ToolbarButton>
<ToolbarLink href="#" onClick={preventNavigation} className="ml-auto">
Edited 2m ago
</ToolbarLink>
</Toolbar>
);
}With an input
ToolbarInput adds a text field. Use at most one, and put it last, since the arrow keys also move
its cursor.
import { ArrowUpDownIcon, ListFilterIcon } from "lucide-react";
import { Toolbar, ToolbarButton, ToolbarInput, ToolbarSeparator } from "@/components/ui/toolbar";
export default function ToolbarWithInput() {
return (
<Toolbar aria-label="Issues">
<ToolbarButton>
<ListFilterIcon />
Filter
</ToolbarButton>
<ToolbarButton>
<ArrowUpDownIcon />
Sort
</ToolbarButton>
<ToolbarSeparator />
<ToolbarInput type="search" placeholder="Search issues…" aria-label="Search issues" />
</Toolbar>
);
}Vertical
Set orientation="vertical" to stack the controls.
import {
CircleIcon,
HandIcon,
MousePointer2Icon,
PencilIcon,
SquareIcon,
TypeIcon,
} from "lucide-react";
import {
Toolbar,
ToolbarSeparator,
ToolbarToggle,
ToolbarToggleGroup,
} from "@/components/ui/toolbar";
export default function ToolbarVertical() {
return (
<Toolbar orientation="vertical" aria-label="Tools">
<ToolbarToggleGroup defaultValue={["select"]} aria-label="Tool">
<ToolbarToggle value="select" aria-label="Select">
<MousePointer2Icon />
</ToolbarToggle>
<ToolbarToggle value="pan" aria-label="Pan">
<HandIcon />
</ToolbarToggle>
<ToolbarSeparator />
<ToolbarToggle value="rectangle" aria-label="Rectangle">
<SquareIcon />
</ToolbarToggle>
<ToolbarToggle value="ellipse" aria-label="Ellipse">
<CircleIcon />
</ToolbarToggle>
<ToolbarToggle value="pencil" aria-label="Pencil">
<PencilIcon />
</ToolbarToggle>
<ToolbarToggle value="text" aria-label="Text">
<TypeIcon />
</ToolbarToggle>
</ToolbarToggleGroup>
</Toolbar>
);
}Props
The tables leave out className, style and render. Parts built on Base UI take them as its styling and composition guides describe.
Toolbar
| Prop | Type | Default |
|---|---|---|
disabled | boolean | — |
orientationThe orientation of the toolbar. | "horizontal" | "vertical" | 'horizontal' |
loopFocusIf | boolean | true |
ToolbarGroup
| Prop | Type | Default |
|---|---|---|
disabledWhen | boolean | false |
ToolbarButton
| Prop | Type | Default |
|---|---|---|
disabledWhen | boolean | false |
focusableWhenDisabledWhen | boolean | true |
nativeButtonWhether the component renders a native | boolean | true |
size | "default" | "icon" | "default" |
ToolbarToggleGroup
| Prop | Type | Default |
|---|---|---|
valueThe pressed state of the toggle group represented by an array of
the values of all pressed toggle buttons.
This is the controlled counterpart of | readonly string[] | — |
defaultValueThe pressed state of the toggle group represented by an array of
the values of all pressed toggle buttons.
This is the uncontrolled counterpart of | readonly string[] | — |
onValueChangeCallback fired when the pressed states of the toggle group changes. | ((groupValue: string[], eventDetails: ToggleGroup.ChangeEventDetails) => void) | — |
disabledWhether the toggle group should ignore user interaction. | boolean | false |
orientation | "horizontal" | "vertical" | 'horizontal' |
loopFocusWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. | boolean | true |
multipleWhen | boolean | false |
ToolbarToggle
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native | boolean | true |
disabledWhen | boolean | false |
focusableWhenDisabledWhen | boolean | true |
pressedWhether the toggle button is currently pressed.
This is the controlled counterpart of | boolean | — |
defaultPressedWhether the toggle button is currently pressed.
This is the uncontrolled counterpart of | boolean | false |
onPressedChangeCallback fired when the pressed state is changed. | ((pressed: boolean, eventDetails: Toggle.ChangeEventDetails) => void) | — |
valueA unique string that identifies the toggle when used inside a toggle group. | string | — |
size | "default" | "icon" | "icon" |
ToolbarSeparator
| Prop | Type | Default |
|---|---|---|
orientationThe orientation of the separator. Defaults to the opposite of the toolbar's orientation, so a horizontal toolbar renders vertical separators. | "horizontal" | "vertical" | — |
ToolbarInput
| Prop | Type | Default |
|---|---|---|
disabledWhen | boolean | false |
focusableWhenDisabledWhen | boolean | true |
defaultValue | string | number | readonly string[] | — |