Toolbar
Roving-focus toolbar for editor controls and compact actions.
Examples
Floating editor toolbar
The toolbar remains placement-agnostic while composing active tools, tooltips, and a nested DropdownMenu trigger.
Composition
The preferred shape for composing the installed primitive from its exported parts.
Parts
Exported compound parts from the installed source.
Toolbar ├── ToolbarButton ├── ToolbarLink ├── ToolbarGroup ├── ToolbarSeparator └── ToolbarInput
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/toolbar.tsx. Install it on its own with the command above, or inspect the source below.
npx shadcn@latest add https://control-ui.dev/r/toolbar.jsonKnobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-toolbar-* · 15 knobsHow the cascade resolves--cui-toolbar-radius<length-percentage>calc(
min(var(--cui-toolbar-item-radius), calc(var(--control-h-sm) / 2)) +
clamp(0px, calc(min(var(--cui-toolbar-item-radius), calc(var(--control-h-sm) / 2)) * 1000), var(--cui-toolbar-padding))
)--cui-toolbar-padding<length-percentage>0.25rem--cui-toolbar-background<color>oklch(from var(--card) l c h / 0.72)--cui-toolbar-foreground<color>var(--foreground)--cui-toolbar-border-color<color>var(--border)--cui-toolbar-shadow*var(--shadow-sm)--cui-toolbar-item-radius<length-percentage>min(var(--radius-sm), calc(var(--control-h-sm) / 2))--cui-toolbar-item-background<color>transparent--cui-toolbar-item-shadow*none--cui-toolbar-item-foreground<color>var(--muted-foreground)--cui-toolbar-item-hover-background<color>oklch(from var(--foreground) l c h / 0.06)--cui-toolbar-item-hover-foreground<color>var(--foreground)--cui-toolbar-item-active-background<color>oklch(from var(--foreground) l c h / 0.08)--cui-toolbar-item-active-foreground<color>var(--foreground)--cui-toolbar-separator-background<color>var(--border)Raw code
This primitive's source and the support files it installs with
"use client";import { Toolbar as ToolbarPrimitive } from "@base-ui/react/toolbar";import type { ComponentProps, CSSProperties } from "react";import { controlSize } from "@/components/control-ui/control-variants";import type { ToolbarKnobStyle } from "@/components/control-ui/knob-contracts/toolbar-knobs";import { cn } from "@/components/control-ui/lib/cn";export type ToolbarVariant = "default" | "inverse";export type ToolbarLinkVariant = "default" | "track";export type ToolbarProps = Omit<ComponentProps<"div">, "style"> & { orientation?: "horizontal" | "vertical"; variant?: ToolbarVariant; style?: CSSProperties & ToolbarKnobStyle;};export type ToolbarButtonProps = Omit<ComponentProps<"button">, "style"> & { iconOnly?: boolean; style?: CSSProperties & ToolbarKnobStyle;};export type ToolbarLinkProps = Omit<ComponentProps<"a">, "style"> & { variant?: ToolbarLinkVariant; style?: CSSProperties & ToolbarKnobStyle;};export type ToolbarGroupProps = ComponentProps<"div"> & { style?: CSSProperties & ToolbarKnobStyle };export type ToolbarSeparatorProps = Omit<ComponentProps<"div">, "style"> & { orientation?: "horizontal" | "vertical"; style?: CSSProperties & ToolbarKnobStyle;};export type ToolbarInputProps = Omit<ComponentProps<"input">, "style"> & { style?: CSSProperties & ToolbarKnobStyle;};// Button and Link forward Base UI's `render` prop, so composed trigger still receives roving-focus wiring.export function Toolbar({ orientation = "horizontal", variant = "default", className, ...props }: ToolbarProps) { return ( <ToolbarPrimitive.Root orientation={orientation} data-control-ui="toolbar" data-control-family="toolbar" data-slot="root" data-variant={variant} className={cn("group/toolbar inline-flex gap-1", orientation === "vertical" ? "flex-col items-stretch" : "items-center", className)} {...props} /> );}// `render` is picked straight off Base UI primitive so composed trigger types and merges as Base UI expectstype RefinedToolbarButtonProps = ToolbarButtonProps & Pick<ComponentProps<typeof ToolbarPrimitive.Button>, "render">;export function ToolbarButton({ iconOnly = false, className, ...props }: RefinedToolbarButtonProps) { return ( <ToolbarPrimitive.Button data-control-ui="toolbar" data-control-family="toolbar" data-slot="button" data-control="true" data-size="sm" data-icon-only={iconOnly ? "true" : undefined} className={cn( "inline-flex shrink-0 items-center justify-center select-none data-[disabled]:pointer-events-none [&_svg]:block [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", controlSize({ size: "sm" }), "px-2", iconOnly && "aspect-square px-0", className, )} {...props} /> );}type RefinedToolbarLinkProps = ToolbarLinkProps & Pick<ComponentProps<typeof ToolbarPrimitive.Link>, "render">;export function ToolbarLink({ variant = "default", className, ...props }: RefinedToolbarLinkProps) { return ( <ToolbarPrimitive.Link data-control-ui="toolbar" data-control-family="toolbar" data-slot="link" data-control="true" data-size="sm" data-variant={variant} className={cn( "inline-flex shrink-0 items-center justify-center [&_svg]:block [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", controlSize({ size: "sm" }), variant === "track" ? "relative z-[2] gap-1 px-1 sm:px-1.5" : "px-2", className, )} {...props} /> );}export function ToolbarGroup({ className, ...props }: ToolbarGroupProps) { return ( <ToolbarPrimitive.Group data-control-ui="toolbar" data-control-family="toolbar" data-slot="group" className={cn("inline-flex items-center gap-1", className)} {...props} /> );}export function ToolbarSeparator({ orientation = "vertical", className, ...props }: ToolbarSeparatorProps) { // horizontal toolbar renders vertical separators, hence default return ( <ToolbarPrimitive.Separator orientation={orientation} data-control-ui="toolbar" data-control-family="toolbar" data-slot="separator" className={cn("shrink-0 self-stretch", orientation === "vertical" ? "w-px" : "h-px w-full", className)} {...props} /> );}type RefinedToolbarInputProps = ToolbarInputProps & Pick<ComponentProps<typeof ToolbarPrimitive.Input>, "render">;export function ToolbarInput({ className, ...props }: RefinedToolbarInputProps) { return ( <ToolbarPrimitive.Input data-control-ui="toolbar" data-control-family="toolbar" data-slot="input" data-control="true" data-size="sm" className={cn("min-w-0", controlSize({ size: "sm" }), "px-2", className)} {...props} /> );}