Skip to content

Button

Flexible button component with multiple variants, sizes and states. Supports keyboard interactions and loading states.

Examples with icons

Icons

Buttons with icons

vue
<script setup>
import { PlusIcon, ArrowRightIcon, HeartIcon, TrashIcon } from '@heroicons/vue/24/outline'
</script>

<template>
  <!-- Icon before text -->
  <SuButton variant="primary" :icon="PlusIcon" iconDisplay="left">
    Add
  </SuButton>
  
  <!-- Icon after text -->
  <SuButton variant="secondary" :icon="ArrowRightIcon" iconDisplay="right">
    Next
  </SuButton>
  
  <!-- Icon only (requires aria-label) -->
  <SuButton variant="outline" :icon="HeartIcon" iconDisplay="only" aria-label="Like" />
  <SuButton variant="ghost" :icon="TrashIcon" iconDisplay="only" aria-label="Delete" />
</template>

Variants

Available variants

vue
<template>
  <SuButton variant="primary">Primary</SuButton>
  <SuButton variant="secondary">Secondary</SuButton>
  <SuButton variant="outline">Outline</SuButton>
  <SuButton variant="ghost">Ghost</SuButton>
</template>

Sizes

Available sizes

vue
<template>
  <SuButton size="sm">Small</SuButton>
  <SuButton size="md">Medium</SuButton>
  <SuButton size="lg">Large</SuButton>
</template>

States

Button states

vue
<template>
  <SuButton>Normal</SuButton>
  <SuButton :disabled="true">Disabled</SuButton>
  <SuButton :loading="true">Loading</SuButton>
</template>

Full width

Full width button

vue
<template>
  <SuButton :block="true">Full width button</SuButton>
  <SuButton variant="outline" :block="true">Outline full width</SuButton>
</template>

API

Props

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'outline' | 'ghost''primary'Button visual variant
size'sm' | 'md' | 'lg''md'Button size
disabledbooleanfalseDisable the button
loadingbooleanfalseShow loading spinner
blockbooleanfalseTake full available width
iconComponentundefinedIcon to display
iconDisplay'left' | 'right' | 'only''left'Icon position
ariaLabelstringundefinedAccessible label for screen readers
ariaDescribedBystringundefinedID of element describing the button
ariaExpandedbooleanundefinedIndicates if controlled element is expanded
ariaPressedbooleanundefinedIndicates pressed state of toggle button
rolestringundefinedCustom ARIA role
tabIndexnumber0Tab order

Events

EventTypeDescription
@click(event: MouseEvent) => voidEmitted when button is clicked
@focus(event: FocusEvent) => voidEmitted when button receives focus
@blur(event: FocusEvent) => voidEmitted when button loses focus
@keydown(event: KeyboardEvent) => voidEmitted when key is pressed

Slots

SlotDescription
defaultButton content

Accessibility

The Button component follows WCAG 2.1 AA standards:

✅ Accessibility features

  • Keyboard navigation: Support for Enter and Space keys
  • Visible focus: Clear and contrasted focus indicator
  • Minimum sizes: Respects minimum touch target sizes (44px)
  • Contrast: WCAG AA compliant contrast ratios (4.5:1 minimum)
  • ARIA states: Complete support for ARIA attributes
  • Screen readers: Accessible labels and descriptions
  • Reduced motion: Respects prefers-reduced-motion
  • Dark mode: Adapted contrast in dark mode
  • High contrast: Support for prefers-contrast: high
  • Accessible icons: Icons marked with aria-hidden="true"

🎯 Best practices

vue
<!-- Button with icon only (REQUIRED: aria-label) -->
<SuButton :icon="TrashIcon" iconDisplay="only" aria-label="Delete item" />

<!-- Button with icon and text -->
<SuButton :icon="PlusIcon" iconDisplay="left">Add item</SuButton>

<!-- Button with accessible label -->
<SuButton aria-label="Delete item">
  <TrashIcon />
</SuButton>

<!-- Button with description -->
<SuButton aria-describedby="save-help">
  Save
</SuButton>
<div id="save-help">Auto-saves every 30 seconds</div>

<!-- Toggle button -->
<SuButton 
  :aria-pressed="isActive"
  @click="toggle"
>
  {{ isActive ? 'Active' : 'Inactive' }}
</SuButton>

Usage examples

Using with Heroicons

The most common Heroicons are available globally:

vue
<template>
  <!-- Icon before text -->
  <SuButton 
    variant="primary" 
    :icon="PlusIcon"
    iconDisplay="left"
  >
    Add
  </SuButton>
  
  <!-- Icon after text -->
  <SuButton 
    variant="secondary" 
    :icon="ArrowRightIcon"
    iconDisplay="right"
  >
    Continue
  </SuButton>
  
  <!-- Icon only with accessible labels -->
  <SuButton 
    variant="ghost" 
    :icon="HeartIcon"
    iconDisplay="only"
    aria-label="Like this post"
  />
  
  <SuButton 
    variant="outline" 
    :icon="ShareIcon"
    iconDisplay="only"
    aria-label="Share"
  />
  
  <SuButton 
    variant="primary" 
    :icon="ArrowDownTrayIcon"
    iconDisplay="only"
    aria-label="Download file"
  />
</template>

Custom icon import

To use other Heroicons:

vue
<script setup>
import { CogIcon } from '@heroicons/vue/24/outline'
</script>

<template>
  <SuButton :icon="CogIcon" iconDisplay="left">
    Settings
  </SuButton>
</template>

Button with click handler

vue
<script setup>
const handleClick = () => {
  console.log('Button clicked!')
}
</script>

<template>
  <SuButton @click="handleClick">
    Click me
  </SuButton>
</template>

Form submission button

vue
<script setup>
import { ref } from 'vue'

const isSubmitting = ref(false)

const handleSubmit = async () => {
  isSubmitting.value = true
  try {
    // Submission logic
    await submitForm()
  } finally {
    isSubmitting.value = false
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <!-- Form fields -->
    <SuButton 
      type="submit" 
      :loading="isSubmitting"
      :disabled="isSubmitting"
      :aria-label="isSubmitting ? 'Submitting...' : 'Submit form'"
    >
      {{ isSubmitting ? 'Submitting...' : 'Submit' }}
    </SuButton>
  </form>
</template>

Global configuration

You can configure default button values when installing the design system:

js
// main.js
import { createApp } from 'vue'
import SurgeUpDS from '@surgeui/ds-vue'
import '@surgeui/ds-vue/style.css'

const app = createApp(App)

// Configure default values
app.use(SurgeUpDS, {
  buttonRadius: 'lg',     // All buttons will have large radius by default
  buttonVariant: 'outline', // All buttons will be outline by default
  buttonSize: 'lg'        // All buttons will be large by default
})

Usage with global configuration

vue
<template>
  <!-- These buttons will use globally configured values -->
  <SuButton>Button with global config</SuButton>
  <SuButton variant="default" size="default" radius="default">Same thing explicitly</SuButton>
  
  <!-- These buttons override global configuration -->
  <SuButton variant="primary">Specific variant</SuButton>
  <SuButton size="sm">Specific size</SuButton>
  <SuButton radius="none">Specific radius</SuButton>
</template>

Available configuration options

OptionTypeDescription
buttonRadius'none' | 'sm' | 'md' | 'lg' | 'xl'Default border radius
buttonVariant'primary' | 'secondary' | 'outline' | 'ghost'Default variant
buttonSize'sm' | 'md' | 'lg'Default size

Publié sous licence MIT.