Button Component
The sando-button
component is a versatile, accessible button with multiple variants, sizes, states, and can render as either a button or link.
Features
- ✅ Fully Tested: Comprehensive unit and E2E tests
- ♿ Accessible: WCAG 2.1 AA compliant with keyboard navigation and screen reader support
- 🎨 Themeable: Token-driven styling with flavor support
- 🔒 Type Safe: Full TypeScript support
- ⚡ Performant: Lightweight (<8KB gzipped)
- 🔗 Dual Mode: Works as button or link (with href attribute)
- 🎯 Icon Support: Multiple ways to add icons (slots or props)
- 🔘 Toggle Support: Built-in toggle button functionality with aria-pressed
Basic Usage
<!-- Import the component -->
<script type="module">
import '@sando/components/button';
</script>
<!-- Use it -->
<sando-button variant="solid">
Click me
</sando-button>
2
3
4
5
6
7
8
9
Variants
The button comes in four visual styles:
Solid (Default)
High emphasis button with filled background.
<sando-button variant="solid">Solid Button</sando-button>
Outline
Medium emphasis button with border.
<sando-button variant="outline">Outline Button</sando-button>
Ghost
Low emphasis button without background or border.
<sando-button variant="ghost">Ghost Button</sando-button>
Text
Minimal text-only button (useful for inline links).
<sando-button variant="text">Text Button</sando-button>
Sizes
Four WCAG-compliant size options for different contexts:
<sando-button size="xs">Extra Small</sando-button>
<sando-button size="small">Small</sando-button>
<sando-button size="medium">Medium (Default)</sando-button>
<sando-button size="large">Large</sando-button>
2
3
4
Touch Target Compliance
All button sizes meet WCAG 2.1 Level AA minimum touch target size of 44x44px through padding adjustments.
Status
Convey different semantic meanings:
<sando-button status="default">Default</sando-button>
<sando-button status="success">Success</sando-button>
<sando-button status="destructive">Destructive</sando-button>
2
3
States
Disabled
<sando-button disabled>Disabled Button</sando-button>
Loading
Shows a loading indicator and prevents interaction.
<sando-button loading>Loading...</sando-button>
Full Width
Make the button span the full container width:
<sando-button full-width>Full Width Button</sando-button>
With Icons
Method 1: Using Properties (Recommended)
The simplest way to add icons using the new start-icon
and end-icon
properties:
<sando-button start-icon="⭐">Favorite</sando-button>
<sando-button end-icon="→">Next Page</sando-button>
<sando-button start-icon="💾" end-icon="✓">Save</sando-button>
2
3
Method 2: Using Slots (Advanced)
For more complex icon requirements or when using icon components:
<sando-button>
<span slot="icon-start">⭐</span>
Favorite
</sando-button>
<sando-button>
Send
<span slot="icon-end">→</span>
</sando-button>
2
3
4
5
6
7
8
9
Icon-Only Buttons
For buttons with only an icon and no text (must provide aria-label
for accessibility):
<sando-button icon-only aria-label="Settings">
<span slot="icon-start">⚙️</span>
</sando-button>
<!-- With border radius -->
<sando-button icon-only radius="full" aria-label="Add item">
<span slot="icon-start">➕</span>
</sando-button>
2
3
4
5
6
7
8
Border Radius
Control the button's corner rounding:
<sando-button radius="none">No Radius</sando-button>
<sando-button radius="default">Default (Medium)</sando-button>
<sando-button radius="full">Fully Rounded (Pill)</sando-button>
2
3
Perfect Circles
Use radius="full"
with icon-only
to create circular icon buttons.
As Link (href)
Render the button as an anchor tag by providing an href
attribute:
<!-- Basic link -->
<sando-button href="/about">About Us</sando-button>
<!-- External link with target -->
<sando-button href="https://example.com" target="_blank">
Visit Site
</sando-button>
<!-- Download link -->
<sando-button href="/files/document.pdf" download>
Download PDF
</sando-button>
2
3
4
5
6
7
8
9
10
11
12
Security
When using target="_blank"
, the component automatically adds rel="noopener noreferrer"
for security unless you specify a custom rel
attribute.
Toggle Buttons
Create toggle buttons with active/inactive states:
<sando-button toggle active>
🔔 Notifications Enabled
</sando-button>
<sando-button toggle>
🔕 Notifications Disabled
</sando-button>
2
3
4
5
6
7
Toggle buttons automatically include aria-pressed
for screen reader support.
Toggle Button with JavaScript
<sando-button id="filter-btn" toggle>
Filter Active
</sando-button>
<script>
const btn = document.getElementById('filter-btn');
btn.addEventListener('click', () => {
btn.active = !btn.active;
console.log('Filter is now:', btn.active ? 'ON' : 'OFF');
});
</script>
2
3
4
5
6
7
8
9
10
11
Theming
Using Flavors
Apply different theme flavors:
<sando-button flavor="original">Original Theme</sando-button>
<sando-button flavor="strawberry">Strawberry Theme</sando-button>
2
Custom Styling
Override CSS custom properties for fine-grained control:
<sando-button
style="
--sando-button-solid-backgroundColor-default: #ff6b6b;
--sando-button-solid-textColor-default: white;
"
>
Custom Colors
</sando-button>
2
3
4
5
6
7
8
API Reference
Properties
Property | Type | Default | Description |
---|---|---|---|
variant | 'solid' | 'outline' | 'ghost' | 'text' | 'solid' | Visual style variant |
size | 'xs' | 'small' | 'medium' | 'large' | 'medium' | Button size (all WCAG compliant) |
status | 'default' | 'success' | 'destructive' | 'default' | Status variant for semantic meaning |
radius | 'none' | 'default' | 'full' | 'default' | Border radius variant |
disabled | boolean | false | Whether the button is disabled |
loading | boolean | false | Whether the button is in loading state |
type | 'button' | 'submit' | 'reset' | 'button' | Button type for forms |
fullWidth | boolean | false | Whether button takes full width |
iconOnly | boolean | false | Square shape for icon-only buttons |
toggle | boolean | false | Enable toggle button behavior |
active | boolean | false | Active/pressed state (for toggles) |
flavor | string | 'original' | Design system theme flavor |
href | string | undefined | URL (renders as <a> instead of <button> ) |
target | '_self' | '_blank' | '_parent' | '_top' | '_self' | Where to open linked document |
rel | string | undefined | Relationship to linked document |
download | string | boolean | undefined | Download linked resource |
startIcon | string | undefined | Icon to display at start (alternative to slot) |
endIcon | string | undefined | Icon to display at end (alternative to slot) |
ariaLabel | string | null | Accessible label (overrides visible text) |
Slots
Slot | Description |
---|---|
Default | Button content (text, icons, etc.) |
icon-start | Icon or content before the button text |
icon-end | Icon or content after the button text |
Events
Event | Type | Description |
---|---|---|
click | CustomEvent | Fired when button is clicked (unless disabled or loading) |
CSS Custom Properties
Key CSS variables you can override:
/* Base styles */
--sando-button-fontFamily
--sando-button-fontWeight
--sando-button-borderRadius
--sando-button-transition-duration
/* Variant-specific (solid/outline/ghost) */
--sando-button-solid-backgroundColor-default
--sando-button-solid-backgroundColor-hover
--sando-button-solid-backgroundColor-active
--sando-button-solid-textColor-default
/* Size-specific */
--sando-button-size-medium-paddingBlock
--sando-button-size-medium-paddingInline
--sando-button-size-medium-fontSize
--sando-button-size-medium-lineHeight
--sando-button-size-medium-gap
/* Status-specific */
--sando-button-solid-backgroundColor-success
--sando-button-solid-backgroundColor-destructive
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Complete Token Reference
See full list in Storybook or check packages/tokens/src/recipes/button.json
Framework Integration
React
import '@sando/components/button';
import type { SandoButton } from '@sando/components';
function App() {
const handleClick = (e: CustomEvent) => {
console.log('Button clicked!', e.detail);
};
return (
<sando-button
variant="solid"
size="medium"
onClick={handleClick}
>
Click me
</sando-button>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Vue 3
<template>
<sando-button
variant="solid"
size="medium"
@click="handleClick"
>
Click me
</sando-button>
</template>
<script setup lang="ts">
import '@sando/components/button';
const handleClick = (e: CustomEvent) => {
console.log('Button clicked!', e.detail);
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Angular
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@sando/components/button';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}
2
3
4
5
6
7
8
<!-- component.html -->
<sando-button
variant="solid"
size="medium"
(click)="handleClick($event)"
>
Click me
</sando-button>
2
3
4
5
6
7
8
Accessibility
The Button component is built with accessibility in mind and exceeds WCAG 2.1 Level AA requirements:
- ✅ Keyboard Navigation: Full keyboard support (Space/Enter)
- ✅ Screen Reader Support: Proper ARIA labels, roles, and live regions
- ✅ Focus Management: Visible focus indicator with sufficient contrast
- ✅ Color Contrast: WCAG AA compliant contrast ratios (4.5:1 for text, 3:1 for UI)
- ✅ Touch Targets: All sizes meet 44x44px minimum (WCAG 2.1 Level AA)
- ✅ State Communication: Loading, disabled, and pressed states announced
- ✅ Toggle Support: Built-in
aria-pressed
for toggle buttons - ✅ Icon-Only Support: Requires
aria-label
for screen readers - ✅ Loading States: Includes
aria-busy
andaria-live="polite"
Keyboard Shortcuts
Key | Action |
---|---|
Space | Activate button |
Enter | Activate button |
Tab | Focus next element |
Shift + Tab | Focus previous element |
ARIA Attributes
The component automatically manages these ARIA attributes:
aria-label
- Custom label (overrides visible text)aria-pressed
- Toggle state (whentoggle
prop is true)aria-disabled
- Disabled statearia-busy
- Loading statearia-live="polite"
- Loading state announcements
Best Practices
Do ✅
- Use
variant="solid"
for primary actions (one per context) - Use
variant="outline"
for secondary actions - Use
variant="ghost"
for tertiary/subtle actions - Use
variant="text"
for inline text links - Use
status="destructive"
for dangerous actions (delete, remove, etc.) - Provide clear, action-oriented labels ("Save Changes", not "Click Here")
- Use
loading
state for async operations - Add
aria-label
toicon-only
buttons - Use
toggle
prop for on/off states (filters, settings) - Use
href
instead ofonClick
navigation when possible (better for SEO and accessibility) - Combine
radius="full"
withicon-only
for circular buttons
Don't ❌
- Don't use multiple solid buttons in the same context (only one primary action)
- Don't use red/destructive for non-dangerous actions
- Don't disable buttons without explaining why (use tooltips or helper text)
- Don't use generic labels like "Click here" or "Submit"
- Don't nest interactive elements inside buttons
- Don't forget
aria-label
on icon-only buttons - Don't use
size="xs"
for primary actions (use for compact UIs only)
Examples
Form Submit Button
<form>
<sando-button
type="submit"
variant="solid"
full-width
>
Submit Form
</sando-button>
</form>
2
3
4
5
6
7
8
9
Destructive Action
<sando-button
variant="outline"
status="destructive"
start-icon="🗑️"
>
Delete Item
</sando-button>
2
3
4
5
6
7
Loading State with Async Action
<sando-button id="save-btn" variant="solid">
Save Changes
</sando-button>
<script>
const button = document.getElementById('save-btn');
button.addEventListener('click', async () => {
button.loading = true;
try {
await saveData();
} finally {
button.loading = false;
}
});
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Navigation Link Button
<!-- Internal navigation -->
<sando-button href="/dashboard" variant="solid">
Go to Dashboard
</sando-button>
<!-- External link with icon -->
<sando-button
href="https://github.com"
target="_blank"
variant="outline"
end-icon="↗"
>
View on GitHub
</sando-button>
2
3
4
5
6
7
8
9
10
11
12
13
14
Icon-Only Circular Buttons
<!-- Floating action button -->
<sando-button
icon-only
radius="full"
size="large"
aria-label="Add new item"
>
<span slot="icon-start">➕</span>
</sando-button>
<!-- Small icon buttons for toolbar -->
<sando-button icon-only radius="full" size="xs" aria-label="Edit">
<span slot="icon-start">✏️</span>
</sando-button>
<sando-button icon-only radius="full" size="xs" aria-label="Delete">
<span slot="icon-start">🗑️</span>
</sando-button>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Toggle Button Group
<div role="group" aria-label="Text formatting">
<sando-button toggle id="bold-btn" aria-label="Bold">
<strong>B</strong>
</sando-button>
<sando-button toggle id="italic-btn" aria-label="Italic">
<em>I</em>
</sando-button>
<sando-button toggle id="underline-btn" aria-label="Underline">
<u>U</u>
</sando-button>
</div>
<script>
['bold', 'italic', 'underline'].forEach(format => {
const btn = document.getElementById(`${format}-btn`);
btn.addEventListener('click', () => {
btn.active = !btn.active;
document.execCommand(format);
});
});
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Button with Multiple Icons
<sando-button
start-icon="⬇"
end-icon="✓"
variant="solid"
status="success"
>
Download Complete
</sando-button>
2
3
4
5
6
7
8
Browser Support
- ✅ Chrome/Edge 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ iOS Safari 14+