Aura Design System

Toolbar

An always-visible bar of related actions.

Preview

Loading...
import { FontBoldIcon, FontItalicIcon, UnderlineIcon } from "@radix-ui/react-icons";
import { Toolbar, ToolbarButton, ToolbarSeparator } from "@/components/ui/Toolbar";

export function ToolbarDemo() {
  return (
    <Toolbar label="Formatting">
      <ToolbarButton aria-label="Bold">
        <FontBoldIcon className="icon" />
      </ToolbarButton>
      <ToolbarButton aria-label="Italic">
        <FontItalicIcon className="icon" />
      </ToolbarButton>
      <ToolbarSeparator />
      <ToolbarButton aria-label="Underline">
        <UnderlineIcon className="icon" />
      </ToolbarButton>
    </Toolbar>
  );
}

Installation

Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces

pnpm dlx shadcn@latest add @aura/toolbar

Manual

Copy and paste the class names utility into your utils/class-names.ts file.

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste the Toolbar component into your components/ui/Toolbar.tsx file.

"use client";

import * as React from "react";
import { cn } from "@/utils/class-names";

function Toolbar({
  className,
  label = "Toolbar",
  ...props
}: React.ComponentProps<"div"> & { label?: string }) {
  return (
    <div
      role="toolbar"
      aria-label={label}
      data-slot="toolbar"
      className={cn(
        "inline-flex items-center gap-0.5 rounded-sm border border-gray-6 bg-gray-1 p-0.5",
        className,
      )}
      {...props}
      onKeyDown={(event) => {
        props.onKeyDown?.(event);
        if (event.defaultPrevented) return;
        const items = event.currentTarget.querySelectorAll<HTMLElement>(
          "[data-slot='toolbar-button']:not([disabled])",
        );
        const list = Array.from(items);
        const index = list.indexOf(document.activeElement as HTMLElement);
        if (index < 0) return;
        if (event.key === "ArrowRight") {
          event.preventDefault();
          list[(index + 1) % list.length]?.focus();
        } else if (event.key === "ArrowLeft") {
          event.preventDefault();
          list[(index - 1 + list.length) % list.length]?.focus();
        }
      }}
    />
  );
}

function ToolbarButton({
  className,
  pressed,
  ...props
}: React.ComponentProps<"button"> & { pressed?: boolean }) {
  return (
    <button
      type="button"
      data-slot="toolbar-button"
      aria-pressed={pressed}
      className={cn(
        "inline-flex size-3 items-center justify-center rounded-sm text-gray-12 hover:bg-gray-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-8 aria-pressed:bg-accent-3",
        className,
      )}
      {...props}
    />
  );
}

function ToolbarSeparator({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      role="separator"
      aria-orientation="vertical"
      data-slot="toolbar-separator"
      className={cn("mx-0.5 h-2 w-px bg-gray-6", className)}
      {...props}
    />
  );
}

export { Toolbar, ToolbarButton, ToolbarSeparator };