LogoRectangle UI

Button

This is a component that represents a button.

  • A button is a clickable element used to trigger actions.
  • Supports visual variants: primary, secondary, and danger.
  • Supports disabled state and keeps interaction styles consistent with the design system.

Preview

Usage

import { Component } from "@angular/core";
import { ButtonComponent } from "@/components/button/button.component";

@Component({
  selector: "rui-button-demo",
  template: `
    <div class="flex flex-wrap items-center gap-3">
      <rui-button variant="primary" (buttonClick)="onButtonClicked('Primary')">Primary</rui-button>
      <rui-button variant="secondary" (buttonClick)="onButtonClicked('Secondary')">
        Secondary
      </rui-button>
      <rui-button variant="danger" (buttonClick)="onButtonClicked('Danger')">Danger</rui-button>
      <rui-button variant="primary" [disabled]="true">Disabled</rui-button>
    </div>
  `,
  imports: [ButtonComponent],
})
export class ButtonDemoComponent {
  onButtonClicked(variant: string) {
    console.log(`${variant} button clicked`);
  }
}

Source Code

import {
  booleanAttribute,
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Input,
  Output,
} from "@angular/core";
import { NgClass } from "@angular/common";

const BUTTON_TEXT = "cursor-pointer select-none text-sm font-semibold";
const BUTTON_LAYOUT = "inline-flex w-fit items-center justify-center rounded-xl px-3 py-2";
const BUTTON_ANIMATION = "transition-colors duration-200 ease-in-out";
const BUTTON_DISABLED = "disabled:cursor-not-allowed disabled:opacity-60";

const BUTTON_VARIANT_CLASSES: Record<ButtonVariant, string> = {
  primary:
    "border border-primary-400 bg-primary-100 text-primary-900 enabled:hover:bg-primary-200 enabled:active:bg-primary-200 dark:border-primary-800 dark:bg-primary-900 dark:text-primary-100 dark:enabled:hover:bg-primary-900/50 dark:enabled:active:bg-primary-900/50",
  secondary:
    "border border-primary-300 bg-transparent text-primary-900 enabled:hover:bg-primary-100 enabled:active:bg-primary-100 dark:border-primary-700 dark:text-primary-100 dark:enabled:hover:bg-primary-900/40 dark:enabled:active:bg-primary-900/40",
  danger:
    "border border-red-300 bg-red-50 text-red-700 enabled:hover:bg-red-100 enabled:active:bg-red-100 dark:border-red-800 dark:bg-red-950/40 dark:text-red-300 dark:enabled:hover:bg-red-900/40 dark:enabled:active:bg-red-900/40",
};

@Component({
  selector: "rui-button",
  imports: [NgClass],
  template: `
    <button
      type="button"
      [disabled]="disabled"
      [ngClass]="styleClasses"
      (click)="buttonClick.emit()">
      <ng-content></ng-content>
    </button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ButtonComponent {
  /**
   * Visual style variant for the button.
   */
  @Input() variant: ButtonVariant | string = "primary";

  /**
   * Whether the button is disabled.
   */
  @Input({ transform: booleanAttribute }) disabled: boolean = false;

  /**
   * Emits when the button is clicked.
   */
  @Output() buttonClick = new EventEmitter<void>();

  protected get styleClasses(): string[] {
    return [
      BUTTON_VARIANT_CLASSES[resolveButtonVariant(this.variant)],
      BUTTON_TEXT,
      BUTTON_LAYOUT,
      BUTTON_ANIMATION,
      BUTTON_DISABLED,
    ];
  }
}

export type ButtonVariant = "primary" | "secondary" | "danger";

function resolveButtonVariant(variant: string): ButtonVariant {
  switch (variant) {
    case "secondary":
    case "danger":
    case "primary":
      return variant;
    default:
      return "primary";
  }
}
Copyright © 2026 Jarrett Huang | MIT License | Github