Toolbar
A row of buttons, toggles and links that acts as a single tab stop with arrow-key navigation — and zero visual opinion.
Formatting toolbar
BitBhToolbar renders role="toolbar" as a single tab stop: Tab moves in and out, ← / →
travel through every control — toggle-group items included — and Home / End jump to the ends. A multiple
BitBhToggleGroup (bold / italic / strikethrough, aria-pressed) and a single one
(alignment, role="radio") share the toolbar's arrow-key navigation with a
BitBhToolbarLink and a BitBhToolbarButton. Name the toolbar with aria-label.
The quick brown fox jumps over the lazy dog.
Style: bold · Align: left · Last action: (none)<BitBhToolbar aria-label="Formatting options" class="flex w-full max-w-xl flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-white p-1 shadow-sm">
<BitBhToggleGroup Type="BitBhToggleGroupType.Multiple" @bind-Values="formats"
aria-label="Text style" class="flex gap-0.5">
<BitBhToggleGroupItem Value="bold" aria-label="Bold" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700"><strong>B</strong></BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="italic" aria-label="Italic" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700"><em>I</em></BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="strike" aria-label="Strikethrough" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700"><s>S</s></BitBhToggleGroupItem>
</BitBhToggleGroup>
<BitBhToolbarSeparator class="shrink-0 self-stretch bg-zinc-200 data-orientation-vertical:mx-1 data-orientation-vertical:my-1.5 data-orientation-vertical:w-px data-orientation-horizontal:mx-1.5 data-orientation-horizontal:my-1 data-orientation-horizontal:h-px" />
<BitBhToggleGroup @bind-Value="alignment" AllowEmpty="false"
aria-label="Text alignment" class="flex gap-0.5">
<BitBhToggleGroupItem Value="left" aria-label="Align left" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 3.5h12M2 6.5h8M2 9.5h12M2 12.5h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="center" aria-label="Align center" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 3.5h12M4 6.5h8M2 9.5h12M4 12.5h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="right" aria-label="Align right" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M2 3.5h12M6 6.5h8M2 9.5h12M6 12.5h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</BitBhToggleGroupItem>
</BitBhToggleGroup>
<BitBhToolbarLink href="https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/" target="_blank" rel="noopener"
class="ml-auto whitespace-nowrap rounded px-2 py-1 text-xs text-zinc-500 no-underline hover:text-blue-600 hover:underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600">
Toolbar pattern ↗
</BitBhToolbarLink>
<BitBhToolbarButton OnClick='@(() => Log("Shared"))' class="inline-flex h-8 cursor-pointer items-center rounded-md bg-blue-600 px-3 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">Share</BitBhToolbarButton>
</BitBhToolbar>
<p class="w-full max-w-xl rounded-lg border border-dashed border-zinc-300 px-4 py-3 text-[0.95rem] text-zinc-700" style="@PreviewStyle">
The quick brown fox jumps over the lazy dog.
</p>
<span class="text-sm text-zinc-500">
Style: <strong class="text-zinc-900">@(formats.Count == 0 ? "none" : string.Join(", ", formats))</strong> ·
Align: <strong class="text-zinc-900">@alignment</strong> ·
Last action: <strong class="text-zinc-900">@(lastAction ?? "(none)")</strong>
</span>
@code {
private IReadOnlyCollection<string> formats = ["bold"];
private string? alignment = "left";
private string? lastAction;
private void Log(string action) => lastAction = action;
private string PreviewStyle =>
$"text-align: {alignment ?? "left"};"
+ (formats.Contains("bold") ? " font-weight: 600;" : "")
+ (formats.Contains("italic") ? " font-style: italic;" : "")
+ (formats.Contains("strike") ? " text-decoration: line-through;" : "");
}Disabled buttons stay focusable
Following the APG, a Disabled BitBhToolbarButton gets aria-disabled="true"
and data-disabled instead of the disabled attribute: arrow keys still land on it, so
keyboard and screen-reader users can discover it, but clicks are ignored. Paste and Clear stay reachable while
the clipboard is empty — cut or copy to enable them.
<BitBhToolbar aria-label="Clipboard" class="flex w-full max-w-xl flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-white p-1 shadow-sm">
<BitBhToolbarButton OnClick="Cut" class="inline-flex h-8 min-w-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent">✂️ Cut</BitBhToolbarButton>
<BitBhToolbarButton OnClick="Copy" class="inline-flex h-8 min-w-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent">📋 Copy</BitBhToolbarButton>
<BitBhToolbarButton OnClick="Paste" Disabled="@(clipboard is null)" class="inline-flex h-8 min-w-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent">📥 Paste</BitBhToolbarButton>
<BitBhToolbarSeparator class="shrink-0 self-stretch bg-zinc-200 data-orientation-vertical:mx-1 data-orientation-vertical:my-1.5 data-orientation-vertical:w-px data-orientation-horizontal:mx-1.5 data-orientation-horizontal:my-1 data-orientation-horizontal:h-px" />
<BitBhToolbarButton OnClick="ClearClipboard" Disabled="@(clipboard is null)" class="inline-flex h-8 min-w-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent">Clear</BitBhToolbarButton>
</BitBhToolbar>
<span class="text-sm text-zinc-500">
Clipboard: <strong class="text-zinc-900">@(clipboard ?? "(empty)")</strong> · Pasted <strong class="text-zinc-900">@pasteCount</strong>×
</span>
@code {
private string? clipboard;
private int pasteCount;
private void Cut() => clipboard = "“Selected text” (cut)";
private void Copy() => clipboard = "“Selected text”";
private void Paste() => pasteCount++;
private void ClearClipboard() => clipboard = null;
}Vertical orientation
Orientation="BitBhRovingOrientation.Vertical" switches navigation to ↑ / ↓ and sets
aria-orientation and data-orientation on the toolbar; its separators flip to
horizontal. The tool picker is a single-select toggle group (AllowEmpty="false" keeps one tool
active), and Undo / Redo disable themselves — while staying focusable — when their stack is empty.
<div class="flex w-full max-w-xl items-start gap-3">
<BitBhToolbar Orientation="BitBhRovingOrientation.Vertical" aria-label="Drawing tools" class="inline-flex shrink-0 flex-col items-center gap-1 rounded-lg border border-zinc-200 bg-white p-1 shadow-sm">
<BitBhToggleGroup @bind-Value="tool" AllowEmpty="false" aria-label="Tool" class="flex flex-col gap-0.5">
<BitBhToggleGroupItem Value="Select" aria-label="Select" title="Select" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">↖</BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="Pen" aria-label="Pen" title="Pen" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">✎</BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="Shape" aria-label="Shape" title="Shape" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">▭</BitBhToggleGroupItem>
<BitBhToggleGroupItem Value="Text" aria-label="Text" title="Text" class="inline-flex size-8 cursor-pointer items-center justify-center rounded-md text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 data-[state=on]:bg-blue-50 data-[state=on]:text-blue-700">T</BitBhToggleGroupItem>
</BitBhToggleGroup>
<BitBhToolbarSeparator class="shrink-0 self-stretch bg-zinc-200 data-orientation-vertical:mx-1 data-orientation-vertical:my-1.5 data-orientation-vertical:w-px data-orientation-horizontal:mx-1.5 data-orientation-horizontal:my-1 data-orientation-horizontal:h-px" />
<BitBhToolbarButton OnClick="Undo" Disabled="@(undoDepth == 0)" aria-label="Undo" title="Undo"
class="inline-flex h-8 min-w-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent">↶</BitBhToolbarButton>
<BitBhToolbarButton OnClick="Redo" Disabled="@(redoDepth == 0)" aria-label="Redo" title="Redo"
class="inline-flex h-8 min-w-8 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 text-sm text-zinc-700 transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent">↷</BitBhToolbarButton>
</BitBhToolbar>
<div class="flex min-h-56 flex-1 flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-zinc-300 bg-zinc-50 bg-[radial-gradient(var(--color-zinc-200)_1px,transparent_1px)] bg-size-[16px_16px] text-xs text-zinc-500">
<strong class="text-lg text-zinc-900">@tool</strong>
<span>Undo stack: @undoDepth · Redo stack: @redoDepth</span>
</div>
</div>
@code {
private string? tool = "Pen";
private int undoDepth = 3;
private int redoDepth;
private void Undo() { undoDepth--; redoDepth++; }
private void Redo() { redoDepth--; undoDepth++; }
}