GitHub

ToggleButton

A toggle button allows to toggle a selection on or off, for example switching between two states or modes.

Unselected, it reads as an outlined button — ink and border only. Selected, it fills with its intent color and switches to the matching contrast ink; hovering and pressing deepen that fill one step at a time, the same ramp a filled Button uses, so the label stays readable in every state.

Import

import { ToggleButton } from 'frontile';

Usage

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { ToggleButton } from 'frontile';
import { StarIcon } from 'site/components/icons';

export default class Example extends Component {
  @tracked
  isSelected = false;

  @action
  onChange(value: boolean): void {
    this.isSelected = value;
  }

  <template>
    <ToggleButton
      @isSelected={{this.isSelected}}
      @onChange={{this.onChange}}
      aria-label='Favourite'
    >
      <StarIcon />
    </ToggleButton>
  </template>
}

Intents

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { fn } from '@ember/helper';
import { ToggleButton } from 'frontile';

export default class Example extends Component {
  @tracked
  isSelected = {
    default: false,
    primary: false,
    secondary: false,
    tertiary: false,
    success: false,
    warning: false,
    danger: false
  };

  @action
  onChange(ty: keyof typeof this.isSelected, value: boolean): void {
    this.isSelected[ty] = value;
    this.isSelected = { ...this.isSelected };
  }

  <template>
    {{#each-in this.isSelected as |key val|}}
      <ToggleButton
        @isSelected={{val}}
        @onChange={{(fn this.onChange key)}}
        @intent={{key}}
      >
        Toggle
      </ToggleButton>
    {{/each-in}}
  </template>
}

ToggleButton Sizes

import { ToggleButton } from 'frontile';

<template>
  <ToggleButton @size='xs'>ToggleButton</ToggleButton>
  <ToggleButton @size='sm'>ToggleButton</ToggleButton>
  <ToggleButton @size='md'>ToggleButton</ToggleButton>
  <ToggleButton @size='lg'>ToggleButton</ToggleButton>
  <ToggleButton @size='xl'>ToggleButton</ToggleButton>
</template>

Disabled

You can pass the attribute disabled to disable a toggle button.

import { ToggleButton } from 'frontile';

<template>
  <div>
    <ToggleButton @intent='default' disabled>ToggleButton</ToggleButton>
    <ToggleButton @intent='primary' disabled>Primary</ToggleButton>
    <ToggleButton @intent='secondary' disabled>Secondary</ToggleButton>
    <ToggleButton @intent='tertiary' disabled>Tertiary</ToggleButton>
    <ToggleButton @intent='success' disabled>Success</ToggleButton>
    <ToggleButton @intent='warning' disabled>Warning</ToggleButton>
    <ToggleButton @intent='danger' disabled>Danger</ToggleButton>
  </div>
</template>

Accessibility

ToggleButton renders a native <button> carrying aria-pressed, which is what turns it from a button into a toggle: assistive technology announces the label followed by its state, and announces the change when it flips.

Key Behaviour
Tab Moves focus to the toggle. Disabled toggles are skipped.
Enter Toggles it, firing @onChange with the new value.
Space Toggles it, firing @onChange with the new value.

aria-pressed tracks @isSelected, which the component does not own. Without an @onChange that writes the new value back, the toggle looks pressed to the eye for as long as the pointer is down but never reports a state change — the demos under Sizes and Disabled are deliberately inert for that reason, and are not the pattern to copy.

An icon with no text leaves the toggle unnamed, so the state is announced with nothing to attach it to. Pass aria-label, as the Usage demo does.

The label should name the control, not the action — "Bold", not "Make bold" — because aria-pressed already conveys whether it is on. A label that changes with the state ("Mute" / "Unmute") duplicates that and contradicts it half the time; if you want changing labels, use a plain Button and no aria-pressed.

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { fn } from '@ember/helper';
import { ToggleButton } from 'frontile';

export default class Example extends Component {
  @tracked format: Record<string, boolean> = {
    Bold: false,
    Italic: false,
    Underline: false
  };

  onChange = (key: string, value: boolean): void => {
    this.format = { ...this.format, [key]: value };
  };

  <template>
    <div class='flex items-center gap-2'>
      {{#each-in this.format as |key selected|}}
        <ToggleButton
          @isSelected={{selected}}
          @onChange={{fn this.onChange key}}
          @intent='primary'
        >
          {{key}}
        </ToggleButton>
      {{/each-in}}
    </div>
  </template>
}

Use the plain HTML disabled attribute to disable a toggle; there is no @isDisabled argument.

API

ToggleButton

Element: <span class="hljs-title class_">HTMLButtonElement</span>

Arguments

Name Type Default Description
class string - Custom class name, it will override the default ones using Tailwind Merge library.
intent enum - The intent of the button
isInGroup boolean - If button is part of a group. Most of the time, this is automatically set when using the ButtonGroup component.
isSelected boolean false If the button is currently selected
onChange function - Callback when the buttle is toggled
size enum - The size of the button

Blocks

Name Type Default Description
default * Array -
Released under MIT License - Created by Josemar Luedke