Toolbar
Disclosure & LayoutLocal guide ↗
import { Toolbar } from "bits-ui" Editing toolbar
Independent multiple- and single-value groups alongside a button and link
marks=[bold] · align=left · Ready
<script lang="ts">
import { Separator, Toolbar } from 'bits-ui';
import { AlignCenter, AlignLeft, AlignRight, Bold, ExternalLink, Italic, Sparkles, Underline } from '@lucide/svelte';
let marks = $state<string[]>(['bold']);
let alignment = $state('left');
let action = $state('Ready');
const itemClass = 'inline-flex size-9 items-center justify-center rounded-md text-ink-400 hover:bg-ink-800 hover:text-ink-50 data-[state=on]:bg-brand-600 data-[state=on]:text-white';
</script>
<div class="grid justify-items-center gap-3">
<Toolbar.Root class="flex max-w-full items-center gap-1 overflow-x-auto rounded-xl border border-ink-700 bg-ink-900 p-1.5">
<Toolbar.Group type="multiple" bind:value={marks} class="flex items-center gap-1" aria-label="Text styles">
<Toolbar.GroupItem value="bold" aria-label="Bold" class={itemClass}><Bold class="size-4" /></Toolbar.GroupItem>
<Toolbar.GroupItem value="italic" aria-label="Italic" class={itemClass}><Italic class="size-4" /></Toolbar.GroupItem>
<Toolbar.GroupItem value="underline" aria-label="Underline" class={itemClass}><Underline class="size-4" /></Toolbar.GroupItem>
</Toolbar.Group>
<Separator.Root orientation="vertical" decorative class="mx-1 h-6 w-px bg-ink-700" />
<Toolbar.Group type="single" bind:value={alignment} class="flex items-center gap-1" aria-label="Text alignment">
<Toolbar.GroupItem value="left" aria-label="Align left" class={itemClass}><AlignLeft class="size-4" /></Toolbar.GroupItem>
<Toolbar.GroupItem value="center" aria-label="Align center" class={itemClass}><AlignCenter class="size-4" /></Toolbar.GroupItem>
<Toolbar.GroupItem value="right" aria-label="Align right" class={itemClass}><AlignRight class="size-4" /></Toolbar.GroupItem>
</Toolbar.Group>
<Separator.Root orientation="vertical" decorative class="mx-1 h-6 w-px bg-ink-700" />
<Toolbar.Button class="btn btn-sm" onclick={() => (action = 'Draft improved')}><Sparkles class="size-4" /> Improve</Toolbar.Button>
<Toolbar.Link class="btn btn-ghost btn-sm" href="/components"><ExternalLink class="size-4" /> Docs</Toolbar.Link>
</Toolbar.Root>
<p class="font-mono text-xs text-ink-400">marks=[{marks.join(', ')}] · align={alignment} · {action}</p>
</div>
Vertical controls
A non-looping vertical toolbar with controlled view selection
preview view
No history action
vertical · loop=false
<script lang="ts">
import { Toolbar } from 'bits-ui';
import { Code2, Eye, PanelLeft, Redo2, Undo2 } from '@lucide/svelte';
let view = $state('preview');
let lastAction = $state('No history action');
const control = 'inline-flex size-10 items-center justify-center rounded-lg text-ink-400 hover:bg-ink-800 hover:text-ink-50 data-[state=on]:bg-ink-700 data-[state=on]:text-brand-400';
</script>
<div class="flex items-center justify-center gap-5">
<Toolbar.Root orientation="vertical" loop={false} class="grid gap-1 rounded-xl border border-ink-700 bg-ink-900 p-1.5">
<Toolbar.Button class={control} aria-label="Undo" onclick={() => (lastAction = 'Undo')}><Undo2 class="size-4" /></Toolbar.Button>
<Toolbar.Button class={control} aria-label="Redo" onclick={() => (lastAction = 'Redo')}><Redo2 class="size-4" /></Toolbar.Button>
<div class="my-1 h-px bg-ink-700"></div>
<Toolbar.Group type="single" bind:value={view} class="grid gap-1" aria-label="Editor view">
<Toolbar.GroupItem value="source" aria-label="Source view" class={control}><Code2 class="size-4" /></Toolbar.GroupItem>
<Toolbar.GroupItem value="split" aria-label="Split view" class={control}><PanelLeft class="size-4" /></Toolbar.GroupItem>
<Toolbar.GroupItem value="preview" aria-label="Preview" class={control}><Eye class="size-4" /></Toolbar.GroupItem>
</Toolbar.Group>
</Toolbar.Root>
<div class="panel min-w-48 p-4">
<p class="text-sm font-medium capitalize">{view} view</p>
<p class="muted mt-1 text-xs">{lastAction}</p>
<p class="mt-4 font-mono text-xs text-ink-600">vertical · loop=false</p>
</div>
</div>
Toolbar.Group requires type: single groups bind a string, while multiple groups bind a string array. Every GroupItem also requires the string value contributed to that state.
The root defaults to horizontal orientation and looping keyboard navigation. Set orientation="vertical" for a column and loop={false} when focus should stop at the first and last controls. Use Button for actions and Link for navigation so each retains its native element semantics.