๐ŸŸข Button


Basic

NButton - use to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or performing a delete operation.

You can use label prop or default slot to set the button text.

<template>
<NButton label="Button" />
</template>

Variants

btn="{variant}" - change the variant of the button.

VariantDescription
solidThe default variant.
outlineThe outline variant.
ghostThe ghost variant.
linkThe link variant.
softThe soft variant.
textThe text variant.
~The unstyle or base variant
<template>
<div 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

btn="{variant}-{color}" - change the color of the button.

You can use any color provided by the Tailwind CSS color palette, the default is primary. You can also add your own colors to the palette through the Configuration section.
Dynamic colors:

Color with states:

Custom colors using utilities:

Static colors:
<template>
<div flex="~ col" gap-4>
  <span class="text-sm font-medium">Dynamic colors:</span>

  <div flex="~ col" gap-4>
    <div flex="~ col md:row" gap-4>
      <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>

    <hr border="base">

    <span class="text-sm font-medium">Color with states:</span>

    <div flex="~ col md:row" gap-4>
      <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>

    <hr border="base">

    <span class="text-sm font-medium">Custom colors using utilities:</span>

    <div flex="~ col md:row" gap-2>
      <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>

  <hr border="base">

  <span class="text-sm font-medium">Static colors:</span>

  <div flex="~ col" gap-4>
    <div flex="~ col md:row" gap-2>
      <NButton
        label="solid-gray"
        btn="solid-gray"
      />
      <NButton
        label="soft-gray"
        btn="soft-gray"
      />
      <NButton
        label="ghost-gray"
        btn="ghost-gray"
      />
      <NButton
        label="link-gray"
        btn="link-gray"
      />
    </div>

    <div flex="~ col md:row" gap-2>
      <NButton
        label="solid-white"
        btn="solid-white"
      />
      <NButton
        label="ghost-white"
        btn="ghost-white"
      />
    </div>

    <div flex="~ col md:row" gap-2>
      <NButton
        label="solid-black"
        btn="solid-black"
      />
      <NButton
        label="link-black"
        btn="link-black"
      />
    </div>

    <div flex="~ col md:row" gap-2>
      <NButton
        label="text-black"
        btn="text-black"
      />
      <NButton
        label="text-gray"
        btn="text-gray"
      />
    </div>
  </div>
</div>
</template>

Size

size="{size}" - change the size of the button.

๐Ÿš€ You can freely adjust the size of the button using any size imaginable. No limits exist, and you can use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state and more.

The padding and font-size of the button scale depends on the size. If you want to change the font-size and padding simultaneously, you can always customize it using utility classes.
<template>
<div 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>

Padding & Shape

btn="square" - to force the padding to have the same size, usefull for icon buttons.

By default, the padding is rectangle, you can change it to square. You can always add and customize it using utility classes such as adding rounded-full to make it circle.
<template>
<div flex="~ wrap" gap-4>
  <NButton
    label="PR"
    btn="square solid-orange"
  />

  <NButton
    label="PR"
    btn="square solid-lime"
    class="rounded-full"
  />

  <NButton
    label="square + rounded-none"
    btn="square solid-pink"
    class="rounded-none"
  />

  <NButton
    label="square + rounded-full"
    btn="square solid-green"
    class="rounded-full"
  />

  <NButton
    label="rectangle + custom-rounded"
    btn="solid-indigo"
    class="rounded-b-none rounded-t-2xl text-white"
  />
</div>
</template>

Icon

icon - change label text to icon.

leading="{icon}" - add a leading icon to the button.

trailing="{icon}" - add a trailing icon to the button.

By default we use heroicons and tabler for the icons, you can use any icon provided by Iconify through icones, refer to configuration for more information.
Icon buttons with and without square preset

Icon with states

Leading icon with label

Trailing icon with label
<template>
<div flex="~ col" gap-4>
  <span class="text-sm font-medium">
    Icon buttons with and without square preset
  </span>

  <div 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>

  <hr border="base">

  <span class="text-sm font-medium">
    Icon with states
  </span>

  <div 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>

  <hr border="base">

  <span class="text-sm font-medium">
    Leading icon with label
  </span>

  <div flex="~ col sm:row" gap-2>
    <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>

  <hr border="base">

  <span class="text-sm font-medium">
    Trailing icon with label
  </span>

  <div flex="~ col sm:row" gap-2>
    <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>

to - add a link to the button.

Since we use NuxtLink for the link, you can use any NuxtLink props such as prefetch, target, activeClass, etc. Refer to NuxtLink for more information.
<template>
<div flex="~ wrap" gap-4>
  <NButton
    btn="solid-black"
    label="Previous page"
    leading="i-heroicons-arrow-small-left-20-solid"
    to="/elements/badge"
  />

  <NButton
    btn="solid-black"
    label="Next page"
    trailing="i-heroicons-arrow-small-right-20-solid"
    to="/elements/dropdown"
  />

  <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="#button"
  />
</div>
</template>

Block

btn="block" - add block style to the button.

<template>
<div flex gap-4>
  <NButton
    label="Normal"
  />

  <NButton
    btn="block"
    label="Block"
  />
</div>
</template>

Disabled

disabled - add a disabled state to the button.

<template>
<div 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

By default we trigger the disabled state when the button is loading.

loading - add a loading state to the button.

loading-placement - change the loading icon placement, default is leading. options are leading, trailing and label.

You can use loading with icon and label at the same time.


<script setup lang="ts">
const loading = ref(true)

function toggleLoading() {
loading.value = !loading.value
}
</script>

<template>
<div flex="~ col" gap-4>
  <div>
    <NButton
      btn="solid-gray"
      :label="`Turn ${loading ? 'off' : 'on'} loading`"
      @click="toggleLoading"
    />
  </div>

  <hr border="base">

  <div flex="~ col sm:row" gap-4>
    <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>

  <hr border="base">

  <div flex="~ col sm:row" gap-4>
    <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

#default - set the button label, refer to label for the example.

Leading

#leading - add a leading icon to the button.

<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

#tailing - add a trailing icon to the button.

<template>
<NButton label="Login">
  <template #trailing>
    <NIcon name="i-heroicons-arrow-right-on-rectangle-20-solid" />
  </template>
</NButton>
</template>

Loading

#loading - add a loading icon to the button.

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

Props

export interface NButtonProps {
  /**
   * 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

  /**
   * 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?: string
  /**
   * 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

  /**
   * `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

    // icons
    btnTrailing?: string
    btnLeading?: string
    btnLoadingIcon?: string
  }
}

Presets

type BtnPrefix = 'btn'

export const staticBtn: Record<`${BtnPrefix}-${string}` | BtnPrefix, string> = {
  // config
  'btn-default-variant': 'btn-solid',
  'btn-loading-icon': 'i-loading',

  // base
  'btn': 'bg-transparent text-0.875em leading-5 gap-0.42857142857142855em btn-rectangle rounded-md inline-flex justify-center items-center btn-disabled font-semibold cursor-pointer',
  'btn-disabled': 'disabled:n-disabled',
  'btn-label': '',
  'btn-icon-label': 'text-1.191em',
  'btn-leading': '-ml-0.14285714285714285em text-1.191em',
  'btn-trailing': '-mr-0.14285714285714285em text-1.191em',
  'btn-loading': 'animate-spin text-1.191em',

  // options
  'btn-block': 'w-full',
  'btn-reverse': 'flex-row-reverse',
  'btn-rectangle': 'px-0.7142857142857143em py-0.42857142857142855em',
  'btn-square': 'p-0.42857142857142855em',

  // 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-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-link-gray': 'text-$c-gray-500 btn-focus hover:text-$c-gray-950 hover:underline underline-offset-4',
  'btn-solid-black': 'bg-$c-gray-950 text-inverted shadow-sm btn-focus hover:bg-$c-gray-900',
  'btn-link-black': 'text-$c-gray-950 btn-focus hover:underline underline-offset-4',
  'btn-text-black': 'text-$c-gray-950 btn-focus hover:text-$c-gray-900',
  'btn-text-gray': 'text-$c-gray-600 btn-focus hover:text-$c-gray-900',
}

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']) => `bg-transparent 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}-500 dark:text-${c}-400 hover:underline underline-offset-4`],
]

export const btn = [
  ...dynamicBtn,
  staticBtn,
]

Component

<script setup lang="ts">
import { computed } from 'vue'
import { createReusableTemplate } from '@vueuse/core'
import NIcon from '../elements/Icon.vue'
import type { NButtonProps } from '../../types'
import NLink from '../elements/Link.vue'

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<NButtonProps>(), {
  type: 'button',
  loadingPlacement: 'leading',
  una: () => ({
    btnDefaultVariant: 'btn-default-variant',
  }),
})

const btnVariants = ['solid', 'outline', 'soft', 'ghost', 'link', 'text'] as const
const hasVariant = computed(() => btnVariants.some(btnVariants => props.btn?.includes(btnVariants)))
const isBaseVariant = computed(() => props.btn?.includes('~'))

const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

<template>
  <Component
    :is="to ? NLink : 'button'"
    :to="to"
    :type="to ? null : type"
    class="btn"
    :class="[
      !hasVariant && !isBaseVariant ? una?.btnDefaultVariant : null,
      { 'btn-reverse': reverse },
      una?.btn,
    ]"
    :disabled="to ? null : disabled || loading"
    :btn="btn"
    :aria-label="icon ? label : null"
    v-bind="$attrs"
  >
    <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"
        :class="una?.btnTrailing"
        btn="trailing"
      />
    </slot>
  </Component>
</template>