Nuxt UI
Vue component library built on Reka UI + Tailwind CSS + Tailwind Variants. Works with Nuxt, Vue (Vite), Laravel (Inertia), and AdonisJS (Inertia).
Installation
Nuxt
pnpm add @nuxt/ui tailwindcss
export default defineNuxtConfig({
modules: ['@nuxt/ui'],
css: ['~/assets/css/main.css']
})
@import "tailwindcss";
@import "@nuxt/ui";
<!-- app.vue -->
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
Vue (Vite)
pnpm add @nuxt/ui tailwindcss
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui()
]
})
import './assets/main.css'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import ui from '@nuxt/ui/vue-plugin'
import App from './App.vue'
const app = createApp(App)
const router = createRouter({
routes: [],
history: createWebHistory()
})
app.use(router)
app.use(ui)
app.mount('#app')
@import "tailwindcss";
@import "@nuxt/ui";
<!-- src/App.vue -->
<template>
<UApp>
<RouterView />
</UApp>
</template>
Vue: Add class="isolate" to your root <div id="app"> in index.html.
Vue + Inertia: Use ui({ router: 'inertia' }) in vite.config.ts.
UApp
Wrapping your app in UApp is required β it provides global config for toasts, tooltips, and programmatic overlays. It also accepts a locale prop for i18n (see composables reference).
Icons
Nuxt UI uses Iconify for 200,000+ icons. In Nuxt, @nuxt/icon is auto-registered. In Vue, icons work out of the box via the Vite plugin.
Naming convention
Icons use the format i-{collection}-{name}:
<UIcon name="i-lucide-sun" class="size-5" />
<UButton icon="i-lucide-plus" label="Add" />
<UAlert icon="i-lucide-info" title="Heads up" />
Browse all icons at icones.js.org. The lucide collection is used throughout Nuxt UI defaults.
Install icon collections locally
pnpm i @iconify-json/lucide
pnpm i @iconify-json/simple-icons
Custom local collections (Nuxt)
export default defineNuxtConfig({
icon: {
customCollections: [{
prefix: 'custom',
dir: './app/assets/icons'
}]
}
})
<UIcon name="i-custom-my-icon" />
Theming & Branding
Nuxt UI ships with a default look. The goal is to adapt it to your brand so every app looks unique.
Always use semantic utilities (text-default, bg-elevated, border-muted), never raw Tailwind palette colors. See references/theming.md for the full list.
Colors
7 semantic colors (primary, secondary, success, info, warning, error, neutral) configurable at runtime:
export default defineAppConfig({
ui: { colors: { primary: 'indigo', neutral: 'zinc' } }
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: { colors: { primary: 'indigo', neutral: 'zinc' } }
})
]
})
Customizing components
Override priority (highest wins): ui prop / class prop > global config > theme defaults.
The ui prop overrides a component's slots after variants are computed β it wins over everything:
<UButton :ui="{ base: 'rounded-none', trailingIcon: 'size-3 rotate-90' }" />
<UCard :ui="{ header: 'bg-muted', body: 'p-8' }" />
Read the generated theme file to find slot names for any component:
- Nuxt:
.nuxt/ui/<component>.ts
- Vue:
node_modules/.nuxt-ui/ui/<component>.ts
For CSS variables, custom colors, global config, compound variants, and a full brand customization playbook, see references/theming.md
Composables
const toast = useToast()
toast.add({ title: 'Saved', color: 'success', icon: 'i-lucide-check' })
const overlay = useOverlay()
const modal = overlay.create(MyModal)
const { result } = modal.open({ title: 'Confirm' })
await result
defineShortcuts({
meta_k: () => openSearch(),
escape: () => close()
})
For full composable reference, see references/composables.md
Form validation
Uses Standard Schema β works with Zod, Valibot, Yup, or Joi.
<script setup lang="ts">
import { z } from 'zod'
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Min 8 characters')
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({ email: '', password: '' })
function onSubmit() {
// UForm validates before emitting @submit β state is valid here
}
</script>
<template>
<UForm :schema="schema" :state="state" @submit="onSubmit">
<UFormField name="email" label="Email" required>
<UInput v-model="state.email" type="email" />
</UFormField>
<UFormField name="password" label="Password" required>
<UInput v-model="state.password" type="password" />
</UFormField>
<UButton type="submit">Sign in</UButton>
</UForm>
</template>
For all form components and validation patterns, see references/components.md
Overlays
<!-- Modal -->
<UModal v-model:open="isOpen" title="Edit" description="Edit your profile">
<template #body>Content</template>
<template #footer>
<UButton variant="ghost" @click="isOpen = false">Cancel</UButton>
<UButton @click="save">Save</UButton>
</template>
</UModal>
<!-- Slideover (side panel) -->
<USlideover v-model:open="isOpen" title="Settings" side="right">
<template #body>Content</template>
</USlideover>
<!-- Dropdown menu (flat array) -->
<UDropdownMenu :items="[
{ label: 'Edit', icon: 'i-lucide-pencil' },