Primitives
Toolbar
Roving-focus toolbar for editor controls and compact actions.
Examples
Floating editor toolbar
Use variant="floating" for the shared popover surface, with skin-aware glass, borders, and shadows. Position it with layout classes.
Composition
Editing controls
- <Toolbar>
- <ToolbarGroup>
- <ToolbarButton />
- <ToolbarLink />
- <ToolbarSeparator />
- <ToolbarInput />
- <ToolbarGroup>
Installation
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.
npx shadcn@latest add https://control-ui.dev/r/toolbar.jsonRaw code
Primary installed source
"use client";import { Toolbar as ToolbarPrimitive } from "@base-ui/react/toolbar";import type { ComponentProps, CSSProperties } from "react";import type { ToolbarKnobStyle } from "@/components/control-ui/knob-contracts/toolbar-knobs";import { cn } from "@/components/control-ui/lib/cn";export type ToolbarVariant = "default" | "inverse" | "floating";export type ToolbarChrome = "standalone" | "embedded";export type ToolbarLinkVariant = "default" | "track";export type ToolbarProps = Omit<ComponentProps<"div">, "style"> & { orientation?: "horizontal" | "vertical"; variant?: ToolbarVariant; chrome?: ToolbarChrome; style?: CSSProperties & ToolbarKnobStyle;};export type ToolbarButtonProps = Omit<ComponentProps<"button">, "style"> & { iconOnly?: boolean; style?: CSSProperties & ToolbarKnobStyle;};export type ToolbarLinkProps = Omit<ComponentProps<"a">, "style"> & { variant?: ToolbarLinkVariant; active?: boolean; 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;};export function Toolbar({ orientation = "horizontal", variant = "default", chrome = "standalone", className, children, ...props}: ToolbarProps) { return ( <ToolbarPrimitive.Root orientation={orientation} data-control-ui="toolbar" data-control-family="toolbar" data-slot="root" data-variant={variant} data-chrome={chrome} className={cn("group/toolbar inline-flex", orientation === "vertical" ? "flex-col items-stretch" : "items-center", className)} {...props} > {variant === "floating" && ( <div aria-hidden="true" data-control-ui="toolbar" data-control-family="popup" data-popup-kind="toolbar" data-popup-part="surface" data-popup-static="" data-surface="floating" data-slot="surface" className="pointer-events-none absolute inset-0 -z-1" /> )} {children} </ToolbarPrimitive.Root> );}type 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", className, )} {...props} /> );}type RefinedToolbarLinkProps = ToolbarLinkProps & Pick<ComponentProps<typeof ToolbarPrimitive.Link>, "render">;export function ToolbarLink({ variant = "default", active = false, 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} data-active={active || undefined} aria-current={active ? "page" : undefined} className={cn( "inline-flex shrink-0 items-center justify-center [&_svg]:block [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", variant === "track" && "relative z-[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", className)} {...props} /> );}export function ToolbarSeparator({ orientation = "vertical", className, ...props }: ToolbarSeparatorProps) { return ( <ToolbarPrimitive.Separator orientation={orientation} data-control-ui="toolbar" data-control-family="toolbar" data-slot="separator" className={cn("shrink-0 self-stretch", 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", className)} {...props} /> );}Knobs
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-popup-* · 44 knobsHow the cascade resolves--cui-popup-title-foreground<color>var(--cui-popup-foreground)--cui-popup-title-font-size<length-percentage>var(--text-heading-3)--cui-popup-title-font-weight<number>var(--font-weight-semibold)--cui-popup-title-line-height<number>var(--text-heading-3--line-height)--cui-popup-title-letter-spacing<length>-0.025em--cui-popup-description-foreground<color>var(--muted-foreground)--cui-popup-close-inset<length-percentage>calc(var(--spacing) * 3)--cui-popup-item-inset<length-percentage>calc(var(--spacing) * 8)--cui-popup-label-padding-block<length-percentage>var(--cui-popup-item-padding-block)--cui-popup-label-padding-inline<length-percentage>var(--cui-popup-item-padding-inline)--cui-popup-label-font-size<length-percentage>var(--text-micro)--cui-popup-label-font-weight<number>var(--font-weight-medium)--cui-popup-label-letter-spacing<length>0.08em--cui-popup-label-text-transformnone | uppercase | lowercase | capitalizeuppercase--cui-popup-separator-inset<length-percentage>calc(var(--popover-padding) * -1)--cui-popup-shortcut-font-size<length-percentage>var(--text-body)--cui-popup-shortcut-letter-spacing<length>0.02em--cui-popup-status-success-foreground<color>var(--success-text)--cui-popup-status-warning-foreground<color>var(--warning-text)--cui-popup-status-info-foreground<color>var(--info-text)--cui-popup-status-error-foreground<color>var(--destructive)--cui-popup-radius<length-percentage>var(--radius-popover)--cui-popup-background<color>var(--popover)--cui-popup-foreground<color>var(--popover-foreground)--cui-popup-border-color<color>var(--control-rim)--cui-popup-border-width<length>var(--control-rim-width)--cui-popup-shadow*var(--shadow-pop)--cui-popup-backdrop-filter*blur(var(--backdrop-blur-popover))--cui-popup-backdrop-background<color>oklch(from var(--foreground) l c h / var(--overlay-opacity))--cui-popup-backdrop-blur<length>var(--backdrop-blur-overlay)--cui-popup-item-radius<length-percentage>var(--radius-popup-item-fit)--cui-popup-item-font-size<length-percentage>var(--text-body)--cui-popup-item-icon-size<length-percentage>1rem--cui-popup-item-padding-block<length-percentage>calc(var(--spacing) * 1)--cui-popup-item-padding-inline<length-percentage>calc(var(--padding-x) * 0.5)--cui-popup-item-min-height<length-percentage>max(var(--target-min), var(--control-h-xs))--cui-popup-item-highlight-shadow*inset 0 0 0 1px oklch(from var(--foreground) l c h / 0.35)--cui-popup-item-foreground<color>var(--foreground)--cui-popup-item-highlight-background<color>var(--hover-fill)--cui-popup-item-highlight-foreground<color>var(--cui-popup-item-foreground)--cui-popup-item-highlight-muted-foreground<color>var(--muted-foreground)--cui-popup-item-disabled-opacity<number>var(--disabled-opacity)--cui-popup-separator-color<color>var(--border)--cui-popup-shortcut-foreground<color>var(--muted-foreground)--cui-toolbar-* · 16 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),
calc(var(--cui-toolbar-padding) + var(--cui-toolbar-border-width))
)
)--cui-toolbar-padding<length-percentage>calc(var(--spacing) * 1)--cui-toolbar-background<color>var(--control-fill)--cui-toolbar-foreground<color>var(--foreground)--cui-toolbar-border-color<color>var(--border)--cui-toolbar-border-width<length>var(--control-rim-width)--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>var(--hover-fill)--cui-toolbar-item-hover-foreground<color>var(--foreground)--cui-toolbar-item-active-background<color>var(--active-fill)--cui-toolbar-item-active-foreground<color>var(--foreground)--cui-toolbar-separator-background<color>var(--border)