Toggle Group
Form ControlsLocal guide ↗
import { ToggleGroup } from "bits-ui" Single selection
type="single" models mutually exclusive alignment with a string value
Alignment: left
<script lang="ts">
import { ToggleGroup } from 'bits-ui';
import { AlignCenter, AlignLeft, AlignRight } from '@lucide/svelte';
let alignment = $state('left');
</script>
<div class="grid justify-items-center gap-4">
<ToggleGroup.Root
type="single"
bind:value={alignment}
aria-label="Text alignment"
class="panel flex w-fit items-center gap-1 p-1"
>
<ToggleGroup.Item
value="left"
aria-label="Align left"
class="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-brand-500 data-[state=on]:text-ink-950"
>
<AlignLeft class="size-5" />
</ToggleGroup.Item>
<ToggleGroup.Item
value="center"
aria-label="Align center"
class="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-brand-500 data-[state=on]:text-ink-950"
>
<AlignCenter class="size-5" />
</ToggleGroup.Item>
<ToggleGroup.Item
value="right"
aria-label="Align right"
class="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-brand-500 data-[state=on]:text-ink-950"
>
<AlignRight class="size-5" />
</ToggleGroup.Item>
</ToggleGroup.Root>
<p class="chip">Alignment: {alignment || 'none'}</p>
</div>
Multiple selection
type="multiple" keeps independent text formats in a string array
Ship precise interfaces, not piles of configuration.
Active: bold, underline
<script lang="ts">
import { ToggleGroup } from 'bits-ui';
import { Bold, Italic, Strikethrough, Underline } from '@lucide/svelte';
let formats = $state<string[]>(['bold', 'underline']);
const previewClass = $derived([
formats.includes('bold') ? 'font-bold' : '',
formats.includes('italic') ? 'italic' : '',
formats.includes('underline') ? 'underline' : '',
formats.includes('strike') ? 'line-through' : ''
].filter(Boolean).join(' '));
</script>
<div class="grid gap-4">
<ToggleGroup.Root
type="multiple"
bind:value={formats}
aria-label="Text formatting"
class="panel flex w-fit items-center gap-1 p-1"
>
<ToggleGroup.Item value="bold" aria-label="Bold" class="inline-flex size-10 items-center justify-center rounded-lg text-ink-400 hover:bg-ink-800 data-[state=on]:bg-brand-500 data-[state=on]:text-ink-950">
<Bold class="size-5" />
</ToggleGroup.Item>
<ToggleGroup.Item value="italic" aria-label="Italic" class="inline-flex size-10 items-center justify-center rounded-lg text-ink-400 hover:bg-ink-800 data-[state=on]:bg-brand-500 data-[state=on]:text-ink-950">
<Italic class="size-5" />
</ToggleGroup.Item>
<ToggleGroup.Item value="underline" aria-label="Underline" class="inline-flex size-10 items-center justify-center rounded-lg text-ink-400 hover:bg-ink-800 data-[state=on]:bg-brand-500 data-[state=on]:text-ink-950">
<Underline class="size-5" />
</ToggleGroup.Item>
<ToggleGroup.Item value="strike" aria-label="Strikethrough" class="inline-flex size-10 items-center justify-center rounded-lg text-ink-400 hover:bg-ink-800 data-[state=on]:bg-brand-500 data-[state=on]:text-ink-950">
<Strikethrough class="size-5" />
</ToggleGroup.Item>
</ToggleGroup.Root>
<div class="rounded-lg border border-ink-800 bg-ink-950/60 p-4 text-sm text-ink-200">
<p class={previewClass}>Ship precise interfaces, not piles of configuration.</p>
<p class="muted mt-2 text-xs">Active: {formats.length ? formats.join(', ') : 'none'}</p>
</div>
</div>
type is required and forms a discriminated union: "single" binds a string while "multiple" binds a string array. Each item exposes data-state="on|off" for pressed styling.
The root defaults to horizontal orientation, looping arrow navigation, and roving focus. Set orientation, loop, or rovingFocus to change those keyboard semantics. Toggle Group has no name or required props and does not create hidden form inputs; serialize its bound value separately when submitting a form.