Button Component
The DsButton component is a flexible button component that wraps Vuetify's v-btn with design system conventions and theming.
Overview
DsButton provides a comprehensive button solution with multiple variants, sizes, and states. It supports Material Design 3 styling, icon-only buttons, loading states, and full accessibility features.
Interactive Demo
Button Playground
Customize the button using the controls below to see different variants, colors, and sizes.
Controls
Button style variant
Color from design tokens
Button size
Render as icon-only button (48px × 48px)
Show loading spinner
Disable button interaction
Text to display on button
<template>
<DsButton>
Click Me
</DsButton>
</template>Variants
<template>
<div class="button-group">
<DsButton variant="elevated">Elevated</DsButton>
<DsButton variant="flat">Flat</DsButton>
<DsButton variant="tonal">Tonal</DsButton>
<DsButton variant="outlined">Outlined</DsButton>
<DsButton variant="text">Text</DsButton>
</div>
</template>The button supports five Material Design 3 variants:
- Elevated (default): Filled button with shadow elevation, best for important primary actions
- Flat: Filled button without shadow, for high-emphasis actions in constrained spaces
- Tonal: Subtle filled button with background tint, for medium-emphasis actions
- Outlined: For secondary actions that need emphasis without solid fill
- Text: For tertiary or low-emphasis actions
Sizes
<template>
<div class="button-group">
<DsButton size="sm">Small</DsButton>
<DsButton size="md">Medium</DsButton>
<DsButton size="lg">Large</DsButton>
</div>
</template>Buttons come in three sizes:
- sm (small): Compact buttons for tight spaces
- md (medium, default): Standard button size for most use cases
- lg (large): Larger buttons for prominent actions or touch interfaces
Colors
<template>
<div class="button-group button-group--compact">
<DsButton color="primary">Primary</DsButton>
<DsButton color="secondary">Secondary</DsButton>
<DsButton color="success">Success</DsButton>
<DsButton color="info">Info</DsButton>
<DsButton color="warning">Warning</DsButton>
<DsButton color="error">Error</DsButton>
</div>
</template>Buttons inherit colors from the design system tokens. Available colors include:
- primary (default): Brand primary color for main actions
- secondary: Secondary brand color for supporting actions
- success: Green color for positive or successful actions
- info: Blue color for informational actions
- warning: Amber/orange color for cautionary actions
- error: Red color for destructive or error-related actions
States
Disabled
<template>
<div class="button-group">
<DsButton disabled>Disabled</DsButton>
<DsButton variant="outlined" disabled>Outlined Disabled</DsButton>
<DsButton variant="text" disabled>Text Disabled</DsButton>
</div>
</template>Disabled buttons prevent user interaction and indicate unavailable actions:
- Do not respond to clicks or keyboard events
- Display with reduced opacity
- Show a "not-allowed" cursor on hover
- Maintain accessibility with
disabledattribute for screen readers
Loading
<template>
<div class="button-group">
<DsButton :loading="isLoading">Submit</DsButton>
<DsButton :loading="isLoading" variant="outlined">Save</DsButton>
<DsButton :loading="isLoading" variant="text">Cancel</DsButton>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isLoading = ref(false);
// Example: Toggle loading state
const handleSubmit = async () => {
isLoading.value = true;
await someApiCall();
isLoading.value = false;
};
</script>The loading state provides visual feedback during async operations:
- Displays a circular progress indicator
- Automatically disables the button to prevent multiple submissions
- Works seamlessly with all variants and colors
- Maintains button dimensions to prevent layout shift
Icon Buttons
<template>
<div class="button-group">
<DsButton icon aria-label="Close" variant="elevated">
<v-icon icon="mdi-close" />
</DsButton>
<DsButton icon aria-label="Edit" variant="flat">
<v-icon icon="mdi-pencil" />
</DsButton>
<DsButton icon aria-label="Share" variant="tonal">
<v-icon icon="mdi-share-variant" />
</DsButton>
<DsButton icon aria-label="Favorite" variant="outlined">
<v-icon icon="mdi-heart" />
</DsButton>
<DsButton icon aria-label="Settings" variant="text">
<v-icon icon="mdi-cog" />
</DsButton>
</div>
</template>Icon buttons are square buttons (48px × 48px) designed for icon-only actions. Use the :icon prop to enable icon button styling.
When to use:
- Close/dismiss actions
- Common actions (edit, delete, share, favorite)
- Toolbar actions
- Compact UI where space is limited
- Actions where icons are universally understood
Important: Always provide an aria-label for screen readers when using icon-only buttons.
Icon Button Colors
<template>
<div class="button-group">
<DsButton icon aria-label="Add" color="primary">
<v-icon icon="mdi-plus" />
</DsButton>
<DsButton icon aria-label="Delete" color="error">
<v-icon icon="mdi-delete" />
</DsButton>
<DsButton icon aria-label="Check" color="success">
<v-icon icon="mdi-check" />
</DsButton>
<DsButton icon aria-label="Alert" color="warning">
<v-icon icon="mdi-alert" />
</DsButton>
<DsButton icon aria-label="Info" color="info">
<v-icon icon="mdi-information" />
</DsButton>
</div>
</template>Icon buttons work with all color options to convey semantic meaning.
Icon Button States
<template>
<div class="button-group">
<!-- Active -->
<DsButton icon aria-label="Save changes">
<v-icon icon="mdi-content-save" />
</DsButton>
<!-- Disabled -->
<DsButton icon aria-label="Cannot save" :disabled="true">
<v-icon icon="mdi-content-save" />
</DsButton>
<!-- Loading -->
<DsButton icon aria-label="Saving changes" :loading="isSaving">
<v-icon icon="mdi-content-save" />
</DsButton>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isSaving = ref(false);
</script>Icon buttons support disabled and loading states just like regular buttons.
Common Icon Button Patterns
Dialog Header with Close Button:
Dialog Title
<template>
<div class="dialog-header">
<h3>Dialog Title</h3>
<DsButton icon aria-label="Close dialog" variant="text" @click="closeDialog">
<v-icon icon="mdi-close" />
</DsButton>
</div>
</template>
<script setup>
const closeDialog = () => {
// Handle dialog close
};
</script>Toolbar Actions:
<template>
<div class="toolbar">
<DsButton icon aria-label="Add item" variant="tonal" @click="addItem">
<v-icon icon="mdi-plus" />
</DsButton>
<DsButton icon aria-label="Edit" variant="tonal" @click="editItem">
<v-icon icon="mdi-pencil" />
</DsButton>
<DsButton icon aria-label="Delete" variant="tonal" color="error" @click="deleteItem">
<v-icon icon="mdi-delete" />
</DsButton>
</div>
</template>
<script setup>
const addItem = () => console.log('Add item');
const editItem = () => console.log('Edit item');
const deleteItem = () => console.log('Delete item');
</script>Card Actions:
<template>
<div class="card-actions">
<DsButton icon aria-label="Favorite" variant="text" @click="toggleFavorite">
<v-icon :icon="isFavorite ? 'mdi-heart' : 'mdi-heart-outline'" />
</DsButton>
<DsButton icon aria-label="Share" variant="text" @click="share">
<v-icon icon="mdi-share-variant" />
</DsButton>
<DsButton icon aria-label="More options" variant="text" @click="showMenu">
<v-icon icon="mdi-dots-vertical" />
</DsButton>
</div>
</template>
<script setup>
import { ref } from 'vue';
const isFavorite = ref(false);
const toggleFavorite = () => isFavorite.value = !isFavorite.value;
const share = () => console.log('Share');
const showMenu = () => console.log('Show menu');
</script>API Reference
Props
| Name | Type | Default | Description |
|---|---|---|---|
| variant | String | elevated | Button style: 'elevated', 'flat', 'tonal', 'outlined', or 'text' |
| color | String | primary | Color from design tokens (primary, secondary, error, warning, etc) |
| disabled | Boolean | false | Disables button interaction |
| loading | Boolean | false | Shows loading spinner and disables button |
| size | String | md | Button size: 'sm', 'md', or 'lg' |
| icon | Boolean | false | Renders button as icon-only with fixed 48px × 48px dimensions |
Events
| Event | Description |
|---|---|
| click | Emitted when button is clicked (if not disabled) |
Slots
| Name | Description |
|---|---|
| default | Button label |
Guidelines
Do
- Use elevated buttons for primary, high-emphasis actions
- Use outlined or text buttons for secondary actions
- Provide clear, action-oriented button labels (e.g., "Save Changes", not just "Save")
- Use icon buttons with
aria-labelfor all icon-only buttons - Use loading state for async operations to provide feedback
- Ensure adequate spacing between buttons in button groups
Don't
- Use too many elevated buttons on one screen - reserve for primary actions
- Use vague labels like "Click Here" or "Submit" without context
- Create icon buttons without
aria-labelattributes - Use disabled state as the only indicator - provide helper text explaining why
- Make buttons too small for touch targets (minimum 48×48px)
- Use buttons for navigation - use links or router-links instead
Accessibility
The Button component is built with WCAG 2.1 AAA compliance in mind:
- Keyboard Navigation: Fully accessible via Tab key, activated with Enter or Space
- Focus States: Clear, high-contrast focus indicators
- Semantic HTML: Uses native
<button>elements for proper screen reader support - Color Contrast: All color variants meet AAA contrast requirements
Icon Button Accessibility
Icon-only buttons require special attention for screen reader users:
Always provide an aria-label:
<!-- Good: Screen readers announce "Close dialog" -->
<ds-button icon aria-label="Close dialog">
<v-icon icon="mdi-close" />
</ds-button>
<!-- Bad: Screen readers cannot identify the button's purpose -->
<ds-button icon>
<v-icon icon="mdi-close" />
</ds-button>Use descriptive labels that explain the action:
<!-- Good: Clear, action-oriented -->
<ds-button icon aria-label="Delete user account">
<v-icon icon="mdi-delete" />
</ds-button>
<!-- Less helpful: Too generic -->
<ds-button icon aria-label="Delete">
<v-icon icon="mdi-delete" />
</ds-button>Loading State Accessibility
When a button is in a loading state:
- The button is automatically disabled
- Screen readers announce the loading state
- The loading spinner has appropriate ARIA attributes
<ds-button :loading="isSubmitting" aria-label="Submit form">
Submit
</ds-button>Focus Management
- Buttons receive keyboard focus with the Tab key
- Focused buttons can be activated with Enter or Space
- Focus order follows DOM order
- Focus indicators meet WCAG AAA contrast requirements (3:1 minimum)
Usage Examples
Basic Usage
<template>
<ds-button @click="handleClick">Save Changes</ds-button>
</template>
<script>
export default {
methods: {
handleClick() {
console.log('Button clicked');
},
},
};
</script><template>
<DsButton @click="handleClick">Save Changes</DsButton>
</template>
<script setup>
const handleClick = () => {
console.log('Button clicked');
alert('Changes saved!');
};
</script>With Router Link
<template>
<ds-button @click="$router.push('/dashboard')">
Go to Dashboard
</ds-button>
</template><template>
<DsButton @click="navigateToDashboard">
Go to Dashboard
</DsButton>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const navigateToDashboard = () => {
router.push('/dashboard');
};
</script>Button Groups
<template>
<div class="d-flex gap-2">
<ds-button variant="outlined">Cancel</ds-button>
<ds-button>Save</ds-button>
</div>
</template><template>
<div class="button-group">
<DsButton variant="outlined" @click="handleCancel">Cancel</DsButton>
<DsButton @click="handleSave">Save</DsButton>
</div>
</template>
<script setup>
const handleCancel = () => {
console.log('Cancelled');
};
const handleSave = () => {
console.log('Saved');
};
</script>
