Lzh on GitHub

Button

一个可以作为链接或触发动作的按钮元素。

用法

标签(Label)

使用默认插槽来设置按钮的标签。

<template>
  <UButton>Button</UButton>
</template>

你也可以使用 label prop 来达到同样的效果。

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

颜色(Color)

使用 color prop 来改变按钮的颜色。

<template>
  <UButton color="neutral">Button</UButton>
</template>

变体(Variant)

使用 variant prop 来改变按钮的变体。

<template>
  <UButton color="neutral" variant="outline">Button</UButton>
</template>

尺寸(Size)

使用 size prop 来改变按钮的尺寸。

<template>
  <UButton size="xl">Button</UButton>
</template>

图标(Icon)

使用 icon prop 在按钮内部显示一个 Icon

<template>
  <UButton icon="i-lucide-rocket" size="md" color="primary" variant="solid">Button</UButton>
</template>

使用 leadingtrailing props 来设置图标位置,或者使用 leading-icontrailing-icon props 为每个位置设置不同的图标。

<template>
  <UButton trailing-icon="i-lucide-arrow-right" size="md">Button</UButton>
</template>

label 作为 prop 或插槽是可选的,所以你可以将按钮用作纯图标按钮。

<template>
  <UButton icon="i-lucide-search" size="md" color="primary" variant="solid" />
</template>

头像(Avatar)

使用 avatar prop 在按钮内部显示一个 Avatar

<template>
  <UButton
    :avatar="{
      src: 'https://github.com/nuxt.png'
    }"
    size="md"
    color="neutral"
    variant="outline"
  >
    Button
  </UButton>
</template>

label 作为 prop 或插槽是可选的,所以你可以将按钮用作纯头像按钮。

<template>
  <UButton
    :avatar="{
      src: 'https://github.com/nuxt.png'
    }"
    size="md"
    color="neutral"
    variant="outline"
  />
</template>

你可以传递 Link 组件的任何属性,例如 totarget 等。

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

当按钮是链接或使用 active prop 时,你可以使用 active-coloractive-variant props 来定制活跃状态。

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

你也可以使用 active-classinactive-class props 来定制活跃状态。

<template>
  <UButton active active-class="font-bold" inactive-class="font-light">Button</UButton>
</template>
你可以在 app.config.ts 文件中的 ui.button.variants.active 键下全局配置这些样式。
export default defineAppConfig({
  ui: {
    button: {
      variants: {
        active: {
          true: {
            base: 'font-bold'
          }
        }
      }
    }
  }
})

加载中(Loading)

使用 loading prop 来显示加载图标并禁用按钮。

<template>
  <UButton loading>Button</UButton>
</template>

使用 loading-auto prop 在 @click promise 待处理时自动显示加载图标。

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

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

这也适用于 Form 组件。

<script setup lang="ts">
const state = reactive({ fullName: '' })

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

async function validate(data: Partial<typeof state>) {
  if (!data.fullName?.length) return [{ name: 'fullName', message: 'Required' }]
  return []
}
</script>

<template>
  <UForm :state="state" :validate="validate" @submit="onSubmit">
    <UFormField name="fullName" label="Full name">
      <UInput v-model="state.fullName" />
    </UFormField>
    <UButton type="submit" class="mt-2" loading-auto>
      Submit
    </UButton>
  </UForm>
</template>

加载图标(Loading Icon)

使用 loading-icon prop 来自定义加载图标。默认为 i-lucide-loader-circle

<template>
  <UButton loading loading-icon="i-lucide-loader">Button</UButton>
</template>
你可以在 app.config.ts 文件中的 ui.icons.loading 键下全局自定义此图标。
你可以在 vite.config.ts 文件中的 ui.icons.loading 键下全局自定义此图标。

禁用(Disabled)

使用 disabled prop 来禁用按钮。

<template>
  <UButton disabled>Button</UButton>
</template>

示例

class prop

使用 class prop 来覆盖按钮的基础样式。

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

ui prop

使用 ui prop 来覆盖按钮的插槽样式。

<template>
  <UButton
    icon="i-lucide-rocket"
    color="neutral"
    variant="outline"
    :ui="{
      leadingIcon: 'text-primary'
    }"
  >
    Button
  </UButton>
</template>

API

Props

Prop Default Type
as

'button'

any

The element or component this component should render as when not a link.

label

string

color

'primary'

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

activeColor

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

variant

'solid'

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

activeVariant

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

size

'md'

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

square

boolean

Render the button with equal padding on all sides.

block

boolean

Render the button full width.

loadingAuto

boolean

Set loading state automatically based on the @click promise state

icon

string

Display an icon based on the leading and trailing props.

avatar

AvatarProps

Display an avatar on the left side.

leading

boolean

When true, the icon will be displayed on the left side.

leadingIcon

string

Display an icon on the left side.

trailing

boolean

When true, the icon will be displayed on the right side.

trailingIcon

string

Display an icon on the right side.

loading

boolean

When true, the loading icon will be displayed.

loadingIcon

appConfig.ui.icons.loading

string

The icon when the loading prop is true.

type

'button'

"reset" | "submit" | "button"

The type of the button when not a link.

disabled

boolean

to

string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric

Route Location the link should navigate to when clicked on.

active

boolean

Force the link to be active independent of the current route.

target

null | "_blank" | "_parent" | "_self" | "_top" | string & {}

Where to display the linked URL, as the name for a browsing context.

ui

{ base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; }

Button 组件扩展了 Link 组件。在 GitHub 上查看源代码。

Slots

Slot Type
leading

{}

default

{}

trailing

{}

Emits

Button 组件中,onClick prop 被设计得非常灵活:

onClick?: ((event: MouseEvent) => void | Promise<void>) | Array<((event: MouseEvent) => void | Promise<void>)>

这意味着 onClick prop 可以接受:

  • 单个函数:最常见的用法,当点击按钮时执行一个函数。
  • 函数数组:当点击按钮时,按顺序执行多个函数。

更重要的是,这些函数可以返回 void (没有返回值) 或者是 Promise<void> (一个 Promise)。当返回 Promise 时,组件内部的 loadingAuto 机制会非常有用。

onClick prop 的使用场景

  1. 简单的点击事件 (返回 void)

这是最基础的用法,点击按钮后立即执行一个动作。

<template>
  <div class="space-y-4">
    <UButton label="普通点击" :on-click="handleClick" />
    <UButton label="内联点击" :on-click="() => alert('你点击了内联按钮!')" />
  </div>
</template>

<script setup lang="ts">
const handleClick = (event: MouseEvent) => {
  console.log('按钮被点击了!', event);
  alert('你点击了普通点击按钮!');
};
</script>
  1. 执行异步操作 (返回 Promise<void>)

当你的点击事件涉及到异步操作(例如 API 调用、数据加载、等待动画完成等)时,onClick 可以返回一个 Promise。配合 loadingAuto prop,这会提供非常好的用户体验。

<template>
  <div class="space-y-4">
    <UButton
      label="保存数据"
      icon="i-heroicons-light-cloud-arrow-up"
      :loading-auto="true"
      :on-click="saveData"
    />
    <UButton
      label="加载内容"
      icon="i-heroicons-light-arrow-path"
      :loading-auto="true"
      :on-click="loadContent"
    />
  </div>
</template>

<script setup lang="ts">
const saveData = async (event: MouseEvent) => {
  console.log('开始保存数据...', event);
  await new Promise(resolve => setTimeout(resolve, 2500)); // 模拟异步 API 调用
  console.log('数据保存成功!');
  alert('数据已保存!');
};

const loadContent = async () => {
  console.log('开始加载内容...');
  await new Promise(resolve => setTimeout(resolve, 1500)); // 模拟异步内容加载
  console.log('内容加载完成!');
  alert('内容已加载!');
};
</script>

解释:

  • saveDataloadContent 都是 async 函数,它们返回一个 Promise
  • 当你将 loadingAuto 设置为 true 时:
    • onClick 函数开始执行时(即 Promise 处于 pending 状态),按钮会自动进入加载状态(显示加载图标,禁用按钮)。
    • Promise 解决 (resolved) 或拒绝 (rejected) 时,按钮会自动退出加载状态。
  • 这种模式非常适合表单提交、数据更新等异步操作。
  1. 执行多个操作 (函数数组)

如果你希望点击一个按钮能触发一系列独立的动作,你可以传递一个函数数组。

<template>
  <div class="space-y-4">
    <UButton
      label="执行多步操作"
      icon="i-heroicons-light-play"
      :loading-auto="true"
      :on-click="[step1, step2, step3]"
    />
  </div>
</template>

<script setup lang="ts">
const step1 = async () => {
  console.log('执行第一步:验证用户...');
  await new Promise(resolve => setTimeout(resolve, 1000));
  console.log('第一步完成。');
};

const step2 = async (event: MouseEvent) => {
  console.log('执行第二步:处理数据...', event);
  await new Promise(resolve => setTimeout(resolve, 1500));
  console.log('第二步完成。');
};

const step3 = async () => {
  console.log('执行第三步:通知结果...');
  await new Promise(resolve => setTimeout(resolve, 500));
  console.log('所有步骤完成!');
  alert('多步操作已完成!');
};
</script>

解释:

  • onClick 是一个函数数组时,组件内部的 onClickWrapper 会使用 Promise.all 来等待所有函数执行完毕。
  • 这意味着:
    • 如果任何一个函数是异步的 (async 函数或返回 Promise),按钮会进入加载状态。
    • 只有当所有函数(无论同步还是异步)都执行完毕后,按钮才会退出加载状态。
  • 这种用法适用于需要按顺序执行一系列独立任务的场景。
  • 参数传递:onClick 接收的函数都会自动获得 MouseEvent 对象作为第一个参数。
  • 优先级:如果你同时使用了 onClick 和 @click (Vue 原生事件监听器),两者都会触发。onClick 是 Nuxt UI 按钮组件特有的 prop,通常建议优先使用它,尤其是在需要利用 loadingAuto 等高级功能时。
  • 类型安全:由于 ButtonProps 中对 onClick 进行了类型定义,TypeScript 会帮助你确保传入的函数符合预期。

    通过这种设计,UButton 组件的 onClick prop 提供了极大的灵活性,能够轻松处理简单的点击事件、复杂的异步操作,甚至是多步任务流,同时保持了良好的用户体验,尤其是在结合 loadingAuto prop 使用时。

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    button: {
      slots: {
        base: [
          'rounded-md font-medium inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75',
          'transition-colors'
        ],
        label: 'truncate',
        leadingIcon: 'shrink-0',
        leadingAvatar: 'shrink-0',
        leadingAvatarSize: '',
        trailingIcon: 'shrink-0'
      },
      variants: {
        buttonGroup: {
          horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
          vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
        },
        color: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        variant: {
          solid: '',
          outline: '',
          soft: '',
          subtle: '',
          ghost: '',
          link: ''
        },
        size: {
          xs: {
            base: 'px-2 py-1 text-xs gap-1',
            leadingIcon: 'size-4',
            leadingAvatarSize: '3xs',
            trailingIcon: 'size-4'
          },
          sm: {
            base: 'px-2.5 py-1.5 text-xs gap-1.5',
            leadingIcon: 'size-4',
            leadingAvatarSize: '3xs',
            trailingIcon: 'size-4'
          },
          md: {
            base: 'px-2.5 py-1.5 text-sm gap-1.5',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs',
            trailingIcon: 'size-5'
          },
          lg: {
            base: 'px-3 py-2 text-sm gap-2',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs',
            trailingIcon: 'size-5'
          },
          xl: {
            base: 'px-3 py-2 text-base gap-2',
            leadingIcon: 'size-6',
            leadingAvatarSize: 'xs',
            trailingIcon: 'size-6'
          }
        },
        block: {
          true: {
            base: 'w-full justify-center',
            trailingIcon: 'ms-auto'
          }
        },
        square: {
          true: ''
        },
        leading: {
          true: ''
        },
        trailing: {
          true: ''
        },
        loading: {
          true: ''
        },
        active: {
          true: {
            base: ''
          },
          false: {
            base: ''
          }
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          variant: 'solid',
          class: 'text-inverted bg-primary hover:bg-primary/75 active:bg-primary/75 disabled:bg-primary aria-disabled:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'
        },
        {
          color: 'primary',
          variant: 'outline',
          class: 'ring ring-inset ring-primary/50 text-primary hover:bg-primary/10 active:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
        },
        {
          color: 'primary',
          variant: 'soft',
          class: 'text-primary bg-primary/10 hover:bg-primary/15 active:bg-primary/15 focus:outline-none focus-visible:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10'
        },
        {
          color: 'primary',
          variant: 'subtle',
          class: 'text-primary ring ring-inset ring-primary/25 bg-primary/10 hover:bg-primary/15 active:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
        },
        {
          color: 'primary',
          variant: 'ghost',
          class: 'text-primary hover:bg-primary/10 active:bg-primary/10 focus:outline-none focus-visible:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
        },
        {
          color: 'primary',
          variant: 'link',
          class: 'text-primary hover:text-primary/75 active:text-primary/75 disabled:text-primary aria-disabled:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary'
        },
        {
          color: 'neutral',
          variant: 'solid',
          class: 'text-inverted bg-inverted hover:bg-inverted/90 active:bg-inverted/90 disabled:bg-inverted aria-disabled:bg-inverted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-inverted'
        },
        {
          color: 'neutral',
          variant: 'outline',
          class: 'ring ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default aria-disabled:bg-default focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
        },
        {
          color: 'neutral',
          variant: 'soft',
          class: 'text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 focus:outline-none focus-visible:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated'
        },
        {
          color: 'neutral',
          variant: 'subtle',
          class: 'ring ring-inset ring-accented text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
        },
        {
          color: 'neutral',
          variant: 'ghost',
          class: 'text-default hover:bg-elevated active:bg-elevated focus:outline-none focus-visible:bg-elevated hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent'
        },
        {
          color: 'neutral',
          variant: 'link',
          class: 'text-muted hover:text-default active:text-default disabled:text-muted aria-disabled:text-muted focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted'
        },
        {
          size: 'xs',
          square: true,
          class: 'p-1'
        },
        {
          size: 'sm',
          square: true,
          class: 'p-1.5'
        },
        {
          size: 'md',
          square: true,
          class: 'p-1.5'
        },
        {
          size: 'lg',
          square: true,
          class: 'p-2'
        },
        {
          size: 'xl',
          square: true,
          class: 'p-2'
        },
        {
          loading: true,
          leading: true,
          class: {
            leadingIcon: 'animate-spin'
          }
        },
        {
          loading: true,
          leading: false,
          trailing: true,
          class: {
            trailingIcon: 'animate-spin'
          }
        }
      ],
      defaultVariants: {
        color: 'primary',
        variant: 'solid',
        size: 'md'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        button: {
          slots: {
            base: [
              'rounded-md font-medium inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75',
              'transition-colors'
            ],
            label: 'truncate',
            leadingIcon: 'shrink-0',
            leadingAvatar: 'shrink-0',
            leadingAvatarSize: '',
            trailingIcon: 'shrink-0'
          },
          variants: {
            buttonGroup: {
              horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
              vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
            },
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            variant: {
              solid: '',
              outline: '',
              soft: '',
              subtle: '',
              ghost: '',
              link: ''
            },
            size: {
              xs: {
                base: 'px-2 py-1 text-xs gap-1',
                leadingIcon: 'size-4',
                leadingAvatarSize: '3xs',
                trailingIcon: 'size-4'
              },
              sm: {
                base: 'px-2.5 py-1.5 text-xs gap-1.5',
                leadingIcon: 'size-4',
                leadingAvatarSize: '3xs',
                trailingIcon: 'size-4'
              },
              md: {
                base: 'px-2.5 py-1.5 text-sm gap-1.5',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs',
                trailingIcon: 'size-5'
              },
              lg: {
                base: 'px-3 py-2 text-sm gap-2',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs',
                trailingIcon: 'size-5'
              },
              xl: {
                base: 'px-3 py-2 text-base gap-2',
                leadingIcon: 'size-6',
                leadingAvatarSize: 'xs',
                trailingIcon: 'size-6'
              }
            },
            block: {
              true: {
                base: 'w-full justify-center',
                trailingIcon: 'ms-auto'
              }
            },
            square: {
              true: ''
            },
            leading: {
              true: ''
            },
            trailing: {
              true: ''
            },
            loading: {
              true: ''
            },
            active: {
              true: {
                base: ''
              },
              false: {
                base: ''
              }
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              variant: 'solid',
              class: 'text-inverted bg-primary hover:bg-primary/75 active:bg-primary/75 disabled:bg-primary aria-disabled:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'
            },
            {
              color: 'primary',
              variant: 'outline',
              class: 'ring ring-inset ring-primary/50 text-primary hover:bg-primary/10 active:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
            },
            {
              color: 'primary',
              variant: 'soft',
              class: 'text-primary bg-primary/10 hover:bg-primary/15 active:bg-primary/15 focus:outline-none focus-visible:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10'
            },
            {
              color: 'primary',
              variant: 'subtle',
              class: 'text-primary ring ring-inset ring-primary/25 bg-primary/10 hover:bg-primary/15 active:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
            },
            {
              color: 'primary',
              variant: 'ghost',
              class: 'text-primary hover:bg-primary/10 active:bg-primary/10 focus:outline-none focus-visible:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
            },
            {
              color: 'primary',
              variant: 'link',
              class: 'text-primary hover:text-primary/75 active:text-primary/75 disabled:text-primary aria-disabled:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary'
            },
            {
              color: 'neutral',
              variant: 'solid',
              class: 'text-inverted bg-inverted hover:bg-inverted/90 active:bg-inverted/90 disabled:bg-inverted aria-disabled:bg-inverted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-inverted'
            },
            {
              color: 'neutral',
              variant: 'outline',
              class: 'ring ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default aria-disabled:bg-default focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
            },
            {
              color: 'neutral',
              variant: 'soft',
              class: 'text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 focus:outline-none focus-visible:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated'
            },
            {
              color: 'neutral',
              variant: 'subtle',
              class: 'ring ring-inset ring-accented text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
            },
            {
              color: 'neutral',
              variant: 'ghost',
              class: 'text-default hover:bg-elevated active:bg-elevated focus:outline-none focus-visible:bg-elevated hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent'
            },
            {
              color: 'neutral',
              variant: 'link',
              class: 'text-muted hover:text-default active:text-default disabled:text-muted aria-disabled:text-muted focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted'
            },
            {
              size: 'xs',
              square: true,
              class: 'p-1'
            },
            {
              size: 'sm',
              square: true,
              class: 'p-1.5'
            },
            {
              size: 'md',
              square: true,
              class: 'p-1.5'
            },
            {
              size: 'lg',
              square: true,
              class: 'p-2'
            },
            {
              size: 'xl',
              square: true,
              class: 'p-2'
            },
            {
              loading: true,
              leading: true,
              class: {
                leadingIcon: 'animate-spin'
              }
            },
            {
              loading: true,
              leading: false,
              trailing: true,
              class: {
                trailingIcon: 'animate-spin'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            variant: 'solid',
            size: 'md'
          }
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      ui: {
        button: {
          slots: {
            base: [
              'rounded-md font-medium inline-flex items-center disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75',
              'transition-colors'
            ],
            label: 'truncate',
            leadingIcon: 'shrink-0',
            leadingAvatar: 'shrink-0',
            leadingAvatarSize: '',
            trailingIcon: 'shrink-0'
          },
          variants: {
            buttonGroup: {
              horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
              vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
            },
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            variant: {
              solid: '',
              outline: '',
              soft: '',
              subtle: '',
              ghost: '',
              link: ''
            },
            size: {
              xs: {
                base: 'px-2 py-1 text-xs gap-1',
                leadingIcon: 'size-4',
                leadingAvatarSize: '3xs',
                trailingIcon: 'size-4'
              },
              sm: {
                base: 'px-2.5 py-1.5 text-xs gap-1.5',
                leadingIcon: 'size-4',
                leadingAvatarSize: '3xs',
                trailingIcon: 'size-4'
              },
              md: {
                base: 'px-2.5 py-1.5 text-sm gap-1.5',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs',
                trailingIcon: 'size-5'
              },
              lg: {
                base: 'px-3 py-2 text-sm gap-2',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs',
                trailingIcon: 'size-5'
              },
              xl: {
                base: 'px-3 py-2 text-base gap-2',
                leadingIcon: 'size-6',
                leadingAvatarSize: 'xs',
                trailingIcon: 'size-6'
              }
            },
            block: {
              true: {
                base: 'w-full justify-center',
                trailingIcon: 'ms-auto'
              }
            },
            square: {
              true: ''
            },
            leading: {
              true: ''
            },
            trailing: {
              true: ''
            },
            loading: {
              true: ''
            },
            active: {
              true: {
                base: ''
              },
              false: {
                base: ''
              }
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              variant: 'solid',
              class: 'text-inverted bg-primary hover:bg-primary/75 active:bg-primary/75 disabled:bg-primary aria-disabled:bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'
            },
            {
              color: 'primary',
              variant: 'outline',
              class: 'ring ring-inset ring-primary/50 text-primary hover:bg-primary/10 active:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
            },
            {
              color: 'primary',
              variant: 'soft',
              class: 'text-primary bg-primary/10 hover:bg-primary/15 active:bg-primary/15 focus:outline-none focus-visible:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10'
            },
            {
              color: 'primary',
              variant: 'subtle',
              class: 'text-primary ring ring-inset ring-primary/25 bg-primary/10 hover:bg-primary/15 active:bg-primary/15 disabled:bg-primary/10 aria-disabled:bg-primary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary'
            },
            {
              color: 'primary',
              variant: 'ghost',
              class: 'text-primary hover:bg-primary/10 active:bg-primary/10 focus:outline-none focus-visible:bg-primary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent'
            },
            {
              color: 'primary',
              variant: 'link',
              class: 'text-primary hover:text-primary/75 active:text-primary/75 disabled:text-primary aria-disabled:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary'
            },
            {
              color: 'neutral',
              variant: 'solid',
              class: 'text-inverted bg-inverted hover:bg-inverted/90 active:bg-inverted/90 disabled:bg-inverted aria-disabled:bg-inverted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-inverted'
            },
            {
              color: 'neutral',
              variant: 'outline',
              class: 'ring ring-inset ring-accented text-default bg-default hover:bg-elevated active:bg-elevated disabled:bg-default aria-disabled:bg-default focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
            },
            {
              color: 'neutral',
              variant: 'soft',
              class: 'text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 focus:outline-none focus-visible:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated'
            },
            {
              color: 'neutral',
              variant: 'subtle',
              class: 'ring ring-inset ring-accented text-default bg-elevated hover:bg-accented/75 active:bg-accented/75 disabled:bg-elevated aria-disabled:bg-elevated focus:outline-none focus-visible:ring-2 focus-visible:ring-inverted'
            },
            {
              color: 'neutral',
              variant: 'ghost',
              class: 'text-default hover:bg-elevated active:bg-elevated focus:outline-none focus-visible:bg-elevated hover:disabled:bg-transparent dark:hover:disabled:bg-transparent hover:aria-disabled:bg-transparent dark:hover:aria-disabled:bg-transparent'
            },
            {
              color: 'neutral',
              variant: 'link',
              class: 'text-muted hover:text-default active:text-default disabled:text-muted aria-disabled:text-muted focus:outline-none focus-visible:ring-inset focus-visible:ring-2 focus-visible:ring-inverted'
            },
            {
              size: 'xs',
              square: true,
              class: 'p-1'
            },
            {
              size: 'sm',
              square: true,
              class: 'p-1.5'
            },
            {
              size: 'md',
              square: true,
              class: 'p-1.5'
            },
            {
              size: 'lg',
              square: true,
              class: 'p-2'
            },
            {
              size: 'xl',
              square: true,
              class: 'p-2'
            },
            {
              loading: true,
              leading: true,
              class: {
                leadingIcon: 'animate-spin'
              }
            },
            {
              loading: true,
              leading: false,
              trailing: true,
              class: {
                trailingIcon: 'animate-spin'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            variant: 'solid',
            size: 'md'
          }
        }
      }
    })
  ]
})
为了可读性,compoundVariants 中的某些颜色被省略。请查看 GitHub 上的源代码。