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>
Production-ready button component with multiple variants, loading states, notifications, and comprehensive accessibility support. Built with Angular Signals and optimized for modern applications.
npx wally-ui add button
import { Button } from './components/wally-ui/button/button';
@Component({
selector: 'app-example',
imports: [Button],
templateUrl: './example.html'
})
<wally-button>Click me</wally-button>
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>
Medium emphasis. Use for secondary actions.
<wally-button variant="secondary">Secondary Button</wally-button>
Use for dangerous actions like delete or remove.
<wally-button variant="destructive">Delete Account</wally-button>
Low emphasis with border. Use for tertiary actions.
<wally-button variant="outline">Outline Button</wally-button>
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>
<!-- 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 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 -->
<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 -->
<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>
<wally-button [disabled]="true">Disabled</wally-button>
<wally-button [loading]="true">Loading...</wally-button>
<!-- 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>
<wally-button (click)="handleClick()">Click Me</wally-button>
handleClick(): void {
console.log('Button clicked!');
// Your logic here
}
<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>
<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>
<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>
<!-- 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>
<!-- For toggle buttons -->
<wally-button [ariaPressed]="isMuted()">
{{ isMuted() ? 'Unmute' : 'Mute' }}
</wally-button>
<!-- Automatically set when loading="true" -->
<wally-button [loading]="isSaving()">
Save Changes
</wally-button>
<!-- Connect button to description -->
<wally-button ariaDescribedBy="save-description">
Save
</wally-button>
<p id="save-description" class="sr-only">
Saves your changes permanently
</p>
Saves your changes permanently
Property | Type | Default | Description |
---|---|---|---|
type | string | 'button' | HTML button type: 'button', 'submit', or 'reset' |
variant | 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link' | 'primary' | Visual style variant of the button |
href | string | '' | URL for navigation (used with variant="link"). Supports internal routes and external URLs |
disabled | boolean | false | Disables the button, preventing user interaction |
loading | boolean | false | Shows loading spinner and disables button. Automatically sets aria-busy="true" |
showNotification | boolean | false | Shows animated notification badge in top-right corner |
ariaLabel | string | '' | Accessible label for screen readers. Essential for icon-only buttons |
ariaPressed | boolean | undefined | undefined | Indicates pressed state for toggle buttons. Use true/false for toggle functionality |
ariaDescribedBy | string | '' | ID(s) of element(s) that describe the button for screen readers |
Event | Payload | Description |
---|---|---|
click | void | Emitted when button is clicked. Also handles navigation for link variant |
Method | Parameters | Return | Description |
---|---|---|---|
handleClick() | event: MouseEvent | void | Internal click handler. Manages navigation for link variant and emits click event |
Property | Type | Description |
---|---|---|
variantClasses | Signal<string> | Computed signal that returns CSS classes based on variant. Automatically updates when variant changes |
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...
}
}
}
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]);
}
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 -->
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 -->
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>
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>
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>