System.Routing

Raya Button

A button that can act as a link or trigger an action. A faithful port of the Nuxt UI v4 button — same props, same slots, same color × variant matrix — running on plain Vue with tailwind-variants.

Installation

npx raya-ui@latest add raya-button

File Structure

your-project
components/ui/raya-button
RayaButton.vue
variants.ts
index.ts

Usage

Label

Use the default slot, or the `label` prop, to set the text of the button.

<RayaButton label="Button" />

<RayaButton>Button</RayaButton>

Color & Variant

Seven colors times six variants. `color` picks the palette, `variant` picks how much of it is painted.

<RayaButton color="neutral">Button</RayaButton>

<RayaButton color="neutral" variant="outline">Button</RayaButton>

<RayaButton color="error" variant="soft">Button</RayaButton>

Size

Sizes scale padding, font size and icon size at once.

<RayaButton size="xs">Button</RayaButton>
<RayaButton size="sm">Button</RayaButton>
<RayaButton size="md">Button</RayaButton>
<RayaButton size="lg">Button</RayaButton>
<RayaButton size="xl">Button</RayaButton>

Icon

Pass any `@lucide/vue` component — or a CSS icon class like `i-lucide-rocket` if you use Iconify. Use `leading-icon` and `trailing-icon` to set a different icon per side. With no label, the button becomes square automatically.

<script setup lang="ts">
import { Rocket, ArrowRight, Search } from '@lucide/vue'
</script>

<template>
  <RayaButton :icon="Rocket">Button</RayaButton>

  <RayaButton :trailing-icon="ArrowRight">Button</RayaButton>

  <!-- Icon-only: square padding, no label needed -->
  <RayaButton :icon="Search" />
</template>

Avatar

The `avatar` prop renders an image on the leading side, sized to match the button. Without `src`, `text` is rendered as initials.

<RayaButton
  :avatar="{ src: 'https://github.com/nuxt.png', loading: 'lazy' }"
  color="neutral"
  variant="outline"
>
  Button
</RayaButton>

Link

Set `to` (or `href`) and the button renders as a link: NuxtLink when available, then RouterLink, then a plain `<a>`. External URLs always render as `<a>`, and `target="_blank"` adds `rel="noopener noreferrer"` for you.

<RayaButton to="https://github.com/nuxt/ui" target="_blank">
  Button
</RayaButton>

<RayaButton to="/docs/introduction">Docs</RayaButton>

Active state

A link is active when it matches the current route; `active` forces it. Restyle the active state with `active-color` and `active-variant`, or with `active-class` and `inactive-class`.

<RayaButton
  active
  color="neutral"
  variant="outline"
  active-color="primary"
  active-variant="solid"
>
  Button
</RayaButton>

<RayaButton active active-class="font-bold" inactive-class="font-light">
  Button
</RayaButton>

Loading

The `loading` prop swaps in a spinner and disables the button. `loading-auto` does it for you while the `@click` handler's promise is pending, and `loading-icon` swaps the spinner.

<script setup lang="ts">
import { Loader } from '@lucide/vue'

async function onClick() {
  return new Promise<void>(res => setTimeout(res, 1000))
}
</script>

<template>
  <RayaButton loading :trailing="false">Button</RayaButton>

  <RayaButton loading-auto @click="onClick">Button</RayaButton>

  <RayaButton loading :loading-icon="Loader">Button</RayaButton>
</template>

Block & square

`block` stretches the button to full width and pushes the trailing icon to the far edge. `square` forces equal padding on all sides.

<RayaButton block :trailing-icon="ArrowRight">Button</RayaButton>

<RayaButton square :icon="Search" />

The class and ui props

`class` overrides the base slot. `ui` targets any individual slot.

<RayaButton class="font-bold rounded-full">Button</RayaButton>

<RayaButton
  :icon="Rocket"
  color="neutral"
  variant="outline"
  :ui="{ leadingIcon: 'text-primary' }"
>
  Button
</RayaButton>

CSS Variables

The button paints itself entirely from these tokens, so it inherits your theme instead of hard-coding colors. Raw values live on :root and .dark; the @theme inline block maps them onto Tailwind's utility namespaces. Splitting them that way is what lets text-inverted and bg-inverted resolve to opposite colors while both still accept opacity modifiers such as bg-inverted/90. Every one of them is listed in the css tab above.

--ui-text-dimmed
text-dimmed

Lowest-emphasis text.

--ui-text-muted
text-muted

Muted text — the resting color of the neutral link variant.

--ui-text-toned
text-toned

One step above muted.

--ui-text
text-default

Default body text — used by every neutral variant except solid.

--ui-text-highlighted
text-highlighted

Strongest text.

--ui-text-inverted
text-inverted

Text on top of an inverted surface — the label color of the neutral solid button.

--ui-bg
bg-default

Page background — the resting fill of the neutral outline button.

--ui-bg-muted
bg-muted

Muted surface.

--ui-bg-elevated
bg-elevated

Raised surface — the fill of the neutral soft and subtle buttons.

--ui-bg-accented
bg-accented

One step above elevated — the hover fill of the neutral variants.

--ui-bg-inverted
bg-inverted

Inverted surface — the fill of the neutral solid button.

--ui-border
border-default / ring-default

Default border color.

--ui-border-muted
border-muted / ring-muted

Quieter border color.

--ui-border-accented
ring-accented

The ring of the neutral outline and subtle buttons.

--ui-border-inverted
ring-inverted / outline-inverted

The focus ring and focus outline of the neutral variants.

--color-primary-content
text-primary-content

Label color on a solid primary button. Maps to `--primary-foreground`.

--color-secondary-content
text-secondary-content

Label color on a solid secondary button. Maps to `--secondary-foreground`.

--success / --success-foreground
bg-success / text-success-content

The success palette and its label color.

--info / --info-foreground
bg-info / text-info-content

The info palette and its label color.

--warning / --warning-foreground
bg-warning / text-warning-content

The warning palette and its label color.

--error / --error-foreground
bg-error / text-error-content

The error palette and its label color. Maps to `--destructive` by default.

Note: solid variants use a per-color foreground (text-primary-content, text-success-content, …) rather than Nuxt UI's single text-inverted. Nuxt UI can share one inverted token because every brand color there is a 500/400 shade; this theme pairs each color with its own foreground, so the pairing has to be per color. The neutral color still uses text-inverted on bg-inverted.

API Reference

Props

Every native <button> attribute is forwarded as well.

label
string
—

Text rendered inside the button. Ignored when the default slot is used.

color
"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
"primary"

The semantic color palette of the button.

variant
"solid" | "outline" | "soft" | "subtle" | "ghost" | "link"
"solid"

The visual style of the button.

size
"xs" | "sm" | "md" | "lg" | "xl"
"md"

The size of the button — drives padding, font size and icon size together.

active-color
color
—

Color applied while the button is active (an active link, or `active` set explicitly).

active-variant
variant
—

Variant applied while the button is active.

square
boolean
false

Equal padding on all sides. Applied automatically when there is no label and no default slot.

block
boolean
false

Render the button full width, centering its content and pushing the trailing icon to the end.

field-group
"horizontal" | "vertical"
—

Flatten the shared edges when several buttons sit in one group.

icon
Component | string
—

Icon displayed on the side chosen by `leading` / `trailing`. Accepts a component (any @lucide/vue icon) or a CSS icon class such as `i-lucide-rocket`.

leading-icon
Component | string
—

Icon pinned to the left side.

trailing-icon
Component | string
—

Icon pinned to the right side.

leading
boolean
false

Force `icon` onto the left side.

trailing
boolean
false

Force `icon` onto the right side.

avatar
{ src?, alt?, text?, loading? }
—

Avatar rendered on the left side. Falls back to `text` initials when `src` is absent.

loading
boolean
false

Show the spinning loading icon and disable the button.

loading-auto
boolean
false

Enter the loading state automatically while the `@click` promise is pending.

loading-icon
Component | string
LoaderCircle

The icon used for the loading state.

disabled
boolean
false

Disable the button. Links receive `aria-disabled` instead of the `disabled` attribute.

type
"button" | "submit" | "reset"
"button"

The native type, used when the button is not a link.

as
Component | string
"button"

The element or component to render as when the button is not a link.

as-child
boolean
false

Merge the props onto the single child element instead of rendering a wrapper.

to
string | RouteLocation
—

Turns the button into a link. Uses NuxtLink, then RouterLink, then a plain `<a>` — whichever is available.

href
string
—

Alias for `to`. Ignored when `to` is set.

target
string
—

Where to open the link. `_blank` adds `rel="noopener noreferrer"` automatically.

rel
string
—

Overrides the automatic `rel` attribute.

external
boolean
—

Force the link to render as a plain `<a>` (true) or as a router link (false).

replace
boolean
false

Call `router.replace` instead of `router.push`.

download
boolean | string
—

Download attribute, forwarded to plain `<a>` links.

active
boolean
—

Force the active state independently of the current route.

exact
boolean
false

Only active when the current path is an exact match.

exact-query
boolean
false

Also require the query string to match.

exact-hash
boolean
false

Also require the hash to match.

active-class
string
—

Class applied while the button is active.

inactive-class
string
—

Class applied while the button is inactive.

class
string
—

Merged onto the base slot, overriding the generated classes.

ui
{ base?, label?, leadingIcon?, leadingAvatar?, trailingIcon? }
—

Override the classes of any individual slot.

Slots

leading

Replaces the leading icon or avatar.

default

The content of the button. Takes precedence over `label`.

trailing

Replaces the trailing icon.

Theme slots

The theme lives in variants.ts as a tailwind-variants definition. Each of these slots can be overridden per instance with the ui prop, or globally by editing that file.

base

The button element itself — layout, radius, padding, colors and states.

label

The `<span>` wrapping the `label` prop.

leadingIcon

The leading icon, spun by `animate-spin` while loading.

leadingAvatar

The leading avatar image or initials.

leadingAvatarSize

The size class applied to the avatar for each button size.

trailingIcon

The trailing icon, spun while loading when `trailing` is set.

Settings