Toggle Group

A set of two-state buttons that can be toggled on or off.

Basic
Outline
Outline With Icons
Sizes
With Spacing
With Icons
Filter
Date Range
Sort
With Input and Select
Vertical
Vertical Outline
Vertical Outline With Icons
Vertical With Spacing
Font Weight Selector

Use font-normal to set the font weight.

Installation

Install the dependencies

pnpm add cn class-variance-authority github:sveltery/base

Copy the component

Copy the following files into src/lib/components/ui/toggle-group.

It imports the toggle component, so copy its folder too.

src/lib/components/ui/toggle-group/ToggleGroup.svelte
<script lang="ts">
  import { ToggleGroup as Primitive } from '@sveltery/base/toggle-group';
  import { cn } from 'cn';
  import { setToggleGroupContext } from './context.js';
  import type { ToggleGroupProps } from './types.js';
  let { value = $bindable(), class: classProp, variant, size, spacing = 2, orientation = 'horizontal', style, children, ...props }: ToggleGroupProps = $props();
  setToggleGroupContext({
    get variant() { return variant; },
    get size() { return size; },
    get spacing() { return spacing; },
    get orientation() { return orientation; },
  });
</script>
<Primitive
  data-slot="toggle-group"
  data-variant={variant}
  data-size={size}
  data-spacing={spacing}
  data-orientation={orientation}
  style="--gap: {spacing};{style ?? ''}"
  {orientation}
  bind:value
  {...props}
  class={cn('rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] data-vertical:flex-col data-vertical:items-stretch', classProp)}
>
  {@render children?.()}
</Primitive>
src/lib/components/ui/toggle-group/ToggleGroupItem.svelte
<script lang="ts">
  import { Toggle as Primitive } from '@sveltery/base/toggle';
  import { cn } from 'cn';
  import { toggleVariants } from '../toggle/index.js';
  import { getToggleGroupContext } from './context.js';
  import type { ToggleGroupItemProps } from './types.js';
  let { class: classProp, variant = 'default', size = 'default', ...props }: ToggleGroupItemProps = $props();
  const group = getToggleGroupContext();
  const resolvedVariant = $derived(group.variant || variant);
  const resolvedSize = $derived(group.size || size);
</script>
<Primitive
  data-slot="toggle-group-item"
  data-variant={resolvedVariant}
  data-size={resolvedSize}
  data-spacing={group.spacing}
  {...props}
  class={cn('group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t', toggleVariants({ variant: resolvedVariant, size: resolvedSize }), classProp)}
/>
src/lib/components/ui/toggle-group/context.ts
import { getContext, setContext } from 'svelte';
import type { ToggleVariants } from '../toggle/index.js';

export type ToggleGroupContextValue = {
  readonly variant: ToggleVariants['variant'];
  readonly size: ToggleVariants['size'];
  readonly spacing: number;
  readonly orientation: 'horizontal' | 'vertical';
};

const key = Symbol('toggle-group');
const outsideGroup: ToggleGroupContextValue = { variant: undefined, size: undefined, spacing: 2, orientation: 'horizontal' };

export const getToggleGroupContext = (): ToggleGroupContextValue => getContext(key) ?? outsideGroup;
export const setToggleGroupContext = (value: ToggleGroupContextValue) => setContext(key, value);
src/lib/components/ui/toggle-group/types.ts
import type { ToggleGroupProps as BaseToggleGroupProps } from '@sveltery/base/toggle-group';
import type { ToggleProps } from '../toggle/index.js';
import type { ToggleVariants } from '../toggle/index.js';
export type ToggleGroupProps = BaseToggleGroupProps & ToggleVariants & { spacing?: number };
export type ToggleGroupItemProps = ToggleProps;
export type { ToggleGroupState } from '@sveltery/base/toggle-group';
src/lib/components/ui/toggle-group/index.ts
export { default as ToggleGroup } from './ToggleGroup.svelte';
export { default as ToggleGroupItem } from './ToggleGroupItem.svelte';
export type { ToggleGroupProps, ToggleGroupItemProps, ToggleGroupState } from './types.js';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { ToggleGroup, ToggleGroupItem } from '$lib/components/ui/toggle-group';
</script>
<ToggleGroup multiple>
  <ToggleGroupItem value="bold" aria-label="Toggle bold">Bold</ToggleGroupItem>
  <ToggleGroupItem value="italic" aria-label="Toggle italic">Italic</ToggleGroupItem>
  <ToggleGroupItem value="underline" aria-label="Toggle underline">Underline</ToggleGroupItem>
</ToggleGroup>

Value

value is the array of pressed item values. Bind it to read and write the selection. Without multiple, pressing an item releases the others.

<script lang="ts">
  let weight = $state(['normal']);
</script>

<ToggleGroup bind:value={weight} variant="outline">
  <ToggleGroupItem value="light">Light</ToggleGroupItem>
  <ToggleGroupItem value="normal">Normal</ToggleGroupItem>
  <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
</ToggleGroup>

Variant and size

variant and size on the group apply to every item. Without them, each item uses its own.

<ToggleGroup variant="outline" size="sm" value={['all']}>
  <ToggleGroupItem value="all">All</ToggleGroupItem>
  <ToggleGroupItem value="missed">Missed</ToggleGroupItem>
</ToggleGroup>

Spacing

spacing is the gap between items in spacing units. The default is 2. With spacing={0}, outline items join into one bar.

<ToggleGroup variant="outline" spacing={0}>
  <ToggleGroupItem value="top">Top</ToggleGroupItem>
  <ToggleGroupItem value="bottom">Bottom</ToggleGroupItem>
</ToggleGroup>

Vertical

Arrow keys follow the orientation.

<ToggleGroup orientation="vertical" multiple spacing={1}>
  <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
  <ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>

API reference

ToggleGroup and ToggleGroupItem are styled on top of the Sveltery Base ToggleGroup and Toggle primitives, which own the pressed state, roving focus and accessibility. See the Base UI Toggle Group documentation for the behavior they port.