Button

Displays a button or a component that looks like a button.

PreviousNext
import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-default',
  imports: [ZardButtonComponent, ZardIconComponent],
  standalone: true,
  template: `
    <button z-button zType="outline">Button</button>
    <button z-button zType="outline"><i z-icon zType="arrow-up"></i></button>
    <button z-button zType="outline">Button <i z-icon zType="popcorn"></i></button>
  `,
})
export class ZardDemoButtonDefaultComponent {}
 

Installation

1

Run the CLI

Use the CLI to add the component to your project.

npx @ngzard/ui@latest add button
1

Add the component files

Create the component directory structure and add the following files to your project.

button.component.ts
button.component.ts
import {
  afterNextRender,
  ChangeDetectionStrategy,
  Component,
  computed,
  type OnDestroy,
  ElementRef,
  inject,
  input,
  signal,
  ViewEncapsulation,
} from '@angular/core';
 
import type { ClassValue } from 'clsx';
 
import { buttonVariants, type ZardButtonVariants } from './button.variants';
import { mergeClasses, transform } from '../../shared/utils/utils';
import { ZardIconComponent } from '../icon/icon.component';
 
@Component({
  selector: 'z-button, button[z-button], a[z-button]',
  imports: [ZardIconComponent],
  standalone: true,
  template: `
    @if (zLoading()) {
      <i z-icon zType="loader-circle" class="animate-spin duration-2000"></i>
    }
    <ng-content />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class]': 'classes()',
    '[attr.data-icon-only]': 'iconOnly() || null',
  },
  exportAs: 'zButton',
})
export class ZardButtonComponent implements OnDestroy {
  private readonly elementRef = inject(ElementRef<HTMLElement>);
 
  readonly zType = input<ZardButtonVariants['zType']>('default');
  readonly zSize = input<ZardButtonVariants['zSize']>('default');
  readonly zShape = input<ZardButtonVariants['zShape']>('default');
  readonly class = input<ClassValue>('');
  readonly zFull = input(false, { transform });
  readonly zLoading = input(false, { transform });
 
  private readonly iconOnlyState = signal(false);
  readonly iconOnly = this.iconOnlyState.asReadonly();
 
  private _mutationObserver: MutationObserver | null = null;
 
  constructor() {
    afterNextRender(() => {
      const check = () => {
        const el = this.elementRef.nativeElement;
        const hasIcon = el.querySelector('z-icon, [z-icon]') !== null;
        const children = Array.from<Node>(el.childNodes);
        const hasText = children.some(node => {
          if (node.nodeType === 3) {
            return node.textContent?.trim() !== '';
          }
          if (node.nodeType === 1) {
            const element = node as HTMLElement;
            if (element.matches('z-icon, [z-icon]')) return false;
            return element.textContent?.trim() !== '';
          }
          return false;
        });
 
        this.iconOnlyState.set(hasIcon && !hasText);
      };
 
      check();
      this._mutationObserver = new MutationObserver(check);
      this._mutationObserver.observe(this.elementRef.nativeElement, {
        childList: true,
        characterData: true,
        subtree: true,
      });
    });
  }
 
  ngOnDestroy(): void {
    if (this._mutationObserver) {
      this._mutationObserver.disconnect();
      this._mutationObserver = null;
    }
  }
 
  protected readonly classes = computed(() =>
    mergeClasses(
      buttonVariants({
        zType: this.zType(),
        zSize: this.zSize(),
        zShape: this.zShape(),
        zFull: this.zFull(),
        zLoading: this.zLoading(),
      }),
      this.class(),
    ),
  );
}
 
button.variants.ts
button.variants.ts
import { cva, type VariantProps } from 'class-variance-authority';
 
export const buttonVariants = cva(
  "cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all active:scale-97 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
  {
    variants: {
      zType: {
        default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
        destructive:
          'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
        outline:
          'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
        secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      zSize: {
        default: 'h-9 px-4 py-2 data-[icon-only]:size-9 data-[icon-only]:p-0',
        sm: 'h-8 rounded-md gap-1.5 px-3 data-[icon-only]:size-8 data-[icon-only]:p-0',
        lg: 'h-10 rounded-md px-6 data-[icon-only]:size-10 data-[icon-only]:p-0',
      },
      zShape: {
        default: 'rounded-md',
        circle: 'rounded-full',
        square: 'rounded-none',
      },
      zFull: {
        true: 'w-full',
      },
      zLoading: {
        true: 'opacity-50 pointer-events-none',
      },
    },
    defaultVariants: {
      zType: 'default',
      zSize: 'default',
      zShape: 'default',
    },
  },
);
export type ZardButtonVariants = VariantProps<typeof buttonVariants>;
 

Examples

default

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-default',
  imports: [ZardButtonComponent, ZardIconComponent],
  standalone: true,
  template: `
    <button z-button zType="outline">Button</button>
    <button z-button zType="outline"><i z-icon zType="arrow-up"></i></button>
    <button z-button zType="outline">Button <i z-icon zType="popcorn"></i></button>
  `,
})
export class ZardDemoButtonDefaultComponent {}
 

type

import { Component } from '@angular/core';
 
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-type',
  imports: [ZardButtonComponent],
  standalone: true,
  template: `
    <button z-button zSize="sm">Default</button>
    <button z-button zSize="sm" zType="outline">Outline</button>
    <button z-button zSize="sm" zType="destructive">Destructive</button>
    <button z-button zSize="sm" zType="secondary">Secondary</button>
    <button z-button zSize="sm" zType="ghost">Ghost</button>
    <button z-button zSize="sm" zType="link">Link</button>
  `,
  host: {
    class: 'flex flex-col items-center gap-4 md:flex-row md:gap-8',
  },
})
export class ZardDemoButtonTypeComponent {}
 

size

import { Component } from '@angular/core';
 
import { ZardIconComponent } from '../../icon/icon.component';
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-size',
  imports: [ZardButtonComponent, ZardIconComponent],
  standalone: true,
  template: `
    <div class="flex flex-col items-center">
      <div class="mb-4 flex gap-2">
        <button z-button zSize="sm">Small</button>
        <button z-button zSize="sm"><z-icon zType="arrow-up" /></button>
      </div>
 
      <div class="mb-4 flex gap-2">
        <button z-button>Default</button>
        <button z-button><z-icon zType="arrow-up" /></button>
      </div>
 
      <div class="mb-4 flex gap-2">
        <button z-button zSize="lg">Large</button>
        <button z-button zSize="lg"><z-icon zType="arrow-up" /></button>
      </div>
    </div>
  `,
})
export class ZardDemoButtonSizeComponent {}
 

shape

import { Component } from '@angular/core';
 
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-shape',
  imports: [ZardButtonComponent],
  standalone: true,
  template: `
    <button z-button>Default</button>
    <button z-button zShape="circle">Circle</button>
    <button z-button zShape="square">Square</button>
  `,
})
export class ZardDemoButtonShapeComponent {}
 

full

import { Component } from '@angular/core';
 
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-full',
  imports: [ZardButtonComponent],
  standalone: true,
  template: ` <button z-button zFull>Default</button> `,
})
export class ZardDemoButtonFullComponent {}
 

loading

import { Component } from '@angular/core';
 
import { ZardButtonComponent } from '../button.component';
 
@Component({
  selector: 'z-demo-button-loading',
  imports: [ZardButtonComponent],
  standalone: true,
  template: ` <button z-button zLoading>Default</button> `,
})
export class ZardDemoButtonLoadingComponent {}
 

API Reference

z-button Directive

Property Description Type Default
zType button type default | destructive | outline | secondary | ghost | link default
zSize button size default | sm | lg default
zShape button shape default | circle | square default
zFull button width 100% boolean false
zLoading button loading state boolean false