๐ข 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
ordefault slot
to set the button text.
Variants
btn="{variant}"
- change the variant of the button.
Variant | Description |
---|---|
solid | The default variant. |
outline | The outline variant. |
ghost | The ghost variant. |
link | The link variant. |
soft | The soft variant. |
text | The text variant. |
~ | The unstyle or base variant |
Color
btn="{variant}-{color}"
- change the color of the button.
primary
. You can also add your own colors to the palette through the Configuration section.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 assm:sm, xs:lg
to change size based on screen size orstates
such ashover:lg, focus:3xl
to change size based on input state and more.
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.Rounded
rounded="{size}"
- change the border-radius of the button.
๐ You can freely adjust the size of the rounded using any size imaginable. No limits exist, and you can use
breakpoints
such assm:sm, xs:lg
to change size based on screen size orstates
such ashover:lg, focus:3xl
to change size based on input state and more.
md
. You can also add your own sizes to the scale through the Configuration section.Square
square
- to force width
and height
to have the same size, usefull for icon buttons.
๐ You can freely adjust the square of the button using any size imaginable. No limits exist, and you can use
breakpoints
such assm:sm, xs:lg
to change size based on screen size orstates
such ashover:lg, focus:3xl
to change size based on input state and more.
Props | Type | Default | Description |
---|---|---|---|
square | string boolean | 2.5em | Set the button to have the same width and height. If you provide empty value or true , it will use the default value. |
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.
heroicons
and tabler
for the icons, you can use any icon provided by Iconify
through icones, refer to configuration for more information.Link
to
- add a link to the button.
NuxtLink
for the link, you can use any NuxtLink
props such as prefetch
, target
, activeClass
, etc. Refer to NuxtLink for more information.Block
btn="block"
- add block style to the button.
Disabled
disabled
- add a disabled state to the button.
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
.
loading
with icon
and label
at the same time.Slots
Default
#default
- set the button label, refer to label for the example.
Leading
#leading
- add a leading icon to the button.
Trailing
#tailing
- add a trailing icon to the button.
Loading
#loading
- add a loading icon to the button.
Props
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
}
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
/**
* 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
// 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',
'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-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',
'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',
'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 ring-1 ring-base ring-inset btn-focus',
'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',
}
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}-500 dark:text-${c}-400 hover:underline underline-offset-4`],
]
export const btn = [
...dynamicBtn,
staticBtn,
]
Component
<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,
}
})
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',
!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>