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-buttonFile Structure
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-dimmedtext-dimmedLowest-emphasis text.
--ui-text-mutedtext-mutedMuted text — the resting color of the neutral link variant.
--ui-text-tonedtext-tonedOne step above muted.
--ui-texttext-defaultDefault body text — used by every neutral variant except solid.
--ui-text-highlightedtext-highlightedStrongest text.
--ui-text-invertedtext-invertedText on top of an inverted surface — the label color of the neutral solid button.
--ui-bgbg-defaultPage background — the resting fill of the neutral outline button.
--ui-bg-mutedbg-mutedMuted surface.
--ui-bg-elevatedbg-elevatedRaised surface — the fill of the neutral soft and subtle buttons.
--ui-bg-accentedbg-accentedOne step above elevated — the hover fill of the neutral variants.
--ui-bg-invertedbg-invertedInverted surface — the fill of the neutral solid button.
--ui-borderborder-default / ring-defaultDefault border color.
--ui-border-mutedborder-muted / ring-mutedQuieter border color.
--ui-border-accentedring-accentedThe ring of the neutral outline and subtle buttons.
--ui-border-invertedring-inverted / outline-invertedThe focus ring and focus outline of the neutral variants.
--color-primary-contenttext-primary-contentLabel color on a solid primary button. Maps to `--primary-foreground`.
--color-secondary-contenttext-secondary-contentLabel color on a solid secondary button. Maps to `--secondary-foreground`.
--success / --success-foregroundbg-success / text-success-contentThe success palette and its label color.
--info / --info-foregroundbg-info / text-info-contentThe info palette and its label color.
--warning / --warning-foregroundbg-warning / text-warning-contentThe warning palette and its label color.
--error / --error-foregroundbg-error / text-error-contentThe error palette and its label color. Maps to `--destructive` by default.
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.
labelstring—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-colorcolor—Color applied while the button is active (an active link, or `active` set explicitly).
active-variantvariant—Variant applied while the button is active.
squarebooleanfalseEqual padding on all sides. Applied automatically when there is no label and no default slot.
blockbooleanfalseRender 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.
iconComponent | 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-iconComponent | string—Icon pinned to the left side.
trailing-iconComponent | string—Icon pinned to the right side.
leadingbooleanfalseForce `icon` onto the left side.
trailingbooleanfalseForce `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.
loadingbooleanfalseShow the spinning loading icon and disable the button.
loading-autobooleanfalseEnter the loading state automatically while the `@click` promise is pending.
loading-iconComponent | stringLoaderCircleThe icon used for the loading state.
disabledbooleanfalseDisable 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.
asComponent | string"button"The element or component to render as when the button is not a link.
as-childbooleanfalseMerge the props onto the single child element instead of rendering a wrapper.
tostring | RouteLocation—Turns the button into a link. Uses NuxtLink, then RouterLink, then a plain `<a>` — whichever is available.
hrefstring—Alias for `to`. Ignored when `to` is set.
targetstring—Where to open the link. `_blank` adds `rel="noopener noreferrer"` automatically.
relstring—Overrides the automatic `rel` attribute.
externalboolean—Force the link to render as a plain `<a>` (true) or as a router link (false).
replacebooleanfalseCall `router.replace` instead of `router.push`.
downloadboolean | string—Download attribute, forwarded to plain `<a>` links.
activeboolean—Force the active state independently of the current route.
exactbooleanfalseOnly active when the current path is an exact match.
exact-querybooleanfalseAlso require the query string to match.
exact-hashbooleanfalseAlso require the hash to match.
active-classstring—Class applied while the button is active.
inactive-classstring—Class applied while the button is inactive.
classstring—Merged onto the base slot, overriding the generated classes.
ui{ base?, label?, leadingIcon?, leadingAvatar?, trailingIcon? }—Override the classes of any individual slot.
Slots
leadingReplaces the leading icon or avatar.
defaultThe content of the button. Takes precedence over `label`.
trailingReplaces 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.
baseThe button element itself — layout, radius, padding, colors and states.
labelThe `<span>` wrapping the `label` prop.
leadingIconThe leading icon, spun by `animate-spin` while loading.
leadingAvatarThe leading avatar image or initials.
leadingAvatarSizeThe size class applied to the avatar for each button size.
trailingIconThe trailing icon, spun while loading when `trailing` is set.
<script setup lang="ts">
import { RayaButton } from '@/components/ui/raya-button'
</script>
<template>
<RayaButton
variant="solid"
>
Raya Button
</RayaButton>
</template>