# Button

> A clickable action element with color, variant and size options.

## Usage


```vue
<template>
  <SButton>
    Click me
  </SButton>
</template>
```


### Variants

`subtle` combines the tinted background of `soft` with a border. `link` uses
the same text color transitions as `text` and adds an underline on hover.


```vue
<template>
  <div class="flex flex-wrap items-center gap-3">
    <SButton variant="solid">
      Solid
    </SButton>
    <SButton variant="soft">
      Soft
    </SButton>
    <SButton variant="subtle">
      Subtle
    </SButton>
    <SButton variant="outline">
      Outline
    </SButton>
    <SButton variant="ghost">
      Ghost
    </SButton>
    <SButton variant="text">
      Text
    </SButton>
    <SButton variant="link">
      Link
    </SButton>
  </div>
</template>
```


### Colors

Every color across every variant, so it's easy to compare both within a
color (is soft/outline/ghost legible?) and across colors (do they stay
distinguishable at a glance?).


```vue
<script setup lang="ts">
const colors = ['primary', 'secondary', 'success', 'danger', 'info', 'warning', 'neutral'] as const
</script>

<template>
  <div class="flex flex-col gap-3">
    <div v-for="color in colors" :key="color" class="flex flex-wrap items-center gap-3">
      <span class="w-20 shrink-0 text-sm text-[var(--selaras-resolved-text-muted)] capitalize">{{ color }}</span>
      <SButton :color="color" variant="solid">
        Solid
      </SButton>
      <SButton :color="color" variant="soft">
        Soft
      </SButton>
      <SButton :color="color" variant="subtle">
        Subtle
      </SButton>
      <SButton :color="color" variant="outline">
        Outline
      </SButton>
      <SButton :color="color" variant="ghost">
        Ghost
      </SButton>
      <SButton :color="color" variant="text">
        Text
      </SButton>
      <SButton :color="color" variant="link">
        Link
      </SButton>
    </div>
  </div>
</template>
```


### Icons

`icon` and `trailingIcon` add an icon before or after the label - any
registered icon name works, not just `hugeicons:*`.


```vue
<template>
  <div class="flex flex-wrap items-center gap-3">
    <SButton icon="hugeicons:download-01">
      Download
    </SButton>
    <SButton trailing-icon="hugeicons:arrow-right-01">
      Continue
    </SButton>
  </div>
</template>
```


```vue-html
<SButton icon="hugeicons:download-01">Download</SButton>
<SButton trailing-icon="hugeicons:arrow-right-01">Continue</SButton>
```

### Icon only

There's no separate `iconOnly` prop - pass `icon` with no default slot
content and it shapes itself to a square automatically (matching its own
`size`), rather than requiring an explicit flag you could forget to set.
Always add `aria-label` yourself when there's no visible text - it falls
through to the root element like any other attr (see
[Accessibility](#accessibility) below).


```vue
<template>
  <div class="flex items-center gap-3">
    <SButton icon="hugeicons:settings-01" variant="outline" aria-label="Settings" />
    <SButton icon="hugeicons:delete-02" color="danger" variant="ghost" aria-label="Delete" />
  </div>
</template>
```


```vue-html
<SButton icon="hugeicons:settings-01" variant="outline" aria-label="Settings" />
```

### Square

Icon-only buttons already shape themselves to a square automatically (see
above) - `square` is for when the content isn't an icon but should still get
that same equal-width/height treatment, like a page number or a calendar day.
Without it, each button sizes to its own text width and a row of them (a
pagination bar, a calendar grid) won't line up:


```vue
<template>
  <div class="flex flex-wrap items-start gap-6">
    <div class="flex flex-col gap-2">
      <span class="text-xs font-medium text-[var(--selaras-resolved-text-muted)]">Default sizing</span>
      <div class="flex gap-1">
        <SButton variant="outline" color="neutral">
          1
        </SButton>
        <SButton variant="outline" color="neutral">
          31
        </SButton>
      </div>
    </div>
    <div class="flex flex-col gap-2">
      <span class="text-xs font-medium text-[var(--selaras-resolved-text-muted)]">Square</span>
      <div class="flex gap-1">
        <SButton variant="outline" color="neutral" square>
          1
        </SButton>
        <SButton variant="outline" color="neutral" square>
          31
        </SButton>
      </div>
    </div>
  </div>
</template>
```


```vue-html
<SButton square>1</SButton>
<SButton square>31</SButton>
```

### Text

`text` never paints a background, at any state - only the label/icon color
shifts on hover. Meant for an action button that sits tight against another
control's own border, like a field's clear button - `ghost`'s hover-fill
would otherwise visually compete with that border right at the seam:

The border in this comparison belongs to the illustrative field, not either
button.


```vue
<template>
  <div class="flex flex-wrap gap-6">
    <div class="flex flex-col gap-1.5">
      <span class="text-xs font-medium text-[var(--selaras-resolved-text-muted)]">Ghost clear action</span>
      <div class="flex h-10 w-52 items-center gap-2 rounded-[var(--selaras-resolved-radius-md)] px-3 ring-1 ring-inset ring-[var(--selaras-resolved-border-default)]">
        <span class="flex-1 text-sm text-[var(--selaras-resolved-text-muted)]">Example field</span>
        <SButton icon="hugeicons:cancel-01" size="sm" variant="ghost" color="neutral" aria-label="Clear" />
      </div>
    </div>
    <div class="flex flex-col gap-1.5">
      <span class="text-xs font-medium text-[var(--selaras-resolved-text-muted)]">Text clear action</span>
      <div class="flex h-10 w-52 items-center gap-2 rounded-[var(--selaras-resolved-radius-md)] px-3 ring-1 ring-inset ring-[var(--selaras-resolved-border-default)]">
        <span class="flex-1 text-sm text-[var(--selaras-resolved-text-muted)]">Example field</span>
        <SButton icon="hugeicons:cancel-01" size="sm" variant="text" color="neutral" aria-label="Clear" />
      </div>
    </div>
  </div>
</template>
```


```vue-html
<div class="flex items-center rounded-[var(--selaras-resolved-radius-md)] ring-1 ring-inset ring-[var(--selaras-resolved-border-default)]">
  <span>Example field</span>
  <SButton icon="hugeicons:cancel-01" variant="text" aria-label="Clear" />
</div>
```

`link` is the underlined text-action variant. Use it for an action that
navigates; `text` is the unadorned text action.

### Loading

`loading` swaps the leading icon for a spinner. It doesn't imply `disabled` -
combine both if a busy button shouldn't be clickable, the same split
[Select](/components/forms/select#states) and
[Autocomplete](/components/forms/autocomplete#sizes-and-states) already use
for their own `loading` prop.


```vue
<script setup lang="ts">
const saving = ref(false)

function save() {
  saving.value = true
  setTimeout(() => {
    saving.value = false
  }, 1500)
}
</script>

<template>
  <SButton icon="hugeicons:floppy-disk" :loading="saving" :disabled="saving" @click="save">
    {{ saving ? 'Saving...' : 'Save' }}
  </SButton>
</template>
```


```vue-html
<SButton icon="hugeicons:floppy-disk" :loading="saving" :disabled="saving" @click="save">
  {{ saving ? 'Saving...' : 'Save' }}
</SButton>
```

### As a link

`to` renders a NuxtLink for client-side navigation. Use `as="a"` with
`href` for external links, or set `as` explicitly to override automatic
routing. Disabled routed buttons expose `aria-disabled`, leave the tab order,
and ignore clicks.


```vue
<template>
  <SButton as="a" href="https://github.com" target="_blank" rel="noopener" variant="outline" trailing-icon="hugeicons:square-arrow-up-right">
    Open a link
  </SButton>
</template>
```


```vue-html
<SButton as="a" href="https://github.com" target="_blank" rel="noopener" trailing-icon="hugeicons:square-arrow-up-right">
  Open a link
</SButton>
<SButton to="/account">Account</SButton>
<SButton variant="link" to="/forgot-password">Forgot password?</SButton>
```

For the common internal-route case, `to` handles NuxtLink automatically. If
you need another routing component, pass its resolved component reference in
`as` - `Primitive` renders the explicit component via `h()`:

```vue
<script setup lang="ts">
const NuxtLinkComponent = resolveComponent('NuxtLink')
</script>

<template>
  <SButton :as="NuxtLinkComponent" to="/components/elements/button">
    Browse components
  </SButton>
</template>
```

### Raised

`raised` adds `--selaras-resolved-shadow-md`, independent of `variant` - useful for a solid
button that needs to lift off a busy background, or even a `soft`/`ghost`
one that wants some elevation without switching to `solid`.


```vue
<template>
  <div class="flex flex-wrap items-center gap-3">
    <SButton raised>
      Raised solid
    </SButton>
    <SButton raised variant="soft">
      Raised soft
    </SButton>
  </div>
</template>
```


```vue-html
<SButton raised>Raised solid</SButton>
<SButton raised variant="soft">Raised soft</SButton>
```

### Badge

There's no built-in badge slot - overlay a real [Badge](/components/elements/badge)
on an icon-only button with a `relative`/`absolute` pair:


```vue
<template>
  <div class="relative inline-flex">
    <SButton variant="outline" icon="hugeicons:notification-01" aria-label="Notifications" />
    <SBadge label="3" color="danger" size="sm" class="absolute -top-1.5 -right-1.5" />
  </div>
</template>
```


```vue-html
<div class="relative inline-flex">
  <SButton variant="outline" icon="hugeicons:notification-01" aria-label="Notifications" />
  <SBadge label="3" color="danger" size="sm" class="absolute -top-1.5 -right-1.5" />
</div>
```

### Button group

Adjacent buttons visually joined into one unit are their own component -
see [ButtonGroup](/components/elements/button-group).

### Sizes and states

`size` takes `sm` / `md` / `lg`, `disabled` prevents interaction and dims the
button - see the [Props](#props) table below.

### Custom `:ui`

Every slot can be overridden without fighting the defaults — the override is
tailwind-merge'd, so conflicting utilities resolve instead of both applying.

```vue-html
<SButton :ui="{ base: 'rounded-full' }">Pill button</SButton>
```

To see exactly what you'd be overriding - the current default classes for
every slot and variant - here's `Button`'s own theme file:

<theme-source name="button">



</theme-source>

### Accessibility

A default `as="button"` gets native button semantics (keyboard-activatable
with <kbd>

Enter

</kbd>

/<kbd>

Space

</kbd>

, exposed as `role="button"`) for free -
nothing to configure. Rendering `as="a"` instead exposes link semantics and
keyboard behavior instead, which is exactly why [As a link](#as-a-link) exists
rather than styling an `<a>` to merely look like a button. An icon-only button
has no accessible name of its own - always pass `aria-label` in that case, as
shown above.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        as
      </code>
    </td>
    
    <td>
      <code>
        string | Component
      </code>
    </td>
    
    <td>
      <code>
        'button'
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        ColorRole
      </code>
      
       (built-in and registered roles)
    </td>
    
    <td>
      <code>
        primary
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        to
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        variant
      </code>
    </td>
    
    <td>
      <code>
        'solid' | 'soft' | 'subtle' | 'outline' | 'ghost' | 'text' | 'link'
      </code>
    </td>
    
    <td>
      <code>
        solid
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        size
      </code>
    </td>
    
    <td>
      <code>
        'sm' | 'md' | 'lg'
      </code>
    </td>
    
    <td>
      <code>
        md
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        block
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        raised
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        disabled
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        loading
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        icon
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        trailingIcon
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        square
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      -
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ui
      </code>
    </td>
    
    <td>
      <code>
        Partial<Record<'base' | 'leadingIcon' | 'trailingIcon', string | object>>
      </code>
    </td>
    
    <td>
      -
    </td>
  </tr>
</tbody>
</table>

## Slots

<table>
<thead>
  <tr>
    <th>
      Slot
    </th>
    
    <th>
      Props
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        icon
      </code>
    </td>
    
    <td>
      <code>
        { class }
      </code>
    </td>
    
    <td>
      Replaces the leading icon entirely; the scoped class carries the icon's resolved size classes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        trailing-icon
      </code>
    </td>
    
    <td>
      <code>
        { class }
      </code>
    </td>
    
    <td>
      Replaces the trailing icon entirely; the scoped class carries the icon's resolved size classes
    </td>
  </tr>
</tbody>
</table>
