Skip to content

Button

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

Usage

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.

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?).

primary
secondary
success
danger
info
warning
neutral

Icons

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

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 below).

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:

Default sizing
Square
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.

Ghost clear action
Example field
Text clear action
Example field
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 and Autocomplete already use for their own loading prop.

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

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-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-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 on an icon-only button with a relative/absolute pair:

3
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.

Sizes and states

size takes sm / md / lg, disabled prevents interaction and dims the button - see the 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 "button" not found

Accessibility

A default as="button" gets native button semantics (keyboard-activatable with Enter/Space, 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 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

PropTypeDefault
asstring | Component'button'
colorColorRole (built-in and registered roles)primary
tostring-
variant'solid' | 'soft' | 'subtle' | 'outline' | 'ghost' | 'text' | 'link'solid
size'sm' | 'md' | 'lg'md
blockbooleanfalse
raisedbooleanfalse
disabledbooleanfalse
loadingbooleanfalse
iconstring-
trailingIconstring-
squareboolean-
uiPartial<Record<'base' | 'leadingIcon' | 'trailingIcon', string | object>>-

Slots

SlotPropsDescription
icon{ class }Replaces the leading icon entirely; the scoped class carries the icon's resolved size classes
trailing-icon{ class }Replaces the trailing icon entirely; the scoped class carries the icon's resolved size classes
Edit this page