Examples
Basic
Prop | Default | Type | Description |
---|---|---|---|
label | - | string | The label of the button. |
You can also use default slot to set the button label. Refer to slots for more information.
Preview
Code
<template>
<NButton label="Button" />
</template>
Variant
Prop | Default | Type | Description |
---|---|---|---|
btn | solid | {variant} | The variant of the button. |
Variant | Description |
---|---|
solid | The default variant. |
outline | The outline variant. |
soft | The soft variant. |
ghost | The ghost variant. |
link | The link variant. |
text | The text variant. |
~ | The unstyle or base variant |
Preview
Code
<template>
<div class="flex flex-wrap gap-4">
<NButton
label="Button Solid"
btn="solid"
/>
<NButton
label="Button Outline"
btn="outline"
/>
<NButton
label="Button Soft"
btn="soft"
/>
<NButton
label="Button Ghost"
btn="ghost"
/>
<NButton
label="Button Link"
btn="link"
/>
<NButton
label="Button text"
btn="text"
/>
<NButton
label="Button Base"
btn="~"
/>
</div>
</template>
Color
Prop | Default | Type | Description |
---|---|---|---|
btn | {variant}-primary | {variant}-{color} | The color of the button. |
Preview
Code
Dynamic colors:
Color with states:Custom colors using utilities:
Static colors:<template>
<div class="flex flex-col gap-4">
<span class="text-sm font-medium">Dynamic colors:</span>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-4 md:flex-row">
<NButton
label="solid-primary"
btn="solid-primary"
/>
<NButton
label="outline-lime"
btn="outline-lime"
/>
<NButton
label="link-yellow"
btn="link-yellow"
/>
<NButton
label="soft-red"
btn="soft-red"
/>
<NButton
label="ghost-orange"
btn="ghost-orange"
/>
</div>
<NSeparator />
<span class="text-sm font-medium">Color with states:</span>
<div class="flex flex-col gap-4 md:flex-row">
<NButton
btn="solid-error hover:solid-success"
label="hover me"
/>
<NButton
label="you can add transition too"
class="transition delay-300 ease-in-out"
btn="soft-error hover:soft-success"
/>
<NButton
btn="active:outline-fuchsia"
label="click me"
/>
<NButton
label="click me"
btn="outline-pink focus:outline-blue"
/>
</div>
<NSeparator />
<span class="text-sm font-medium">Custom colors using utilities:</span>
<div class="flex flex-col gap-2 md:flex-row">
<NButton
btn="~"
class="from-primary to-$c-brand-next bg-gradient-to-r text-white hover:from-pink-500 hover:to-yellow-500"
label="gradient color"
/>
<NButton
btn="~"
class="from-primary-500 via-primary-600 to-primary-700 bg-gradient-to-r text-white shadow-lg shadow-primary-500/50 hover:bg-gradient-to-br dark:shadow-lg dark:shadow-primary-800/80 dark:focus:ring-primary-800"
label="glowing effect"
/>
</div>
</div>
<NSeparator />
<span class="text-sm font-medium">Static colors:</span>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2 md:flex-row">
<NButton
label="solid-gray"
btn="solid-gray"
/>
<NButton
label="soft-gray"
btn="soft-gray"
/>
<NButton
label="outline-gray"
btn="outline-gray"
/>
<NButton
label="ghost-gray"
btn="ghost-gray"
/>
<NButton
label="link-gray"
btn="link-gray"
/>
<NButton
label="text-gray"
btn="text-gray"
/>
</div>
<div class="flex flex-col gap-2 md:flex-row">
<NButton
label="solid-white"
btn="solid-white"
/>
<NButton
label="ghost-white"
btn="ghost-white"
/>
<NButton
label="outline-white"
btn="outline-white"
/>
</div>
<div class="flex flex-col gap-2 md:flex-row">
<NButton
label="solid-black"
btn="solid-black"
/>
<NButton
label="soft-black"
btn="soft-black"
/>
<NButton
label="link-black"
btn="link-black"
/>
<NButton
label="text-black"
btn="text-black"
/>
</div>
<div class="flex flex-col gap-2 md:flex-row">
<NButton
label="ghost-muted"
btn="ghost-muted"
/>
<NButton
label="link-muted"
btn="link-muted"
/>
<NButton
label="text-muted"
btn="text-muted"
/>
</div>
<div class="flex flex-col gap-2 md:flex-row">
<NButton
label="soft-accent"
btn="soft-accent"
/>
<NButton
label="text-accent"
btn="text-accent"
/>
<NButton
label="link-accent"
btn="link-accent"
/>
</div>
</div>
</div>
</template>
Size
Prop | Default | Type | Description |
---|---|---|---|
size | sm | string | Allows you to change the size of the button. |
🚀 Adjust input size freely using any size, breakpoints (e.g.,
sm:sm, xs:lg
), or states (e.g.,hover:lg, focus:3xl
).
The padding, icons, and text-size of the input scale are dynamically adjusted based on the size property. To customize the text-size and padding simultaneously, you can use utility classes.
Preview
Code
<template>
<div class="flex-1 space-x-4 space-y-2">
<NButton
size="xs"
label="button xs"
/>
<NButton
size="sm"
label="button sm (default)"
/>
<NButton
size="md"
label="button md"
/>
<NButton
size="lg"
label="button lg"
/>
<NButton
size="xl"
label="button xl"
/>
<NButton
size="3vw"
label="button 3vw"
/>
<NButton
size="1cm"
label="button 1cm"
/>
<NButton
class="px-8 py-10 text-xs"
label="custom size"
/>
<NButton
size="18px hover:40px"
btn="solid hover:outline-lime"
label="hover me 18px->40px"
/>
</div>
</template>
Rounded
Prop | Default | Type | Description |
---|---|---|---|
rounded | md | string | Set the button to have rounded corners. |
Preview
Code
<script setup lang="ts">
const examples = [
{
rounded: 'none',
},
{
rounded: 'sm',
},
{
rounded: 'md',
},
{
rounded: 'lg',
},
{
rounded: 't-xl sm:r-xl',
},
{
rounded: '3xl',
},
{
rounded: 'b-full',
},
{
rounded: 's-3xl',
},
]
</script>
<template>
<div class="flex flex-wrap items-center gap-4">
<NButton
v-for="example in examples"
:key="example.rounded"
:label="example.rounded"
:rounded="example.rounded"
/>
</div>
</template>
Square
Prop | Default | Type | Description |
---|---|---|---|
square | true | boolean , string | Set the button to have the same width and height. If you provide empty value or true , it will provide 2.5em . |
You can also provide a custom value to the square
prop to set the width and height of the button, e.g., square="3em"
.
Preview
Code
<template>
<div class="flex flex-wrap items-start gap-4">
<NButton
label="Button"
square
btn="solid-orange"
/>
<NButton
label="Button"
square="16"
btn="solid-lime"
/>
<NButton
label="Button"
square="24"
btn="solid-pink"
/>
<NButton
label="Button"
square="32"
btn="solid-purple"
/>
<NButton
label="Button"
square="40"
btn="solid-red"
/>
</div>
</template>
Icon
Prop | Default | Type | Description |
---|---|---|---|
icon | - | boolean | Force the label to be an icon. |
leading | - | string | Display leading icon. |
trailing | - | string | Display trailing icon. |
Read more in Icon component
Preview
Code
Icon buttons with and without square preset Icon with states Leading icon with label Trailing icon with label
<template>
<div class="flex flex-col gap-4">
<span class="text-sm font-medium">
Icon buttons with and without square preset
</span>
<div class="flex flex-row gap-2">
<NButton
label="i-heroicons-arrow-down-tray-20-solid"
icon
/>
<NButton
label="i-heroicons-arrow-down-tray-20-solid"
icon
btn="soft"
square
/>
</div>
<NSeparator />
<span class="text-sm font-medium">
Icon with states
</span>
<div class="flex flex-row gap-2">
<NButton
label="i-heroicons-bell-20-solid group-hover:i-heroicons-bell-alert-20-solid"
icon
btn="square solid-black"
class="group rounded-none"
/>
<NButton
label="i-heroicons-envelope-20-solid group-focus:i-heroicons-envelope-open-20-solid"
icon
btn="outline-green square focus:outline-yellow"
class="group rounded-full"
/>
</div>
<NSeparator />
<span class="text-sm font-medium">
Leading icon with label
</span>
<div class="flex flex-col gap-2 sm:flex-row">
<NButton
leading="i-logos-google-icon"
btn="solid-gray"
label="Sign in with Google"
/>
<NButton
leading="i-logos-facebook text-lg"
btn="solid-white"
label="Sign in with Facebook"
/>
<NButton
leading="i-bi-github text-lg"
btn="solid-black"
label="Sign in with Github"
/>
</div>
<NSeparator />
<span class="text-sm font-medium">
Trailing icon with label
</span>
<div class="flex flex-col gap-2 sm:flex-row">
<NButton
trailing="i-heroicons-at-symbol-20-solid"
btn="outline"
label="Subscribe now"
/>
<NButton
trailing="i-heroicons-arrow-right-on-rectangle-20-solid"
label="Login"
/>
</div>
</div>
</template>
Link
Prop | Default | Type | Description |
---|---|---|---|
to | - | string | The link to navigate to. |
Read more in Link component
Preview
Code
<template>
<div class="flex flex-wrap gap-4">
<NButton
btn="solid-black"
label="Previous page"
leading="i-heroicons-arrow-small-left-20-solid"
to="/components/badge"
/>
<NButton
btn="solid-black"
label="Next page"
trailing="i-heroicons-arrow-small-right-20-solid"
to="/components/dropdown-menu"
/>
<NButton
to="https://github.com/una-ui/una-ui"
target="_blank"
btn="outline-pink"
class="rounded-none"
leading="i-heroicons-star-20-solid text-yellow text-sm"
label="Star us on GitHub"
/>
<NButton
btn="link"
label="Back to top"
leading="i-heroicons-arrow-small-up-20-solid"
active-class="btn-link-lime"
to="#examples"
/>
</div>
</template>
Block
Prop | Default | Type | Description |
---|---|---|---|
block | - | boolean | Set the button to have full width. |
Preview
Code
<template>
<div class="flex gap-4">
<NButton
label="Normal"
/>
<NButton
block
label="Block"
/>
</div>
</template>
Disabled
Prop | Default | Type | Description |
---|---|---|---|
disabled | - | boolean | Set the button to disabled. |
Preview
Code
<template>
<div class="flex flex-wrap gap-4">
<NButton
disabled
label="Disabled"
/>
<NButton
btn="outline"
disabled
label="Disabled"
/>
<NButton
btn="link"
disabled
label="Disabled"
/>
<NButton
btn="ghost"
disabled
label="Disabled"
/>
<NButton
btn="soft"
disabled
label="Disabled"
/>
<NButton
btn="text"
disabled
label="Disabled"
/>
<NButton
btn="solid-black"
disabled
label="Disabled"
/>
<NButton
btn="solid-white"
disabled
label="Disabled"
/>
</div>
</template>
Loading
Prop | Default | Type | Description |
---|---|---|---|
loading | - | boolean | Set the button to loading state. |
loading-placement | leading | leading , trailing , label | Set the loading icon placement. |
By default, the disabled state is triggered when the button is loading.
Preview
Code
<script setup lang="ts">
const loading = ref(true)
function toggleLoading() {
loading.value = !loading.value
}
</script>
<template>
<div class="flex flex-col gap-4">
<div>
<NButton
btn="solid-gray"
:label="`Turn ${loading ? 'off' : 'on'} loading`"
@click="toggleLoading"
/>
</div>
<NSeparator />
<div class="flex flex-col gap-4 sm:flex-row">
<NButton
:loading="loading"
:label="loading ? 'Leading...' : 'Leading'"
@click="toggleLoading"
/>
<NButton
btn="soft-green"
loading-placement="label"
:loading="loading"
:una="{
btnLoadingIcon: 'i-tabler-loader-3',
}"
label="Label Loading"
@click="toggleLoading"
/>
<NButton
btn="solid-black"
loading-placement="trailing"
:loading="loading"
:una="{
btnLoadingIcon: 'i-tabler-loader',
}"
:label="loading ? 'Trailing...' : 'Trailing'"
@click="toggleLoading"
/>
</div>
<NSeparator />
<div class="flex flex-col gap-4 sm:flex-row">
<NButton
btn="soft-yellow"
:class="{ 'animate-pulse': loading }"
:loading="loading"
:label="loading ? 'Downloading...' : 'Download'"
leading="i-heroicons-cloud-arrow-down-20-solid"
@click="toggleLoading"
/>
<NButton
btn="link-orange"
loading-placement="label"
:loading="loading"
:una="{
btnLoadingIcon: 'i-heroicons-cloud-arrow-down-20-solid',
btnLoading: 'animate-pulse',
}"
icon
label="i-tabler-download"
@click="toggleLoading"
/>
<NButton
btn="solid-white"
loading-placement="label"
:loading="loading"
:una="{
btnLoadingIcon: 'i-tabler-reload',
}"
label="Refresh"
@click="toggleLoading"
/>
</div>
</div>
</template>
Slots
Default
Name | Props | Description |
---|---|---|
default | - | The button label. |
Leading
Name | Props | Description |
---|---|---|
leading | - | The leading icon. |
Preview
Code
<template>
<NButton btn="outline" class="rounded-full" label="View">
<template #leading>
<!-- TODO convert to NAvatar soon -->
<span class="rounded-full bg-base">
<img
class="h-5"
src="https://avatars.githubusercontent.com/u/33350692?s=400&u=49395c835e8197ae2ee42ca02c95e828d8f64239&v=4"
>
</span>
</template>
</NButton>
</template>
Trailing
Name | Props | Description |
---|---|---|
trailing | - | The trailing icon. |
Preview
Code
<template>
<NButton label="Login">
<template #trailing>
<NIcon name="i-heroicons-arrow-right-on-rectangle-20-solid" />
</template>
</NButton>
</template>
Loading
Name | Props | Description |
---|---|---|
loading | - | The loading icon. |
Preview
Code
<template>
<NButton
loading
label="Button"
>
<template #loading>
<span class="animate-pulse rounded-full bg-base">
<img
class="h-5"
src="https://avatars.githubusercontent.com/u/33350692?s=400&u=49395c835e8197ae2ee42ca02c95e828d8f64239&v=4"
>
</span>
</template>
</NButton>
</template>
Presets
shortcuts/btn.ts
type BtnPrefix = 'btn'
export const staticBtn: Record<`${BtnPrefix}-${string}` | BtnPrefix, string> = {
// config
'btn-default-variant': 'btn-solid',
'btn-loading-icon': 'i-loading',
'btn-default-radius': 'rounded-md',
// base
'btn': 'btn-rectangle px-1em py-0.5em bg-transparent transition-colors text-0.875em leading-5 gap-x-0.5em rounded-md whitespace-nowrap inline-flex justify-center items-center btn-disabled font-medium cursor-pointer',
'btn-disabled': 'disabled:n-disabled',
'btn-label': '',
'btn-icon-label': 'text-1em',
'btn-leading': '-ml-0.14285714285714285em text-1em',
'btn-trailing': '-mr-0.14285714285714285em text-1em',
'btn-loading': 'animate-spin text-1em',
'btn-rectangle': 'h-2.5em',
'btn-square': 'w-2.5em h-2.5em',
// options
'btn-block': 'w-full',
'btn-reverse': 'flex-row-reverse',
// variants
'btn-solid-white': 'bg-base text-base ring-1 ring-base ring-inset shadow-sm btn-focus hover:bg-muted',
'btn-ghost-white': 'text-base btn-focus hover:bg-$c-gray-50',
'btn-outline-white': 'text-base ring-1 ring-base ring-inset btn-focus hover:bg-$c-gray-50',
'btn-solid-gray': 'bg-$c-gray-50 text-$c-gray-800 ring-1 ring-base ring-inset shadow-sm btn-focus hover:bg-$c-gray-100',
'btn-ghost-gray': 'text-$c-gray-600 btn-focus hover:bg-$c-gray-100',
'btn-soft-gray': 'text-$c-gray-600 bg-$c-gray-50 btn-focus hover:bg-$c-gray-100',
'btn-outline-gray': 'text-muted hover:text-$c-gray-600 ring-1 ring-base ring-inset btn-focus hover:bg-$c-gray-50',
'btn-link-gray': 'text-muted btn-focus hover:text-base hover:underline underline-offset-4',
'btn-text-gray': 'text-$c-gray-600 btn-focus hover:text-$c-gray-900',
'btn-solid-black': 'bg-inverted text-inverted shadow-sm btn-focus',
'btn-link-black': 'text-base btn-focus hover:underline underline-offset-4',
'btn-text-black': 'text-base btn-focus',
'btn-soft-black': 'text-base bg-base btn-focus shadow-sm',
'btn-text-muted': 'text-muted btn-focus hover:text-accent',
'btn-link-muted': 'text-muted btn-focus hover:underline underline-offset-4',
'btn-ghost-muted': 'text-accent hover:text-muted btn-focus hover:bg-muted',
'btn-soft-accent': 'text-accent bg-accent btn-focus',
'btn-text-accent': 'text-accent btn-focus',
'btn-link-accent': 'text-accent btn-focus hover:underline underline-offset-4',
}
export const dynamicBtn: [RegExp, (params: RegExpExecArray) => string][] = [
// base
[/^btn-focus(-(\S+))?$/, ([, , c = 'primary']) => `focus-visible:outline-${c}-600 dark:focus-visible:outline-${c}-500 focus-visible:outline-2 focus-visible:outline-offset-2`],
// variants
[/^btn-solid(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} text-inverted shadow-sm bg-${c}-600 hover:bg-${c}-500 dark:bg-${c}-500 dark:hover:bg-${c}-400`],
[/^btn-text(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} text-${c}-600 dark:text-${c}-500 hover:text-${c}-500 dark:hover:text-${c}-400`],
[/^btn-outline(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} text-${c}-500 dark:text-${c}-400 ring-1 ring-inset ring-${c}-500 dark:ring-${c}-400 hover:bg-${c}-50 dark:hover:bg-${c}-950`],
[/^btn-soft(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} text-${c}-600 dark:text-${c}-400 bg-${c}-50 dark:bg-${c}-950 hover:bg-${c}-100 dark:hover:bg-${c}-900`],
[/^btn-ghost(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} text-${c}-600 dark:text-${c}-400 hover:bg-${c}-100 dark:hover:bg-${c}-900`],
[/^btn-link(-(\S+))?$/, ([, , c = 'primary']) => `btn-focus-${c} text-${c}-600 dark:text-${c}-500 hover:underline underline-offset-4`],
]
export const btn = [
...dynamicBtn,
staticBtn,
]
Props
types/button.ts
import type { HTMLAttributes } from 'vue'
import type { RouteLocationRaw } from 'vue-router'
interface BaseExtensionProps {
square?: HTMLAttributes['class']
rounded?: HTMLAttributes['class']
class?: HTMLAttributes['class']
breadcrumbActive?: string
breadcrumbInactive?: string
paginationSelected?: string
paginationUnselected?: string
dropdownMenu?: string
toggleOn?: string
toggleOff?: string
}
export interface NButtonProps extends BaseExtensionProps {
/**
* Change the button type.
*
* @default 'button'
*/
type?: 'button' | 'submit' | 'reset'
/**
* Change the loading placement of the button.
*
* @default 'leading'
*/
loadingPlacement?: 'leading' | 'trailing' | 'label'
/**
* Convert `label` prop to icon component.
*
* @default false
* @example
* icon
* label="i-heroicons-information-circle"
*/
icon?: boolean
/**
* Disable the button.
*
* @default false
*/
disabled?: boolean
/**
* Swap the position of the leading and trailing icons.
*
* @default false
*/
reverse?: boolean
/**
* Show loading state on button
* @default false
*/
loading?: boolean
/**
* Make the button full width.
*
* @default false
*/
block?: boolean
/**
* Change the button tag to `NuxtLink` component,
* This allows you to use `NuxtLink` available props.
*
* @see https://nuxt.com/docs/api/components/nuxt-link#props
* @example
* to="/"
*/
to?: RouteLocationRaw
/**
* Add a label to the button.
*
* @example
* label="Click me"
*/
label?: string
/**
* Allows you to add `UnaUI` button preset properties,
* Think of it as a shortcut for adding options or variants to the preset if available.
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/button.ts
* @example
* btn="solid-green block square"
*/
btn?: string
/**
* Add leading icon the button,
* This also allows you to add utility classes to the icon.
*
* @example
* leading="i-heroicons-information-circle text-green-500 dark:text-green-400 text-2xl"
*/
leading?: string
/**
* Add trailing icon the button.
* This also allows you to add utility classes to the icon.
*
* @example
* trailing="i-heroicons-information-circle text-green-500 dark:text-green-400 text-2xl"
*/
trailing?: string
/**
* Allows you to change the size of the input.
*
* @default sm
*
* @example
* size="sm" | size="2cm" | size="2rem" | size="2px"
*/
size?: string
/**
* `UnaUI` preset configuration
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/button.ts
*/
una?: {
// base
btnDefaultVariant?: string
btn?: string
btnLabel?: string
btnIconLabel?: string
btnLoading?: string
btnTrailing?: string
btnLeading?: string
// icon
btnLoadingIcon?: string
}
}
Components
Button.vue
<script setup lang="ts">
import type { NButtonProps } from '../../types'
import { createReusableTemplate } from '@vueuse/core'
import { computed } from 'vue'
import { cn } from '../../utils'
import NIcon from '../elements/Icon.vue'
import NLink from '../elements/Link.vue'
const props = withDefaults(defineProps<NButtonProps>(), {
type: 'button',
loadingPlacement: 'leading',
square: false,
una: () => ({
btnDefaultVariant: 'btn-default-variant',
}),
})
const mergeVariants = computed(() => {
return {
'btn': props.btn,
'breadcrumb-active': props.breadcrumbActive,
'breadcrumb-inactive': props.breadcrumbInactive,
'pagination-selected': props.paginationSelected,
'pagination-unselected': props.paginationUnselected,
'dropdown-menu': props.dropdownMenu,
'toggle-on': props.toggleOn,
'toggle-off': props.toggleOff,
}
})
const btnVariants = ['solid', 'outline', 'soft', 'ghost', 'link', 'text'] as const
const hasVariant = computed(() =>
Object.values(mergeVariants.value).some(variantList =>
btnVariants.some(variant => variantList?.includes(variant)),
),
)
const isBaseVariant = computed(() =>
Object.values(mergeVariants.value).some(variantList =>
variantList?.includes('~'),
),
)
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>
<template>
<Component
:is="to ? NLink : 'button'"
:to="to"
:type="to ? null : type"
:class="cn(
(square === '' || square === true) && 'btn-square',
block && 'btn-block',
!rounded && 'btn-default-radius',
!hasVariant && !isBaseVariant ? una?.btnDefaultVariant : null,
reverse && 'btn-reverse',
'btn',
una?.btn,
props.class,
)"
:disabled="to ? null : disabled || loading"
:aria-label="icon ? label : null"
:rounded
:size
:square
v-bind="mergeVariants"
>
<DefineTemplate v-if="loading">
<slot name="loading">
<NIcon
:name="una?.btnLoadingIcon ?? 'btn-loading-icon'"
:class="una?.btnLoading"
btn="loading"
/>
</slot>
</DefineTemplate>
<ReuseTemplate v-if="loading && loadingPlacement === 'leading'" />
<slot
v-else
name="leading"
>
<NIcon
v-if="leading"
:name="leading"
:class="una?.btnLeading"
btn="leading"
/>
</slot>
<ReuseTemplate v-if="loading && loadingPlacement === 'label'" />
<slot v-else>
<NIcon
v-if="label && icon"
:name="label"
btn="icon-label"
:class="una?.btnIconLabel"
/>
<span
v-if="!icon"
btn="label"
:class="una?.btnLabel"
>
{{ label }}
</span>
</slot>
<ReuseTemplate v-if="loading && loadingPlacement === 'trailing'" />
<slot
v-else
name="trailing"
>
<NIcon
v-if="trailing"
:name="trailing"
btn="trailing"
:class="una?.btnTrailing"
/>
</slot>
</Component>
</template>