Chapter 09

UI 组件库:Nuxt UI / Shadcn-vue

选择合适的 UI 方案,掌握 Tailwind CSS 集成、主题定制和暗色模式的完整实现路径

9.1 Vue 3 生态 UI 方案对比

设计风格特点适用场景
Nuxt UI v3Tailwind,高度可定制Nuxt 官方,Reka UI 无障碍基础Nuxt 全栈项目首选
Shadcn-vueTailwind,复制粘贴风格拥有代码所有权,无版本锁定需要完全控制组件代码
Element Plus传统企业风格功能完整,中文文档后台管理系统
Naive UI现代简洁完整 TypeScript,主题可配中型项目
PrimeVue多主题可选组件丰富,企业级数据密集应用
Headless UI无样式纯逻辑,完全自定义样式需要完全自定义 UI 的项目

9.2 Nuxt UI v3 完整集成

npm install @nuxt/ui
export default defineNuxtConfig({
  modules: ['@nuxt/ui'],
  // Nuxt UI 会自动配置 Tailwind CSS 和 ColorMode
})

常用组件使用

<template>
  <!-- 按钮 -->
  <UButton color="primary" variant="solid" icon="i-heroicons-plus">
    新建项目
  </UButton>

  <!-- 表单输入 -->
  <UForm :schema="schema" :state="formState" @submit="onSubmit">
    <UFormField label="用户名" name="username">
      <UInput v-model="formState.username" placeholder="请输入用户名" />
    </UFormField>
    <UButton type="submit">提交</UButton>
  </UForm>

  <!-- 模态框 -->
  <UModal v-model:open="isOpen">
    <template #content>
      <UCard>
        <template #header>确认操作</template>
        <p>确定要删除吗?</p>
        <template #footer>
          <UButton @click="isOpen = false">取消</UButton>
          <UButton color="red" @click="confirmDelete">删除</UButton>
        </template>
      </UCard>
    </template>
  </UModal>

  <!-- 数据表格 -->
  <UTable :data="rows" :columns="columns">
    <template #actions-cell="{ row }">
      <UButton size="xs" @click="edit(row)">编辑</UButton>
    </template>
  </UTable>
</template>

9.3 主题定制

export default defineAppConfig({
  ui: {
    // 全局颜色主题(使用 Tailwind 颜色)
    colors: {
      primary: 'green',     // 主色:绿色
      secondary: 'teal',   // 副色:青色
      neutral: 'zinc'      // 中性色
    },
    // 组件级别覆盖
    button: {
      defaultVariants: {
        color: 'primary',
        variant: 'solid',
        size: 'md'
      }
    },
    card: {
      slots: {
        root: 'rounded-xl shadow-sm border border-green-100'
      }
    }
  }
})

9.4 Shadcn-vue:拥有代码所有权的 UI

Shadcn-vue 是 shadcn/ui 的 Vue 版本。不同于传统 UI 库,它将组件代码直接复制到你的项目中,你完全拥有代码,可以随意修改。

# 初始化(会创建 components/ui/ 目录和 tailwind 配置)
npx shadcn-vue@latest init

# 按需添加组件(代码会复制到 src/components/ui/)
npx shadcn-vue@latest add button
npx shadcn-vue@latest add dialog
npx shadcn-vue@latest add form
npx shadcn-vue@latest add table
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'

const open = ref(false)
</script>

<template>
  <Button variant="default" @click="open = true">打开对话框</Button>

  <Dialog v-model:open="open">
    <DialogContent>
      <DialogHeader>
        <DialogTitle>编辑信息</DialogTitle>
      </DialogHeader>
      <Input placeholder="输入内容" />
      <Button @click="open = false">保存</Button>
    </DialogContent>
  </Dialog>
</template>

9.5 Tailwind CSS 集成

# 在 Nuxt 3 中安装(通过模块方式)
npm install --save-dev @nuxtjs/tailwindcss
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss']
})
import type { Config } from 'tailwindcss'

export default {
  content: [],   // Nuxt 模块自动配置 content
  theme: {
    extend: {
      colors: {
        brand: {
          50:  '#f0fdf4',
          500: '#22c55e',
          900: '#14532d',
        }
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif']
      }
    }
  },
  plugins: [
    require('@tailwindcss/typography'),   // prose 样式
    require('@tailwindcss/forms'),         // 表单样式重置
  ]
} satisfies Config

9.6 暗色模式实现

Nuxt UI 内置了 @nuxtjs/color-mode,实现暗色模式切换极为简单:

<script setup>
const colorMode = useColorMode()

function toggleDark() {
  colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
</script>

<template>
  <button @click="toggleDark">
    <span v-if="colorMode.value === 'dark'">☀️ 亮色</span>
    <span v-else>🌙 暗色</span>
  </button>
</template>
/* 使用 dark: 前缀 */
.card {
  @apply bg-white text-gray-900;
  @apply dark:bg-gray-900 dark:text-gray-100;
}
export default defineNuxtConfig({
  colorMode: {
    classSuffix: '',        // 使用 class="dark"(Tailwind 默认)
    preference: 'system',   // 默认跟随系统
    fallback: 'light'        // SSR 回退值
  }
})

9.7 无障碍(Accessibility)最佳实践

现代 UI 库(特别是基于 Reka UI 的 Nuxt UI v3)内置了完整的 ARIA 支持和键盘导航。理解无障碍原则有助于选择和使用正确的组件。

<template>
  <!-- Nuxt UI 的 UButton 自动处理 role、aria-disabled -->
  <UButton
    :loading="pending"
    aria-label="提交表单"
    type="submit"
  >
    提交
  </UButton>

  <!-- UForm 自动关联 label 和 input 的 aria-describedby -->
  <UFormField label="邮箱地址" name="email" help="我们不会分享你的邮箱">
    <UInput v-model="email" type="email" autocomplete="email" />
  </UFormField>
</template>
选择 UI 库的建议

对于 Nuxt 项目:Nuxt UI v3 是最无缝的选择,完整集成 Nuxt 生态。对于需要完全定制化的项目:Shadcn-vue + Tailwind 是最灵活的方案,你拥有代码所有权。对于传统企业后台:Element Plus 组件更完整,中文文档更友好。不推荐在同一项目中混用多个 UI 库。

Headless UI
无样式 UI 库,只提供可访问性逻辑(ARIA、键盘导航),样式完全由开发者编写。Radix UI、Reka UI 是典型代表,Nuxt UI v3 基于 Reka UI 构建。
CVA(class-variance-authority)
用于管理 Tailwind 条件类名的工具,Shadcn-vue 使用它来处理组件的 variant(如 primary/secondary/destructive)。
CSS 变量主题
Shadcn-vue 使用 CSS 变量(--background、--foreground 等)定义颜色,暗色模式通过 .dark 类切换 CSS 变量值实现,无需 JS 条件判断。
常见误区:在 Server Component 中使用客户端 UI 组件

Nuxt UI 和 Shadcn-vue 的大多数组件(按钮、输入框、模态框)都需要在客户端运行(事件处理、动画)。如果你的页面文件是纯服务端渲染(无 'use client' 或无 ref()),使用这些组件时 Nuxt 会自动处理客户端激活(hydration)。但若 UI 库组件内部使用了浏览器 API(如 window.matchMedia 用于暗色模式检测),需用 <ClientOnly> 包裹,避免 SSR 水合错误。

本章小结

Vue 3 生态有丰富的 UI 方案,各有适用场景:Nuxt UI v3 基于 Reka UI(Headless),提供完整无障碍支持,与 Nuxt 深度集成,是 Nuxt 项目的首选;Shadcn-vue 拥有代码所有权,适合需要深度定制的场景;Element Plus 适合中大型企业后台;Naive UI TypeScript 支持出色。所有现代 UI 库都依托 Tailwind CSS 实现样式系统,通过 CSS 变量实现主题切换。暗色模式通过 @nuxtjs/color-mode + Tailwind dark: 前缀轻松实现,classSuffix: '' 配置使其与 Tailwind 的 class="dark" 策略兼容。