AvesraAvesrabeta

ToggleButtonGroup

Groups multiple ToggleButtons into a unified control, allowing users to select one or multiple options.

Import

import { AvToggleButtonGroupImports } from '@avesra/angular';

Usage

import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-basic-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<av-toggle-button-group selection-mode="multiple">
  <button av-toggle-button icon-only value="bold" aria-label="Bold">
    <app-icon icon="solar:text-bold-linear" size="16" />
  </button>
  <button av-toggle-button icon-only value="italic" aria-label="Italic">
    <span av-toggle-button-group-separator></span>
    <app-icon icon="solar:text-italic-linear" size="16" />
  </button>
  <button av-toggle-button icon-only value="underline" aria-label="Underline">
    <span av-toggle-button-group-separator></span>
    <app-icon icon="solar:text-underline-linear" size="16" />
  </button>
  <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
    <span av-toggle-button-group-separator></span>
    <app-icon icon="solar:text-cross-linear" size="16" />
  </button>
</av-toggle-button-group>`,
})
export class ToggleButtonGroupBasicDemo {}

Anatomy

Wrap button[av-toggle-button] elements in av-toggle-button-group. Each button needs a unique value. Add span[av-toggle-button-group-separator] inside each button except the first to show dividers when attached.

<av-toggle-button-group selection-mode="multiple">
  <button av-toggle-button value="first">First</button>
  <button av-toggle-button value="second">
    <span av-toggle-button-group-separator></span>
    Second
  </button>
  <button av-toggle-button value="third">
    <span av-toggle-button-group-separator></span>
    Third
  </button>
</av-toggle-button-group>

Sizes

Set size on the group to propagate a shared size to all child toggle buttons.

Small
Medium (default)
Large
// See component preview for full template

Orientation

Use orientation="horizontal" (default) or orientation="vertical" to change the layout direction.

Horizontal
Vertical
import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-orientation-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<div class="flex items-start gap-8">
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Horizontal</span>
    <av-toggle-button-group orientation="horizontal" selection-mode="multiple">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Vertical</span>
    <av-toggle-button-group orientation="vertical" selection-mode="multiple">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
</div>`,
})
export class ToggleButtonGroupOrientationDemo {}

Full Width

Use full-width to expand the group and distribute buttons evenly.

import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-full-width-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<div class="w-full max-w-md space-y-3">
  <av-toggle-button-group full-width selection-mode="multiple">
    <button av-toggle-button icon-only value="bold" aria-label="Bold">
      <app-icon icon="solar:text-bold-linear" size="16" />
    </button>
    <button av-toggle-button icon-only value="italic" aria-label="Italic">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:text-italic-linear" size="16" />
    </button>
    <button av-toggle-button icon-only value="underline" aria-label="Underline">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:text-underline-linear" size="16" />
    </button>
    <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:text-cross-linear" size="16" />
    </button>
  </av-toggle-button-group>
  <av-toggle-button-group full-width selection-mode="single">
    <button av-toggle-button value="left">
      <app-icon icon="solar:align-left-linear" size="16" />
      Left
    </button>
    <button av-toggle-button value="top">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:align-top-linear" size="16" />
      Top
    </button>
    <button av-toggle-button value="right">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:align-right-linear" size="16" />
      Right
    </button>
    <button av-toggle-button value="bottom">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:align-bottom-linear" size="16" />
      Bottom
    </button>
  </av-toggle-button-group>
</div>`,
})
export class ToggleButtonGroupFullWidthDemo {}

Disabled

Set disabled on the group to disable all child toggle buttons.

All buttons disabled
Individual button disabled
import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-disabled-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<div class="flex flex-col gap-6">
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">All buttons disabled</span>
    <av-toggle-button-group selection-mode="multiple" [disabled]="true">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Individual button disabled</span>
    <av-toggle-button-group selection-mode="multiple">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic" [disabled]="true">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
</div>`,
})
export class ToggleButtonGroupDisabledDemo {}

Without Separator

Simply omit span[av-toggle-button-group-separator] from your buttons.

import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-without-separator-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<av-toggle-button-group selection-mode="multiple">
  <button av-toggle-button icon-only value="bold" aria-label="Bold">
    <app-icon icon="solar:text-bold-linear" size="16" />
  </button>
  <button av-toggle-button icon-only value="italic" aria-label="Italic">
    <app-icon icon="solar:text-italic-linear" size="16" />
  </button>
  <button av-toggle-button icon-only value="underline" aria-label="Underline">
    <app-icon icon="solar:text-underline-linear" size="16" />
  </button>
  <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
    <app-icon icon="solar:text-cross-linear" size="16" />
  </button>
</av-toggle-button-group>`,
})
export class ToggleButtonGroupWithoutSeparatorDemo {}

Detached

Use [detached]="true" to separate buttons with gaps instead of connecting them.

Attached (default)
Detached
import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-attached-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<div class="flex flex-col gap-6">
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Attached (default)</span>
    <av-toggle-button-group selection-mode="multiple">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-cross-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Detached</span>
    <av-toggle-button-group selection-mode="multiple" [detached]="true">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic">
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
        <app-icon icon="solar:text-cross-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
</div>`,
})
export class ToggleButtonGroupAttachedDemo {}

Selection Mode

Use selection-mode="single" for mutually exclusive choices or selection-mode="multiple" for independent toggles.

Single selection
Multiple selection
import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-selection-mode-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `<div class="flex flex-col gap-6">
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Single selection</span>
    <av-toggle-button-group selection-mode="single" [default-selected-keys]="['top']">
      <button av-toggle-button value="left">
        <app-icon icon="solar:align-left-linear" size="16" />
        Left
      </button>
      <button av-toggle-button value="top">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:align-top-linear" size="16" />
        Top
      </button>
      <button av-toggle-button value="right">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:align-right-linear" size="16" />
        Right
      </button>
      <button av-toggle-button value="bottom">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:align-bottom-linear" size="16" />
        Bottom
      </button>
    </av-toggle-button-group>
  </div>
  <div class="flex flex-col gap-2">
    <span class="text-sm text-muted">Multiple selection</span>
    <av-toggle-button-group selection-mode="multiple" [default-selected-keys]="['bold', 'underline']">
      <button av-toggle-button icon-only value="bold" aria-label="Bold">
        <app-icon icon="solar:text-bold-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="italic" aria-label="Italic">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-italic-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="underline" aria-label="Underline">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-underline-linear" size="16" />
      </button>
      <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
        <span av-toggle-button-group-separator></span>
        <app-icon icon="solar:text-cross-linear" size="16" />
      </button>
    </av-toggle-button-group>
  </div>
</div>`,
})
export class ToggleButtonGroupSelectionModeDemo {}

Controlled

Bind [(selectedKeys)] for two-way control over the selected button values.

Selected: bold

import { Component, computed, signal } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';

@Component({
  selector: 'app-toggle-button-group-controlled-demo',
  imports: [AvToggleButtonGroupImports],
  template: `<div class="flex flex-col gap-4">
  <av-toggle-button-group selection-mode="multiple" [(selectedKeys)]="selectedKeys">
    <button av-toggle-button icon-only value="bold" aria-label="Bold">
      <app-icon icon="solar:text-bold-linear" size="16" />
    </button>
    <button av-toggle-button icon-only value="italic" aria-label="Italic">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:text-italic-linear" size="16" />
    </button>
    <button av-toggle-button icon-only value="underline" aria-label="Underline">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:text-underline-linear" size="16" />
    </button>
    <button av-toggle-button icon-only value="strikethrough" aria-label="Strikethrough">
      <span av-toggle-button-group-separator></span>
      <app-icon icon="solar:text-cross-linear" size="16" />
    </button>
  </av-toggle-button-group>
  <p class="text-sm text-muted">
    Selected:
    <span class="font-medium">{{ selectedLabel() }}</span>
  </p>
</div>`,
})
export class ToggleButtonGroupControlledDemo {
  readonly selectedKeys = signal(['bold']);
  readonly selectedLabel = computed(() =>
    this.selectedKeys().length > 0 ? this.selectedKeys().join(', ') : 'None',
  );
}

Disallow Empty Selection

Set disallow-empty-selection with selection-mode="single" to ensure one option is always selected.

Selected: center

// See component preview for full template

Customization

Tailwind CSS

import { Component } from '@angular/core';
import { AvToggleButtonGroupImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';

@Component({
  selector: 'app-toggle-button-group-custom-styles-demo',
  imports: [AvToggleButtonGroupImports, AppIconComponent],
  template: `
<av-toggle-button-group
  class="gap-1 rounded-xl border border-border/80 bg-surface p-1 shadow-sm"
  selection-mode="multiple"
  aria-label="Text formatting"
>
  <button
    av-toggle-button
    icon-only
    class="rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground"
    value="bold"
    aria-label="Bold"
  >
    <app-icon icon="solar:text-bold-linear" size="16" />
  </button>
  <button
    av-toggle-button
    icon-only
    class="rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground"
    value="italic"
    aria-label="Italic"
  >
    <app-icon icon="solar:text-italic-linear" size="16" />
  </button>
  <button
    av-toggle-button
    icon-only
    class="rounded-lg text-muted data-[selected=true]:bg-accent-soft data-[selected=true]:text-accent-soft-foreground"
    value="underline"
    aria-label="Underline"
  >
    <app-icon icon="solar:text-underline-linear" size="16" />
  </button>
</av-toggle-button-group>
`,
})
export class ToggleButtonGroupCustomStylesDemo {}

Global CSS

To customize ToggleButtonGroup classes, use the @layer components directive.

@layer components {
  .av-toggle-button-group {
    @apply rounded-lg;
  }

  .av-toggle-button-group__separator {
    @apply opacity-25;
  }

  .av-toggle-button-group--full-width {
    @apply w-full;
  }
}

Styling Reference

Avesra follows the BEM methodology so variants and states stay reusable and easy to customize.

CSS Classes

Base & Layout Classes

  • .av-toggle-button-group — Base container styles
  • .av-toggle-button-group--horizontal — Horizontal orientation
  • .av-toggle-button-group--vertical — Vertical orientation
  • .av-toggle-button-group--full-width — Full width modifier
  • .av-toggle-button-group__separator — Separator between attached buttons

Modifier Classes

  • .av-toggle-button-group--detached — Detached mode (separated buttons with gaps)

API Reference

ToggleButtonGroup

PropTypeDefaultDescription
selection-mode'single' | 'multiple''single'Whether one or multiple buttons can be selected.
selectedKeysstring[]—Controlled selection state. Supports two-way binding with [(selectedKeys)].
default-selected-keysstring[][]Default selected keys (uncontrolled).
disallow-empty-selectionbooleanfalsePrevents clearing all selections.
orientation'horizontal' | 'vertical''horizontal'Layout direction.
size'sm' | 'md' | 'lg''md'Size propagated to child ToggleButtons.
detachedbooleanfalseWhether buttons are visually separated with gaps.
full-widthbooleanfalseWhether the group fills available width.
disabledbooleanfalseDisables all buttons in the group.

ToggleButtonGroup.Separator

PropTypeDefaultDescription
(host)span[av-toggle-button-group-separator]—Decorative divider. Place inside each toggle button except the first. Hidden automatically in detached mode.

Notes

  • The group uses context to pass size and disabled to child av-toggle-button components
  • Each toggle button must have a unique value that corresponds to keys in selectedKeys / default-selected-keys
  • Group disabled disables all children — an individual button can still set its own disabled
  • Attached mode automatically handles border radius between buttons
  • Add span[av-toggle-button-group-separator] inside each button except the first to show dividers
  • Use disallow-empty-selection with selection-mode="single" to ensure one option is always selected

Made with ❤ by SyntaxHertz. Open source and available on GitHub.