Propel

Toolbar

A row of grouped controls with a toolbar role and roving keyboard focus.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarMenuTrigger,
  ToolbarSeparator,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@makeplane/propel/components/toolbar";
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  Bold,
  Code,
  Image,
  Italic,
  Link,
  List,
  ListChecks,
  ListOrdered,
  MessageSquare,
  Quote,
  Strikethrough,
  Table,
  Underline,
} from "lucide-react";

const TEXT_STYLES = ["Paragraph", "Heading 1", "Heading 2", "Heading 3"];
const FONTS = ["Sans", "Serif", "Mono"];

export default function BasicDemo() {
  return (
    <Toolbar elevation="raised" size="sm" aria-label="Formatting">
      <Menu>
        <ToolbarMenuTrigger aria-label="Text style" label="Text" />
        <MenuContent>
          {TEXT_STYLES.map((style) => (
            <MenuItem key={style} label={style} />
          ))}
        </MenuContent>
      </Menu>
      <Menu>
        <ToolbarMenuTrigger aria-label="Font" label="Aa" />
        <MenuContent>
          {FONTS.map((font) => (
            <MenuItem key={font} label={font} />
          ))}
        </MenuContent>
      </Menu>
      <ToolbarButton aria-label="Comment" icon={<Icon icon={MessageSquare} />} />
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Text formatting">
        <ToolbarToggle aria-label="Bold" icon={<Icon icon={Bold} />} />
        <ToolbarToggle aria-label="Italic" icon={<Icon icon={Italic} />} />
        <ToolbarToggle aria-label="Underline" icon={<Icon icon={Underline} />} />
        <ToolbarToggle aria-label="Strikethrough" icon={<Icon icon={Strikethrough} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarToggleGroup defaultValue={["left"]} aria-label="Text alignment">
        <ToolbarToggle value="left" aria-label="Align left" icon={<Icon icon={AlignLeft} />} />
        <ToolbarToggle
          value="center"
          aria-label="Align center"
          icon={<Icon icon={AlignCenter} />}
        />
        <ToolbarToggle value="right" aria-label="Align right" icon={<Icon icon={AlignRight} />} />
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Lists">
        <ToolbarToggle aria-label="Bullet list" icon={<Icon icon={List} />} />
        <ToolbarToggle aria-label="Numbered list" icon={<Icon icon={ListOrdered} />} />
        <ToolbarToggle aria-label="Checklist" icon={<Icon icon={ListChecks} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Blocks">
        <ToolbarToggle aria-label="Quote" icon={<Icon icon={Quote} />} />
        <ToolbarToggle aria-label="Code block" icon={<Icon icon={Code} />} />
        <ToolbarButton aria-label="Insert table" icon={<Icon icon={Table} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton aria-label="Insert link" icon={<Icon icon={Link} />} />
      <ToolbarButton aria-label="Insert image" icon={<Icon icon={Image} />} />
    </Toolbar>
  );
}

Installation

import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarInput,
  ToolbarLink,
  ToolbarMenuTrigger,
  ToolbarSeparator,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@makeplane/propel/components/toolbar";

Usage

import { Icon } from "@makeplane/propel/components/icon";
import { Menu, MenuContent, MenuItem } from "@makeplane/propel/components/menu";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarMenuTrigger,
  ToolbarSeparator,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@makeplane/propel/components/toolbar";
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  Bold,
  Code,
  Image,
  Italic,
  Link,
  List,
  ListChecks,
  ListOrdered,
  MessageSquare,
  Quote,
  Strikethrough,
  Table,
  Underline,
} from "lucide-react";

const TEXT_STYLES = ["Paragraph", "Heading 1", "Heading 2", "Heading 3"];
const FONTS = ["Sans", "Serif", "Mono"];

export default function BasicDemo() {
  return (
    <Toolbar elevation="raised" size="sm" aria-label="Formatting">
      <Menu>
        <ToolbarMenuTrigger aria-label="Text style" label="Text" />
        <MenuContent>
          {TEXT_STYLES.map((style) => (
            <MenuItem key={style} label={style} />
          ))}
        </MenuContent>
      </Menu>
      <Menu>
        <ToolbarMenuTrigger aria-label="Font" label="Aa" />
        <MenuContent>
          {FONTS.map((font) => (
            <MenuItem key={font} label={font} />
          ))}
        </MenuContent>
      </Menu>
      <ToolbarButton aria-label="Comment" icon={<Icon icon={MessageSquare} />} />
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Text formatting">
        <ToolbarToggle aria-label="Bold" icon={<Icon icon={Bold} />} />
        <ToolbarToggle aria-label="Italic" icon={<Icon icon={Italic} />} />
        <ToolbarToggle aria-label="Underline" icon={<Icon icon={Underline} />} />
        <ToolbarToggle aria-label="Strikethrough" icon={<Icon icon={Strikethrough} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarToggleGroup defaultValue={["left"]} aria-label="Text alignment">
        <ToolbarToggle value="left" aria-label="Align left" icon={<Icon icon={AlignLeft} />} />
        <ToolbarToggle
          value="center"
          aria-label="Align center"
          icon={<Icon icon={AlignCenter} />}
        />
        <ToolbarToggle value="right" aria-label="Align right" icon={<Icon icon={AlignRight} />} />
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Lists">
        <ToolbarToggle aria-label="Bullet list" icon={<Icon icon={List} />} />
        <ToolbarToggle aria-label="Numbered list" icon={<Icon icon={ListOrdered} />} />
        <ToolbarToggle aria-label="Checklist" icon={<Icon icon={ListChecks} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Blocks">
        <ToolbarToggle aria-label="Quote" icon={<Icon icon={Quote} />} />
        <ToolbarToggle aria-label="Code block" icon={<Icon icon={Code} />} />
        <ToolbarButton aria-label="Insert table" icon={<Icon icon={Table} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton aria-label="Insert link" icon={<Icon icon={Link} />} />
      <ToolbarButton aria-label="Insert image" icon={<Icon icon={Image} />} />
    </Toolbar>
  );
}

Examples

Elevations

raised draws its own card (border and shadow) so the toolbar can float over content; flat draws no surface and sits flush inside an existing bar.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarSeparator,
  ToolbarToggle,
} from "@makeplane/propel/components/toolbar";
import type { ToolbarElevation } from "@makeplane/propel/components/toolbar";
import { Bold, Italic, Link, Underline } from "lucide-react";

function Formatting({ elevation }: { elevation: ToolbarElevation }) {
  return (
    <Toolbar elevation={elevation} size="sm" aria-label="Text formatting">
      <ToolbarGroup aria-label="Text formatting">
        <ToolbarToggle aria-label="Bold" icon={<Icon icon={Bold} />} />
        <ToolbarToggle aria-label="Italic" icon={<Icon icon={Italic} />} />
        <ToolbarToggle aria-label="Underline" icon={<Icon icon={Underline} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton aria-label="Insert link" icon={<Icon icon={Link} />} />
    </Toolbar>
  );
}

export default function ElevationsDemo() {
  return (
    <div className="flex flex-col gap-6">
      <Formatting elevation="raised" />
      <Formatting elevation="flat" />
    </div>
  );
}

Sizes

size sets the control hit targets: sm packs them to 24px, md gives them 28px. Orthogonal to elevation. Figma’s density maps here — compactsm, comfortablemd (raised floaters use sm).

Show code
import { Icon } from "@makeplane/propel/components/icon";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarSeparator,
  ToolbarToggle,
} from "@makeplane/propel/components/toolbar";
import type { ToolbarSize } from "@makeplane/propel/components/toolbar";
import { Bold, Italic, Link, Underline } from "lucide-react";

function Formatting({ size }: { size: ToolbarSize }) {
  return (
    <Toolbar elevation="raised" size={size} aria-label="Text formatting">
      <ToolbarGroup aria-label="Text formatting">
        <ToolbarToggle aria-label="Bold" icon={<Icon icon={Bold} />} />
        <ToolbarToggle aria-label="Italic" icon={<Icon icon={Italic} />} />
        <ToolbarToggle aria-label="Underline" icon={<Icon icon={Underline} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton aria-label="Insert link" icon={<Icon icon={Link} />} />
    </Toolbar>
  );
}

export default function SizesDemo() {
  return (
    <div className="flex flex-col gap-6">
      <Formatting size="sm" />
      <Formatting size="md" />
    </div>
  );
}

With filter

ToolbarInput is an inline filter box that joins the roving tab order, its height tracking the toolbar’s size like every other item.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import {
  Toolbar,
  ToolbarGroup,
  ToolbarInput,
  ToolbarSeparator,
  ToolbarToggle,
} from "@makeplane/propel/components/toolbar";
import { Bold, Italic } from "lucide-react";

export default function WithFilterDemo() {
  return (
    <Toolbar elevation="raised" size="sm" aria-label="Issue actions">
      <ToolbarGroup aria-label="Text formatting">
        <ToolbarToggle aria-label="Bold" icon={<Icon icon={Bold} />} />
        <ToolbarToggle aria-label="Italic" icon={<Icon icon={Italic} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarInput aria-label="Filter issues" placeholder="Filter…" />
    </Toolbar>
  );
}

ToolbarLink is a toolbar item that navigates — an <a> sharing the same roving tab order and icon chrome as the buttons around it. Its required icon takes the bare glyph, so name it with aria-label.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import {
  Toolbar,
  ToolbarGroup,
  ToolbarLink,
  ToolbarSeparator,
  ToolbarToggle,
} from "@makeplane/propel/components/toolbar";
import { Bold, History, Italic } from "lucide-react";

export default function WithLinkDemo() {
  return (
    <Toolbar elevation="raised" size="sm" aria-label="Document actions">
      <ToolbarGroup aria-label="Text formatting">
        <ToolbarToggle aria-label="Bold" icon={<Icon icon={Bold} />} />
        <ToolbarToggle aria-label="Italic" icon={<Icon icon={Italic} />} />
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarLink
        href="#"
        onClick={(event) => event.preventDefault()}
        aria-label="View edit history"
        icon={<Icon icon={History} />}
      />
    </Toolbar>
  );
}

With tooltips

Toolbar controls are icon-only, so pair each with a Tooltip naming it (plus a dimmed shortcut hint). A shared TooltipProvider keeps open and close timing consistent across the bar.

Show code
import { Icon } from "@makeplane/propel/components/icon";
import { Toolbar, ToolbarToggle } from "@makeplane/propel/components/toolbar";
import { Tooltip, TooltipProvider } from "@makeplane/propel/components/tooltip";
import { Bold, Italic, Underline } from "lucide-react";

export default function WithTooltipsDemo() {
  return (
    <TooltipProvider>
      <Toolbar elevation="raised" size="sm" aria-label="Text formatting">
        <Tooltip label="Bold" shortcut="⌘ B">
          <ToolbarToggle aria-label="Bold" aria-keyshortcuts="Meta+B" icon={<Icon icon={Bold} />} />
        </Tooltip>
        <Tooltip label="Italic" shortcut="⌘ I">
          <ToolbarToggle
            aria-label="Italic"
            aria-keyshortcuts="Meta+I"
            icon={<Icon icon={Italic} />}
          />
        </Tooltip>
        <Tooltip label="Underline" shortcut="⌘ U">
          <ToolbarToggle
            aria-label="Underline"
            aria-keyshortcuts="Meta+U"
            icon={<Icon icon={Underline} />}
          />
        </Tooltip>
      </Toolbar>
    </TooltipProvider>
  );
}

API Reference

Toolbar

The ready-made toolbar: grafts Base UI's `Toolbar.Root` behavior (the `toolbar` role + roving focus, plus `disabled` / `loopFocus`) onto the styled horizontal `elements/toolbar` row and shares its `size` with the controls inside via context (so each control packs to match).

PropTypeDefaultDescription
size(required)"md" | "sm"Control hit-target scale: `sm` (24px) or `md` (28px). Orthogonal to `elevation`. Shared with the controls inside via context. Figma's `density` maps here — `compact` → `sm`, `comfortable` → `md` (raised floaters use `sm`).
elevation(required)"raised" | "flat"Whether the toolbar draws its own surface (`raised`) or sits flush (`flat`).
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar.

ToolbarGroup

Groups related controls: grafts Base UI's `Toolbar.Group` behavior (the `group` role + group `disabled`) onto the styled `ToolbarGroup` cluster. The group shares the toolbar row's `gap-2` per Figma, so it is a semantic grouping — pair it with `ToolbarSeparator`s to make it visible.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar group.

ToolbarSeparator

A thin vertical rule that visually divides one cluster of controls from the next: grafts Base UI's `Toolbar.Separator` behavior (the `separator` role) onto the styled rule.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar separator.

ToolbarButton

A toolbar action button: grafts Base UI's `Toolbar.Button` (roving-focus item) behavior onto the styled `ToolbarButton`, taking its `size` from the surrounding `Toolbar` via context.

PropTypeDefaultDescription
icon(required)ReactNodeIcon element rendered inside the toolbar button.
aria-labelstringDefines a string value that labels the current element. Accessible name for the icon button. @see aria-labelledby.
size"md" | "sm"Size override; defaults to the surrounding `Toolbar`'s size.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar button.

ToolbarToggle

A two-state toolbar button for formatting toggles like bold or italic. Grafts Base UI's `Toolbar.Button` (roving-focus item) and `Toggle` (pressed state) behaviors onto the styled `ToolbarButton`, and takes its `size` from the surrounding `Toolbar` via context.

PropTypeDefaultDescription
icon(required)ReactNodeIcon element rendered inside the toolbar toggle.
aria-labelstringDefines a string value that labels the current element. Accessible name for the icon toggle. @see aria-labelledby.
size"md" | "sm"Size override; defaults to the surrounding `Toolbar`'s size.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar button.

ToolbarToggleGroup

A cluster of `ToolbarToggle`s sharing one selection: grafts Base UI `Toolbar.Group` (toolbar group semantics + group `disabled`) and `ToggleGroup` (value state) onto the styled toggle-group container. Drive it with `value`/`defaultValue` + `onValueChange`. An `aria-label` is required.

PropTypeDefaultDescription
aria-label(required)stringAccessible name for the toggle cluster.

ToolbarLink

A toolbar item that navigates — Base UI's `Toolbar.Link` (`<a>` in the roving tab order) grafted onto the toolbar item chrome. Pass an icon element, e.g. `<Icon icon={ExternalLink} />`. An `aria-label` is required (icon-only).

PropTypeDefaultDescription
aria-label(required)stringAccessible name for the icon link.
icon(required)ReactNodeIcon element rendered inside the toolbar link.
size"md" | "sm"Size override; defaults to the surrounding `Toolbar`'s size.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar link (e.g. a router `Link`).

ToolbarInput

An inline text input in the toolbar's roving tab order (e.g. a filter box) — Base UI's `Toolbar.Input` grafted onto the styled miniature field, its height tracking the toolbar's size. An `aria-label` is required.

PropTypeDefaultDescription
aria-label(required)stringAccessible name for the field (placeholder is not a name).
size"md" | "sm"Size override; defaults to the surrounding `Toolbar`'s size.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Custom render for the underlying styled toolbar input.

ToolbarMenuTrigger

The trigger that opens a `ToolbarMenu`: a label plus a disclosure chevron.

PropTypeDefaultDescription
label(required)stringVisible menu-trigger label.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, MenuTriggerState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.