Toggle button

A toggle button is on or off, and says so with aria-pressed. Bold in a text editor, a filter that is applied, a panel that is showing.

There is no hidden input and no script behind it: on a static page a toggle button shows a state rather than changing one. Add your own listener, or reach for checkbox() when it belongs in a form and toggle() — the switch — when it is a setting in a list.

Basic toggle

stack({ direction: 'row', gap: 'sm' },
  toggleButton({ pressed: true }, 'Bold'),
  toggleButton('Italic'),
  toggleButton('Underline'),
)

Variants

stack({ gap: 'md' },
  stack({ direction: 'row', gap: 'sm' },
    toggleButton({ variant: 'outline', pressed: true }, 'Outline on'),
    toggleButton({ variant: 'outline' }, 'Outline off'),
  ),
  stack({ direction: 'row', gap: 'sm' },
    toggleButton({ variant: 'ghost', pressed: true }, 'Ghost on'),
    toggleButton({ variant: 'ghost' }, 'Ghost off'),
  ),
  stack({ direction: 'row', gap: 'sm' },
    toggleButton({ variant: 'soft', pressed: true }, 'Soft on'),
    toggleButton({ variant: 'soft' }, 'Soft off'),
  ),
)

Sizes

stack({ direction: 'row', gap: 'sm', align: 'center' },
  toggleButton({ size: 'sm', pressed: true }, 'Small'),
  toggleButton({ size: 'md', pressed: true }, 'Medium'),
  toggleButton({ size: 'lg', pressed: true }, 'Large'),
)

With icons

An icon-only toggle needs an accessible name — pass aria-label, which falls through to the button.

stack({ direction: 'row', gap: 'sm' },
  toggleButton({
    pressed: true,
    'aria-label': 'Bold',
    title: 'Bold',
    startIcon: icon('bold'),
  }),
  toggleButton({
    'aria-label': 'Italic',
    title: 'Italic',
    startIcon: icon('italic'),
  }),
)

Disabled

stack({ direction: 'row', gap: 'sm' },
  toggleButton({ pressed: true, disabled: true }, 'On, locked'),
  toggleButton({ disabled: true }, 'Off, locked'),
)

Making it do something

One listener flips the attribute; the styling follows it.

for (const button of document.querySelectorAll('[aria-pressed]')) {
  button.addEventListener('click', () => {
    const on = button.getAttribute('aria-pressed') === 'true'
    button.setAttribute('aria-pressed', String(!on))
  })
}

Props

PropTypeDefaultDescription
pressedbooleanfalseSets aria-pressed. There is no script behind it.
variant'outline' | 'ghost' | 'soft''outline'How the unpressed button looks.
size'sm' | 'md' | 'lg''md'Same scale as button().
disabledbooleanfalseDisables the button.

Everything else falls through to button()startIcon, endIcon, onclick and the rest. For a set of them, see toggleGroup().