[ Installation ]

npx wally-ui add button

[ Import ]

import { Button } from './components/wally-ui/button/button';
@Component({
  selector: 'app-example',
  imports: [Button],
  templateUrl: './example.html'
})

[ Basic Usage ]

<wally-button>Click me</wally-button>

[ All Variants ]

[ Variants ]

Primary (Default)

High emphasis. Use for main actions and CTAs.

<!-- Default variant -->
<wally-button>Primary Button</wally-button>

<!-- Explicit primary -->
<wally-button variant="primary">Primary Button</wally-button>

Secondary

Medium emphasis. Use for secondary actions.

<wally-button variant="secondary">Secondary Button</wally-button>

Destructive

Use for dangerous actions like delete or remove.

<wally-button variant="destructive">Delete Account</wally-button>

Outline

Low emphasis with border. Use for tertiary actions.

<wally-button variant="outline">Outline Button</wally-button>

Ghost

Minimal styling. Use for less prominent actions.

<wally-button variant="ghost">Ghost Button</wally-button>

Styled as a link. Supports internal routing and external URLs.

<!-- Internal navigation -->
<wally-button variant="link" href="/components">View Components</wally-button>

<!-- External link -->
<wally-button variant="link" href="https://github.com">GitHub</wally-button>

[ Production Examples ]

Call-to-Action Button

<!-- Call-to-Action Example -->
<wally-button>
  Get Started Free
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="2" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round"
          d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
  </svg>
</wally-button>

Login Form

<!-- Login Form Example -->
<form (ngSubmit)="onLogin()">
  <!-- ... form fields ... -->

  <div class="flex gap-2">
    <wally-button
      type="submit"
      [loading]="isLoggingIn()"
      [disabled]="!loginForm.valid">
      Sign In
    </wally-button>

    <wally-button
      variant="secondary"
      type="button"
      (click)="goToSignUp()">
      Create Account
    </wally-button>
  </div>
</form>
export class LoginComponent {
  isLoggingIn = signal(false);
  loginForm: FormGroup;

  onLogin() {
    this.isLoggingIn.set(true);

    this.authService.login(this.loginForm.value)
      .subscribe({
        next: () => this.router.navigate(['/dashboard']),
        error: () => this.isLoggingIn.set(false)
      });
  }
}

Delete Confirmation Modal

<!-- Delete Confirmation Modal -->
<div class="modal">
  <h2>Delete Account?</h2>
  <p>This action cannot be undone.</p>

  <div class="flex gap-2 justify-end">
    <wally-button
      variant="ghost"
      (click)="closeModal()">
      Cancel
    </wally-button>

    <wally-button
      variant="destructive"
      [loading]="isDeleting()"
      (click)="confirmDelete()">
      Delete Account
    </wally-button>
  </div>
</div>

Dashboard Actions

<!-- Dashboard Actions -->
<div class="dashboard-header">
  <wally-button variant="outline" (click)="exportData()">
    Export
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
         stroke-width="1.5" stroke="currentColor" class="size-5">
      <path stroke-linecap="round" stroke-linejoin="round"
            d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
    </svg>
  </wally-button>

  <wally-button (click)="createNew()">
    Create New
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
         stroke-width="2" stroke="currentColor" class="size-5">
      <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
    </svg>
  </wally-button>
</div>

[ States ]

Disabled

<wally-button [disabled]="true">Disabled</wally-button>

Loading

<wally-button [loading]="true">Loading...</wally-button>

With Notification Badge

<!-- Notification Icon Button -->
<wally-button
  [showNotification]="hasUnreadMessages()"
  ariaLabel="View messages">
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="1.5" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round"
          d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
  </svg>
</wally-button>

[ Button Types ]

Submit Button

<wally-button type="submit">Submit Form</wally-button>

Reset Button

<wally-button type="reset" variant="ghost">Reset</wally-button>

[ Click Events ]

<wally-button (click)="handleClick()">Click Me</wally-button>
handleClick(): void {
  console.log('Button clicked!');
  // Your logic here
}

[ Button with Icons ]

Icon with Text

<wally-button>
  Save Changes
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="1.5" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round"
          d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
  </svg>
</wally-button>

Icon Only

<wally-button ariaLabel="Notifications">
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="1.5" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round"
          d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0M3.124 7.5A8.969 8.969 0 0 1 5.292 3m13.416 0a8.969 8.969 0 0 1 2.168 4.5" />
  </svg>
</wally-button>

Icon Left

<wally-button>
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="2" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round"
          d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
  </svg>
  Back
</wally-button>

[ Accessibility ]

ARIA Label (Essential for Icon-Only Buttons)

<!-- Essential for icon-only buttons -->
<wally-button ariaLabel="Save document">
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="1.5" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round"
          d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z" />
  </svg>
</wally-button>

Toggle Button with ARIA Pressed

<!-- For toggle buttons -->
<wally-button [ariaPressed]="isMuted()">
  {{ isMuted() ? 'Unmute' : 'Mute' }}
</wally-button>

Loading State (Automatic ARIA Busy)

<!-- Automatically set when loading="true" -->
<wally-button [loading]="isSaving()">
  Save Changes
</wally-button>

ARIA Described By

<!-- Connect button to description -->
<wally-button ariaDescribedBy="save-description">
  Save
</wally-button>
<p id="save-description" class="sr-only">
  Saves your changes permanently
</p>

[ API Reference ]

Input Properties

PropertyTypeDefaultDescription
typestring'button'HTML button type: 'button', 'submit', or 'reset'
variant'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link''primary'Visual style variant of the button
hrefstring''URL for navigation (used with variant="link"). Supports internal routes and external URLs
disabledbooleanfalseDisables the button, preventing user interaction
loadingbooleanfalseShows loading spinner and disables button. Automatically sets aria-busy="true"
showNotificationbooleanfalseShows animated notification badge in top-right corner
ariaLabelstring''Accessible label for screen readers. Essential for icon-only buttons
ariaPressedboolean | undefinedundefinedIndicates pressed state for toggle buttons. Use true/false for toggle functionality
ariaDescribedBystring''ID(s) of element(s) that describe the button for screen readers

Output Events

EventPayloadDescription
clickvoidEmitted when button is clicked. Also handles navigation for link variant

Methods

MethodParametersReturnDescription
handleClick()event: MouseEventvoidInternal click handler. Manages navigation for link variant and emits click event

Computed Properties

PropertyTypeDescription
variantClassesSignal<string>Computed signal that returns CSS classes based on variant. Automatically updates when variant changes

[ Advanced Usage & Edge Cases ]

Form Validation Integration

Button automatically integrates with Angular Forms. Combine disabled with form validation for better UX.

<!-- Button with form validation -->
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
  <!-- form fields -->

  <wally-button
    type="submit"
    [disabled]="!myForm.valid"
    [loading]="isSubmitting()">
    Submit
  </wally-button>
</form>
export class MyComponent {
  myForm = new FormGroup({
    name: new FormControl('', Validators.required)
  });

  isSubmitting = signal(false);

  onSubmit() {
    if (this.myForm.valid) {
      this.isSubmitting.set(true);
      // API call...
    }
  }
}

Router Integration

Use variant="link" for declarative navigation, or programmatic navigation with click events.

<!-- Using with Angular Router -->
<wally-button variant="link" href="/dashboard">
  Go to Dashboard
</wally-button>

<!-- Programmatic navigation -->
<wally-button (click)="navigateToProfile()">
  View Profile
</wally-button>
constructor(private router: Router) {}

navigateToProfile() {
  this.router.navigate(['/profile', this.userId]);
}

Loading + Disabled Behavior

When both loading and disabled are set, loading takes precedence. The button will show the loading spinner.

<!-- Loading takes precedence over disabled -->
<wally-button
  [disabled]="formInvalid()"
  [loading]="isSaving()">
  Save
</wally-button>

<!-- Result: When loading=true, button is disabled regardless of disabled prop -->

Icon Library Recommendations

Use size-5 (20px) icons for optimal visual balance. Compatible with all SVG icon libraries.

<!-- Recommended: Heroicons with size-5 (20px) -->
<wally-button>
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
       stroke-width="1.5" stroke="currentColor" class="size-5">
    <path stroke-linecap="round" stroke-linejoin="round" d="..." />
  </svg>
  Action
</wally-button>

<!-- Also works with: Lucide, Phosphor, Font Awesome SVG -->

Using Angular Signals (Recommended)

Button properties work seamlessly with Angular Signals for reactive state management.

<!-- Using Angular Signals (reactive) -->
export class MyComponent {
  isLoading = signal(false);
  isDisabled = computed(() => !this.form.valid);

  async handleSubmit() {
    this.isLoading.set(true);
    await this.api.save();
    this.isLoading.set(false);
  }
}

<!-- Template -->
<wally-button
  [loading]="isLoading()"
  [disabled]="isDisabled()"
  (click)="handleSubmit()">
  Submit
</wally-button>

Understanding Button Types

Always specify type="button" for non-form buttons to prevent accidental form submission.

<!-- GOOD: Explicit type prevents accidental form submission -->
<wally-button type="button" (click)="openModal()">
  Open
</wally-button>

<!-- CAUTION: Default type="button" is safe, but explicit is better -->
<wally-button (click)="openModal()">Open</wally-button>

<!-- GOOD: Use type="submit" for form submission -->
<form (ngSubmit)="save()">
  <wally-button type="submit">Save</wally-button>
</form>

Href Behavior (Important)

The href property only works when variant="link" is set. Otherwise, it's ignored.

<!-- href only works with variant="link" -->

<!-- CORRECT: Works as expected -->
<wally-button variant="link" href="/page">Link</wally-button>

<!-- INCORRECT: href is ignored, just a regular button -->
<wally-button href="/page">Button</wally-button>