Skip to content

ButtonGroup

import { ButtonGroup, Button } from '@delightstack/components';
View code
<ButtonGroup aria-label="Document actions">
<Button>Save</Button>
<Button>Save As</Button>
<Button>Discard</Button>
</ButtonGroup>

Use the active prop on child Buttons to create a toggle group.

Icon buttons rarely speak for themselves, so pass a tooltip to each one. Hover a button and its tooltip appears after a short delay — then slide across to the next button and its tooltip shows instantly, no waiting. The skip-delay window is shared by every tooltip on the page and persists briefly after you leave, so the buttons don’t even need to touch.

View code
<script>
import { ButtonGroup, Button } from '@delightstack/components';
let viewMode = $state('grid');
</script>
<ButtonGroup size="0" aria-label="View mode">
<Button
active={viewMode === 'grid'}
onclick={() => viewMode = 'grid'}
icon
tooltip="Grid view"
aria-label="Grid view">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M3 3h8v8H3V3zm0 10h8v8H3v-8zm10-10h8v8h-8V3zm0 10h8v8h-8v-8z"/>
</svg>
</Button>
<Button
active={viewMode === 'list'}
onclick={() => viewMode = 'list'}
icon
tooltip="List view"
aria-label="List view">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/>
</svg>
</Button>
</ButtonGroup>

Set variant props on the group to apply them to all children at once.

View code
<ButtonGroup outline accent aria-label="Accent group">
<Button>First</Button>
<Button>Second</Button>
<Button>Third</Button>
</ButtonGroup>

Stack buttons vertically instead of horizontally.

View code
<ButtonGroup orientation="vertical" aria-label="Options">
<Button>Option 1</Button>
<Button>Option 2</Button>
<Button>Option 3</Button>
</ButtonGroup>

Combine a primary action button with a dropdown trigger inside a group.

<script>
import { ButtonGroup, Button } from '@delightstack/components';
</script>
<ButtonGroup aria-label="Save options">
<Button onclick={primarySave}>Save</Button>
<Button icon aria-label="More save options">
<!-- ChevronDownIcon -->
{#snippet menu({ close })}
<button onclick={() => { saveAs(); close(); }}>Save As...</button>
<button onclick={() => { saveCopy(); close(); }}>Save Copy</button>
{/snippet}
</Button>
</ButtonGroup>

Set attached={false} to add a small gap between buttons instead of merging borders.

View code
<ButtonGroup attached={false} aria-label="Spaced actions">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
</ButtonGroup>
PropTypeDefaultDescription
size'0000' | '000' | '00' | '0' | '1' | '2' | '3' | '4' | '5' | '6'undefinedSize applied to all child Buttons via context
outlinebooleanfalseApply outline variant to all children
transparentbooleanfalseApply transparent variant to all children
translucentbooleanfalseApply translucent variant to all children
accentbooleanfalseApply accent color to all children
errorbooleanfalseApply error color to all children
successbooleanfalseApply success color to all children
orientation'horizontal' | 'vertical''horizontal'Layout direction
disabledbooleanfalseDisable all child Buttons via context
attachedbooleantrueVisually connect buttons (shared borders, merged radii)
idstringauto-generatedElement ID
classstring''Additional CSS classes
childrenSnippetundefinedChild Button components

The ButtonGroup exports a ButtonGroupContext interface used for its context contract with child Buttons:

interface ButtonGroupContext {
size:
| undefined
| '0000' | '000' | '00' | '0'
| '1' | '2' | '3' | '4' | '5' | '6';
outline: boolean;
transparent: boolean;
translucent: boolean;
accent: boolean;
error: boolean;
success: boolean;
disabled: boolean;
}

Child Buttons read this context via getContext('button-group') and merge the group values with their own props (own props take precedence when explicitly set).

  • role="group" on the container element
  • Provide an aria-label to describe the group’s purpose
  • Buttons remain individually focusable
  • Hovered and focused buttons are raised above siblings via z-index for clean border visibility