AvesraAvesrabeta

Button

A clickable button component with multiple variants and states

Import

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

Usage

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

@Component({
  selector: 'app-button-basic-demo',
  imports: [AvButtonComponent],
  template: `<button av-button (click)="onPress()">Click me</button>`,
})
export class ButtonBasicDemo {
  onPress(): void {
    console.log('Button pressed');
  }
}

Variants

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

@Component({
  selector: 'app-button-variants-demo',
  imports: [AvButtonComponent],
  template: `<div class="flex flex-wrap gap-3">
  <button av-button>Primary</button>
  <button av-button variant="secondary">Secondary</button>
  <button av-button variant="tertiary">Tertiary</button>
  <button av-button variant="outline">Outline</button>
  <button av-button variant="ghost">Ghost</button>
  <button av-button variant="danger">Danger</button>
  <button av-button variant="danger-soft">Danger Soft</button>
</div>`,
})
export class ButtonVariantsDemo {}

Sizes

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

@Component({
  selector: 'app-button-sizes-demo',
  imports: [AvButtonComponent],
  template: `<div class="flex flex-wrap items-center gap-3">
  <button av-button size="sm">Small</button>
  <button av-button size="md">Medium</button>
  <button av-button size="lg">Large</button>
</div>`,
})
export class ButtonSizesDemo {}

With Icons

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

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

@Component({
  selector: 'app-button-with-icons-demo',
  imports: [AvButtonComponent, AppIconComponent],
  template: `<div class="flex flex-wrap gap-3">
  <button av-button>
    <app-icon icon="solar:global-linear" size="16" />
    Search
  </button>
  <button av-button variant="secondary">
    <app-icon icon="solar:add-circle-linear" size="16" />
    Add Member
  </button>
  <button av-button variant="tertiary">
    <app-icon icon="solar:letter-linear" size="16" />
    Email
  </button>
  <button av-button variant="danger">
    <app-icon icon="solar:trash-bin-trash-linear" size="16" />
    Delete
  </button>
</div>`,
})
export class ButtonWithIconsDemo {}

Icon Only

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

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

@Component({
  selector: 'app-button-icon-only-demo',
  imports: [AvButtonComponent, AppIconComponent],
  template: `<div class="flex gap-3">
  <button av-button variant="tertiary" icon-only aria-label="More options">
    <app-icon icon="solar:menu-dots-linear" size="16" />
  </button>
  <button av-button variant="secondary" icon-only aria-label="Settings">
    <app-icon icon="solar:settings-linear" size="16" />
  </button>
  <button av-button variant="danger" icon-only aria-label="Delete">
    <app-icon icon="solar:trash-bin-trash-linear" size="16" />
  </button>
</div>`,
})
export class ButtonIconOnlyDemo {}

Loading

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

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

@Component({
  selector: 'app-button-loading-demo',
  imports: [AvButtonComponent, AppIconComponent],
  template: `<button av-button [pending]="true">
  <app-icon icon="solar:refresh-linear" size="16" class="animate-spin" />
  Uploading...
</button>`,
})
export class ButtonLoadingDemo {}

Loading State

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

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

@Component({
  selector: 'app-button-loading-state-demo',
  imports: [AvButtonComponent, AppIconComponent],
  template: `<button av-button [pending]="isLoading()" (click)="handlePress()">
  @if (isLoading()) {
    <app-icon icon="solar:refresh-linear" size="16" class="animate-spin" />
    Uploading...
  } @else {
    <app-icon icon="solar:paperclip-linear" size="16" />
    Upload File
  }
</button>`,
})
export class ButtonLoadingStateDemo {
  readonly isLoading = signal(false);

  handlePress(): void {
    this.isLoading.set(true);
    setTimeout(() => this.isLoading.set(false), 2000);
  }
}

Full Width

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

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

@Component({
  selector: 'app-button-full-width-demo',
  imports: [AvButtonComponent, AppIconComponent],
  template: `<div class="w-[400px] space-y-3">
  <button av-button full-width>Primary Button</button>
  <button av-button full-width>
    <app-icon icon="solar:add-circle-linear" size="16" />
    With Icon
  </button>
</div>`,
})
export class ButtonFullWidthDemo {}

Disabled State

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

@Component({
  selector: 'app-button-disabled-demo',
  imports: [AvButtonComponent],
  template: `<div class="flex flex-wrap gap-3">
  <button av-button [disabled]="true">Primary</button>
  <button av-button variant="secondary" [disabled]="true">Secondary</button>
  <button av-button variant="tertiary" [disabled]="true">Tertiary</button>
  <button av-button variant="outline" [disabled]="true">Outline</button>
  <button av-button variant="ghost" [disabled]="true">Ghost</button>
  <button av-button variant="danger" [disabled]="true">Danger</button>
  <button av-button variant="danger-soft" [disabled]="true">Danger Soft</button>
</div>`,
})
export class ButtonDisabledDemo {}

Social Buttons

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

@Component({
  selector: 'app-button-social-demo',
  imports: [AvButtonComponent],
  host: { class: 'flex items-center justify-center w-full' },
  template: `<div class="flex w-full max-w-xs flex-col gap-3">
  <button av-button full-width variant="tertiary">
    <svg aria-hidden="true" class="shrink-0" width="16" height="16" viewBox="0 0 128 128" role="img">
  <path fill="#fff" d="M44.59 4.21a63.28 63.28 0 0 0 4.33 120.9a67.6 67.6 0 0 0 32.36.35a57.13 57.13 0 0 0 25.9-13.46a57.44 57.44 0 0 0 16-26.26a74.3 74.3 0 0 0 1.61-33.58H65.27v24.69h34.47a29.72 29.72 0 0 1-12.66 19.52a36.2 36.2 0 0 1-13.93 5.5a41.3 41.3 0 0 1-15.1 0A37.2 37.2 0 0 1 44 95.74a39.3 39.3 0 0 1-14.5-19.42a38.3 38.3 0 0 1 0-24.63a39.25 39.25 0 0 1 9.18-14.91A37.17 37.17 0 0 1 76.13 27a34.3 34.3 0 0 1 13.64 8q5.83-5.8 11.64-11.63c2-2.09 4.18-4.08 6.15-6.22A61.2 61.2 0 0 0 87.2 4.59a64 64 0 0 0-42.61-.38" />
  <path fill="#e33629" d="M44.59 4.21a64 64 0 0 1 42.61.37a61.2 61.2 0 0 1 20.35 12.62c-2 2.14-4.11 4.14-6.15 6.22Q95.58 29.23 89.77 35a34.3 34.3 0 0 0-13.64-8a37.17 37.17 0 0 0-37.46 9.74a39.25 39.25 0 0 0-9.18 14.91L8.76 35.6A63.53 63.53 0 0 1 44.59 4.21" />
  <path fill="#f8bd00" d="M3.26 51.5a63 63 0 0 1 5.5-15.9l20.73 16.09a38.3 38.3 0 0 0 0 24.63q-10.36 8-20.73 16.08a63.33 63.33 0 0 1-5.5-40.9" />
  <path fill="#587dbd" d="M65.27 52.15h59.52a74.3 74.3 0 0 1-1.61 33.58a57.44 57.44 0 0 1-16 26.26c-6.69-5.22-13.41-10.4-20.1-15.62a29.72 29.72 0 0 0 12.66-19.54H65.27c-.01-8.22 0-16.45 0-24.68" />
  <path fill="#319f43" d="M8.75 92.4q10.37-8 20.73-16.08A39.3 39.3 0 0 0 44 95.74a37.2 37.2 0 0 0 14.08 6.08a41.3 41.3 0 0 0 15.1 0a36.2 36.2 0 0 0 13.93-5.5c6.69 5.22 13.41 10.4 20.1 15.62a57.13 57.13 0 0 1-25.9 13.47a67.6 67.6 0 0 1-32.36-.35a63 63 0 0 1-23-11.59A63.7 63.7 0 0 1 8.75 92.4" />
</svg>
    Sign in with Google
  </button>
  <button av-button full-width variant="tertiary">
    <svg aria-hidden="true" class="shrink-0" width="16" height="16" viewBox="0 0 24 24" role="img">
  <path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2" />
</svg>
    Sign in with GitHub
  </button>
  <button av-button full-width variant="tertiary">
    <svg aria-hidden="true" class="shrink-0" width="16" height="16" viewBox="0 0 512 512" role="img">
  <path fill="currentColor" d="M349.13 136.86c-40.32 0-57.36 19.24-85.44 19.24c-28.79 0-50.75-19.1-85.69-19.1c-34.2 0-70.67 20.88-93.83 56.45c-32.52 50.16-27 144.63 25.67 225.11c18.84 28.81 44 61.12 77 61.47h.6c28.68 0 37.2-18.78 76.67-19h.6c38.88 0 46.68 18.89 75.24 18.89h.6c33-.35 59.51-36.15 78.35-64.85c13.56-20.64 18.6-31 29-54.35c-76.19-28.92-88.43-136.93-13.08-178.34c-23-28.8-55.32-45.48-85.79-45.48Z" />
  <path fill="currentColor" d="M340.25 32c-24 1.63-52 16.91-68.4 36.86c-14.88 18.08-27.12 44.9-22.32 70.91h1.92c25.56 0 51.72-15.39 67-35.11c14.72-18.77 25.88-45.37 21.8-72.66" />
</svg>
    Sign in with Apple
  </button>
</div>`,
})
export class ButtonSocialDemo {}

Render Function

In Angular, customize the host element directly with attributes — no render prop needed. Here data-custom tracks the pressed state.

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

@Component({
  selector: 'app-button-render-function-demo',
  imports: [AvButtonComponent],
  template: `<button
      av-button
      [attr.data-custom]="pressed ? 'pressed' : 'bar'"
      (pointerdown)="pressed = true"
      (pointerup)="pressed = false"
      (pointerleave)="pressed = false"
    >
      Press me
    </button>`,
})
export class ButtonRenderFunctionDemo {
  pressed = false;
}

Adding custom variants

You can extend Avesra components by wrapping them and adding your own custom variants.

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

@Component({
  selector: 'app-button-custom-variants-demo',
  imports: [AvButtonComponent],
  template: `<button
  av-button
  class="h-11 rounded-full px-6 text-md font-semibold shadow-md"
>
  Custom Button
</button>`,
})
export class ButtonCustomVariantsDemo {}

Adding Ripple Effect

The Button component supports ripple effects through composition, allowing you to nest ripple elements as children.

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

@Component({
  selector: 'app-button-ripple-effect-demo',
  imports: [AvButtonComponent],
  template: `<button av-button variant="secondary" class="relative overflow-hidden">
  <span class="ripple-dot" aria-hidden="true"></span>
  Click me
</button>`,
  styles: [`
    .ripple-dot {
      position: absolute;
      inset: 50% auto auto 50%;
      width: 0;
      height: 0;
      border-radius: 9999px;
      background: currentColor;
      opacity: 0.2;
      transform: translate(-50%, -50%);
      animation: ripple-pulse 1.5s ease-out infinite;
      pointer-events: none;
    }

    @keyframes ripple-pulse {
      0% { width: 0; height: 0; opacity: 0.3; }
      100% { width: 200%; height: 200%; opacity: 0; }
    }
  `],
})
export class ButtonRippleEffectDemo {}

Styling

Passing Tailwind CSS classes

Pass utility classes on the button host to customize shape, color, and decoration.

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

@Component({
  selector: 'app-button-custom-styles-demo',
  imports: [AvButtonComponent],
  template: `<div class="relative inline-flex items-center justify-center">
      <span
        class="pointer-events-none absolute inset-[-28%] rounded-full bg-accent/25 blur-2xl dark:bg-accent/35"
        aria-hidden="true"
      ></span>
      <button
        av-button
        variant="ghost"
        class="relative rounded-full border-0 bg-foreground px-9 py-3 text-[15px] font-medium tracking-wide text-background shadow-[inset_0_1px_0_rgba(255,255,255,0.18),0_10px_28px_-12px_rgba(0,0,0,0.45)] transition-[transform,box-shadow,filter] duration-300 ease-out hover:-translate-y-0.5 hover:shadow-[inset_0_1px_0_rgba(255,255,255,0.22),0_16px_36px_-12px_rgba(0,0,0,0.5)] hover:brightness-110 active:translate-y-0 active:scale-[0.98] dark:bg-white dark:text-neutral-950 dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_12px_32px_-10px_rgba(255,255,255,0.35)]"
      >
        Upgrade
      </button>
    </div>`,
})
export class ButtonCustomStylesDemo {}

Customizing the component classes

To customize the Button classes, use the @layer components directive. Learn more.

@layer components {
  .av-button {
    @apply bg-purple-500 text-white hover:bg-purple-600;
  }

  .av-button--icon-only {
    @apply rounded-lg bg-blue-500;
  }
}

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

CSS Classes

The Button component uses these CSS classes:

Base & Size Classes

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

Variant Classes

  • .av-button--primary
  • .av-button--secondary
  • .av-button--tertiary
  • .av-button--outline
  • .av-button--ghost
  • .av-button--danger
  • .av-button--danger-soft

Modifier Classes

  • .av-button--icon-only
  • .av-button--icon-only.av-button--sm
  • .av-button--icon-only.av-button--lg
  • .av-button--full-width

Interactive States

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

  • Hover::hover or [data-hovered="true"]
  • Active/Pressed::active or [data-pressed="true"] (includes scale transform)
  • Focus::focus-visible or [data-focus-visible="true"] (shows focus ring)
  • Disabled::disabled or [aria-disabled="true"] (reduced opacity, no pointer events)
  • Pending:[data-pending] (no pointer events during loading)

API Reference

Apply av-button on a native <button>. Use DOM events such as (click) for interaction. When nested in Button Group, omitted variant/size/disabled inherit from the group.

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'tertiary' | 'outline' | 'ghost' | 'danger' | 'danger-soft''primary'Visual style variant. Inherits from `av-button-group` when omitted.
size'sm' | 'md' | 'lg''md'Size of the button. Inherits from `av-button-group` when omitted.
fullWidthbooleanfalseWhether the button should take full width of its container. Template alias: full-width.
disabledboolean | undefinedundefinedWhether the button is disabled. When omitted, `pending` disables the button; otherwise inherits the parent `av-button-group` disabled state.
pendingbooleanfalseWhether the button is in a loading state (`data-pending`).
iconOnlybooleanfalseWhether the button contains only an icon. Template alias: icon-only. Pair with `aria-label`.
type'button' | 'submit' | 'reset''button'Native button type.

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