|$ curl https://forge-ai.dev/api/markdown?path=docs/components/toolbar
$cat docs/toolbar.md
updated Recently·25 min read·published

Toolbar

UI ComponentsIntermediate🎯Free Tools
Introduction

A toolbar is a container for a group of controls (buttons, toggles, dropdowns) that perform related actions. Toolbars are common in rich text editors, media players, and IDEs. Proper ARIA semantics ensure screen readers announce the toolbar and its controls correctly.

Basic Toolbar
Toolbar.tsx
TypeScript
1"use client";
2import { useState, useRef } from "react";
3
4function Toolbar() {
5 const [bold, setBold] = useState(false);
6 const [italic, setItalic] = useState(false);
7 const [underline, setUnderline] = useState(false);
8 const toolbarRef = useRef<HTMLDivElement>(null);
9
10 // Roving tabindex: only one button is tabbable at a time
11 const handleKeyDown = (e: React.KeyboardEvent) => {
12 const buttons = Array.from(toolbarRef.current!.querySelectorAll("button"));
13 const currentIndex = buttons.indexOf(e.target as HTMLButtonElement);
14
15 let nextIndex = currentIndex;
16 if (e.key === "ArrowRight") nextIndex = (currentIndex + 1) % buttons.length;
17 if (e.key === "ArrowLeft") nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
18 if (e.key === "Home") nextIndex = 0;
19 if (e.key === "End") nextIndex = buttons.length - 1;
20
21 if (nextIndex !== currentIndex) {
22 e.preventDefault();
23 buttons[nextIndex].focus();
24 }
25 };
26
27 return (
28 <div
29 ref={toolbarRef}
30 role="toolbar"
31 aria-label="Text formatting"
32 aria-orientation="horizontal"
33 className="flex items-center gap-1 p-2 bg-[#1a1a28] border border-[#333] rounded-lg"
34 onKeyDown={handleKeyDown}
35 >
36 <button
37 role="button"
38 aria-pressed={bold}
39 tabIndex={bold ? 0 : -1}
40 className={`px-2 py-1 rounded text-sm ${bold ? "bg-[#00FF41] text-black" : "bg-white/10 text-[#E0E0E0]"}`}
41 onClick={() => setBold(!bold)}
42 >
43 B
44 </button>
45 <button
46 aria-pressed={italic}
47 tabIndex={italic ? 0 : -1}
48 className={`px-2 py-1 rounded text-sm ${italic ? "bg-[#00FF41] text-black" : "bg-white/10 text-[#E0E0E0]"}`}
49 onClick={() => setItalic(!italic)}
50 >
51 I
52 </button>
53 <button
54 aria-pressed={underline}
55 tabIndex={underline ? 0 : -1}
56 className={`px-2 py-1 rounded text-sm ${underline ? "bg-[#00FF41] text-black" : "bg-white/10 text-[#E0E0E0]"}`}
57 onClick={() => setUnderline(!underline)}
58 >
59 U
60 </button>
61 <div role="separator" className="w-px h-5 bg-[#333] mx-1" />
62 <button tabIndex={-1} className="px-2 py-1 rounded text-sm bg-white/10 text-[#E0E0E0]">Link</button>
63 </div>
64 );
65}

info

Use aria-pressed for toggle buttons and role="toolbar" for the container. Arrow keys should move focus between buttons (roving tabindex pattern).
Overflow Menu
ToolbarOverflow.tsx
TypeScript
1// Toolbar overflow: move hidden items to a "..." menu
2"use client";
3import { useState, useRef, useEffect } from "react";
4
5function ToolbarWithOverflow() {
6 const [showOverflow, setShowOverflow] = useState(false);
7 const overflowRef = useRef<HTMLDivElement>(null);
8
9 useEffect(() => {
10 if (!showOverflow) return;
11 const handleClick = (e: MouseEvent) => {
12 if (overflowRef.current && !overflowRef.current.contains(e.target as Node)) {
13 setShowOverflow(false);
14 }
15 };
16 document.addEventListener("mousedown", handleClick);
17 return () => document.removeEventListener("mousedown", handleClick);
18 }, [showOverflow]);
19
20 return (
21 <div role="toolbar" aria-label="Formatting" className="flex items-center gap-1 p-2">
22 <button className="px-2 py-1 rounded bg-white/10 text-[#E0E0E0]">Bold</button>
23 <button className="px-2 py-1 rounded bg-white/10 text-[#E0E0E0]">Italic</button>
24 <div className="relative" ref={overflowRef}>
25 <button
26 aria-haspopup="true"
27 aria-expanded={showOverflow}
28 onClick={() => setShowOverflow(!showOverflow)}
29 className="px-2 py-1 rounded bg-white/10 text-[#E0E0E0]"
30 >
31 ...
32 </button>
33 {showOverflow && (
34 <div className="absolute top-full mt-1 right-0 bg-[#1a1a28] border border-[#333] rounded-lg shadow-xl py-1 z-50">
35 <button className="block w-full px-3 py-2 text-left text-sm text-[#E0E0E0] hover:bg-white/10">Underline</button>
36 <button className="block w-full px-3 py-2 text-left text-sm text-[#E0E0E0] hover:bg-white/10">Strikethrough</button>
37 <button className="block w-full px-3 py-2 text-left text-sm text-[#E0E0E0] hover:bg-white/10">Code</button>
38 </div>
39 )}
40 </div>
41 </div>
42 );
43}
$Blueprint — Engineering Documentation·Section ID: UI-TB-01·Revision: 1.0