AvesraAvesrabeta

ToggleButton

An interactive toggle control for on/off or selected/unselected states. Use standalone with [(selected)] or inside a ToggleButtonGroup with a value key.

Import

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

Usage

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

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

@Component({
  selector: 'app-toggle-button-basic-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<button av-toggle-button>
  <app-icon icon="solar:heart-linear" size="16" />
  Like
</button>`,
})
export class ToggleButtonBasicDemo {}

Anatomy

Apply av-toggle-button to a native <button>. Content is projected via ng-content.

<button av-toggle-button [(selected)]="isSelected">
  Toggle label
</button>

Variants

Two visual styles — default (filled) and ghost (transparent).

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

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

@Component({
  selector: 'app-toggle-button-variants-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<div class="flex items-center gap-3">
  <button av-toggle-button>
    <app-icon icon="solar:heart-linear" size="16" />
    Default
  </button>
  <button av-toggle-button variant="ghost">
    <app-icon icon="solar:heart-linear" size="16" />
    Ghost
  </button>
</div>`,
})
export class ToggleButtonVariantsDemo {}

Icon Only

Set icon-only for a square layout and always provide an aria-label for screen readers.

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

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

@Component({
  selector: 'app-toggle-button-icon-only-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<div class="flex items-center gap-3">
  <button av-toggle-button icon-only aria-label="Like">
    <app-icon icon="solar:heart-linear" size="16" />
  </button>
  <button av-toggle-button icon-only variant="ghost" aria-label="Bookmark">
    <app-icon icon="solar:bookmark-linear" size="16" />
  </button>
</div>`,
})
export class ToggleButtonIconOnlyDemo {}

Sizes

Three sizes — sm, md (default), and lg. Size inherits from the parent group when omitted.

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

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

@Component({
  selector: 'app-toggle-button-sizes-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<div class="flex flex-col gap-6">
  <div class="flex items-center gap-3">
    <button av-toggle-button size="sm">
      <app-icon icon="solar:heart-linear" size="16" />
      Small
    </button>
    <button av-toggle-button size="md">
      <app-icon icon="solar:heart-linear" size="16" />
      Medium
    </button>
    <button av-toggle-button size="lg">
      <app-icon icon="solar:heart-linear" size="16" />
      Large
    </button>
  </div>
  <div class="flex items-center gap-3">
    <button av-toggle-button icon-only size="sm" aria-label="Like">
      <app-icon icon="solar:heart-linear" size="16" />
    </button>
    <button av-toggle-button icon-only size="md" aria-label="Like">
      <app-icon icon="solar:heart-linear" size="16" />
    </button>
    <button av-toggle-button icon-only size="lg" aria-label="Like">
      <app-icon icon="solar:heart-linear" size="16" />
    </button>
  </div>
</div>`,
})
export class ToggleButtonSizesDemo {}

Disabled

Set disabled to prevent interaction. Disabled state also inherits from a parent toggle button group.

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

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

@Component({
  selector: 'app-toggle-button-disabled-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<div class="flex items-center gap-3">
  <button av-toggle-button [disabled]="true">
    <app-icon icon="solar:heart-linear" size="16" />
    Like
  </button>
  <button av-toggle-button [disabled]="true" [selected]="true">
    <app-icon icon="solar:heart-bold" size="16" />
    Like
  </button>
</div>`,
})
export class ToggleButtonDisabledDemo {}

Controlled

Bind [(selected)] for two-way control. Swap icon and label content based on the selected state.

Status: Not selected

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

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

@Component({
  selector: 'app-toggle-button-controlled-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<div class="flex flex-col gap-4">
  <button av-toggle-button [(selected)]="isSelected">
    @if (isSelected()) {
      <app-icon icon="solar:heart-bold" size="16" />
      Liked
    } @else {
      <app-icon icon="solar:heart-linear" size="16" />
      Like
    }
  </button>
  <p class="text-sm text-muted">
    Status:
    <span class="font-medium">{{ isSelected() ? 'Selected' : 'Not selected' }}</span>
  </p>
</div>`,
})
export class ToggleButtonControlledDemo {
  readonly isSelected = signal(false);
}

Customization

Tailwind CSS

Pass utility classes and CSS variables on the host to customize appearance — for example an X.com-style like control.

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

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

@Component({
  selector: 'app-toggle-button-custom-styling-demo',
  imports: [AvToggleButtonComponent, AppIconComponent],
  template: `<button
  av-toggle-button
  variant="ghost"
  class="x-like"
  aria-label="Like"
  [(selected)]="liked"
>
  <span class="x-like__hit">
    <span class="x-like__burst" aria-hidden="true"></span>
    <span class="x-like__icon">
      <app-icon
        [icon]="liked() ? 'solar:heart-bold' : 'solar:heart-linear'"
        size="20"
      />
    </span>
  </span>
  <span class="x-like__count">{{ liked() ? '68.1K' : '68K' }}</span>
</button>`,
  styles: [`
    .x-like {
      --av-toggle-button-bg: transparent;
      --av-toggle-button-bg-hover: transparent;
      --av-toggle-button-bg-pressed: transparent;
      --av-toggle-button-bg-selected: transparent;
      --av-toggle-button-bg-selected-hover: transparent;
      --av-toggle-button-bg-selected-pressed: transparent;
      --av-toggle-button-fg-selected: #f91880;
      gap: 0;
      padding-inline: 0;
      color: var(--av-muted);
    }

    .x-like:hover,
    .x-like[data-hovered='true'],
    .x-like[data-selected='true'] {
      color: #f91880;
    }

    .x-like__hit {
      position: relative;
      display: grid;
      place-items: center;
      width: 2.25rem;
      height: 2.25rem;
      margin-inline-end: -0.35rem;
      border-radius: 9999px;
      transition: background-color 100ms ease;
    }

    .x-like:hover .x-like__hit,
    .x-like[data-hovered='true'] .x-like__hit {
      background-color: rgba(249, 24, 128, 0.1);
    }

    .x-like__icon {
      display: grid;
      place-items: center;
      transition: transform 150ms ease;
    }

    .x-like[data-selected='true'] .x-like__icon {
      animation: x-like-pop 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97);
    }

    .x-like__count {
      min-width: 2rem;
      font-size: 0.8125rem;
      font-weight: 400;
      font-variant-numeric: tabular-nums;
      line-height: 1;
    }

    .x-like__burst {
      position: absolute;
      inset: 0;
      border-radius: 9999px;
      border: 2px solid #f91880;
      opacity: 0;
      pointer-events: none;
    }

    .x-like[data-selected='true'] .x-like__burst {
      animation: x-like-burst 0.55s ease-out;
    }

    @keyframes x-like-pop {
      0% { transform: scale(1); }
      30% { transform: scale(0.75); }
      60% { transform: scale(1.28); }
      100% { transform: scale(1); }
    }

    @keyframes x-like-burst {
      0% { transform: scale(0.35); opacity: 0.7; }
      100% { transform: scale(1.75); opacity: 0; }
    }
  `],
})
export class ToggleButtonCustomStylingDemo {
  readonly liked = signal(false);
}

Global CSS

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

@layer components {
  .av-toggle-button {
    @apply bg-accent text-accent-foreground;
  }

  .av-toggle-button--icon-only {
    @apply rounded-lg;
  }
}

Styling Reference

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

CSS Classes

Base & Size Classes

  • .av-toggle-button — Base toggle button styles
  • .av-toggle-button--sm — Small size variant
  • .av-toggle-button--md — Medium size variant (default)
  • .av-toggle-button--lg — Large size variant

Variant Classes

  • .av-toggle-button--default — Default variant with filled background
  • .av-toggle-button--ghost — Ghost variant with transparent background

Modifier Classes

  • .av-toggle-button--icon-only — Icon-only toggle button
  • .av-toggle-button--icon-only.av-toggle-button--sm — Small icon-only
  • .av-toggle-button--icon-only.av-toggle-button--lg — Large icon-only

Interactive States

The toggle button supports both CSS pseudo-classes and data attributes:

  • Selected: [data-selected="true"] (also [aria-pressed="true"])
  • Hover: :hover or [data-hovered="true"]
  • Active/Pressed: :active or [data-pressed="true"]
  • Focus: :focus-visible or [data-focus-visible="true"]
  • Disabled: :disabled or [aria-disabled="true"]

API Reference

Props for button[av-toggle-button]. Implements toggle semantics via aria-pressed. Outside a group, bind selection with [(selected)].

PropTypeDefaultDescription
variant'default' | 'ghost''default'Visual style variant.
size'sm' | 'md' | 'lg''md'Size of the toggle button. Inherits from toggle-button-group when omitted.
icon-onlybooleanfalseWhether the button contains only an icon (square layout).
valuestring''Unique value used for selection inside a toggle-button-group.
disabledbooleanfalseWhether the toggle button is disabled.
selectedbooleanfalseSelected state outside a group. Supports two-way binding with [(selected)].

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