Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Guidance for building UIs with Nuxt UI, the official Tailwind-based component library for Nuxt.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/guidelines/conventions.md
1# Conventions23Coding patterns specific to Nuxt UI.45## Auto-registered modules67Nuxt UI automatically registers `@nuxt/icon`, `@nuxt/fonts`, and `@nuxtjs/color-mode`. Do **not** add them to your `modules` array. Configure them via root-level keys in `nuxt.config.ts`:89```ts10// nuxt.config.ts11export default defineNuxtConfig({12modules: ['@nuxt/ui'],13icon: { /* @nuxt/icon options */ },14fonts: { /* @nuxt/fonts options */ },15colorMode: { /* @nuxtjs/color-mode options */ }16})17```1819Disable any of them: `ui: { fonts: false }`, `ui: { colorMode: false }`.2021## Content module integration2223When using `@nuxt/content`, it **must** come after `@nuxt/ui` in the `modules` array — otherwise prose components won't be available:2425```ts26// nuxt.config.ts27export default defineNuxtConfig({28modules: ['@nuxt/ui', '@nuxt/content']29})30```3132Add `@source` in your CSS so Tailwind generates classes used in markdown/MDC:3334```css35/* app/assets/css/main.css */36@import "tailwindcss";37@import "@nuxt/ui";3839@source "../../../content/**/*";40```4142Use `mapContentNavigation` to transform content navigation for components like `UBreadcrumb`:4344```ts45import { mapContentNavigation } from '@nuxt/ui/utils/content'46import { findPageBreadcrumb } from '@nuxt/content/utils'4748const breadcrumb = computed(() =>49mapContentNavigation(findPageBreadcrumb(navigation.value, page.value?.path))50)51```5253## IDE setup5455Recommended `.vscode/settings.json` for Tailwind IntelliSense autocomplete with Nuxt UI:5657```json58{59"files.associations": { "*.css": "tailwindcss" },60"editor.quickSuggestions": { "strings": "on" },61"tailwindCSS.classAttributes": ["class", "ui"],62"tailwindCSS.classFunctions": ["defineAppConfig"]63}64```6566## UApp wrapper6768Always wrap your app in `UApp` — it provides:69- Toast container (`useToast`)70- Tooltip provider71- Programmatic overlay context (`useOverlay`)72- i18n locale support7374```vue75<UApp :locale="fr">76<NuxtPage /> <!-- or <RouterView /> for Vue -->77</UApp>78```7980## Icons8182Nuxt UI registers `@nuxt/icon` automatically. Format: `i-{collection}-{name}`. Prefer `lucide` collection.8384```vue85<UIcon name="i-lucide-sun" class="size-5" />86<UButton icon="i-lucide-plus" label="Add" />87<UAlert icon="i-lucide-info" title="Heads up" />88```8990Install collections locally for reliable SSR and best performance:9192```bash93pnpm i @iconify-json/lucide94pnpm i @iconify-json/simple-icons95```9697Custom local collections (Nuxt only):9899```ts100// nuxt.config.ts101export default defineNuxtConfig({102icon: {103customCollections: [{104prefix: 'custom',105dir: './app/assets/icons'106}]107}108})109```110111### Default icon overrides112113Components like `Modal`, `Select`, `Accordion`, etc. use default icons from `appConfig.ui.icons`. Override them globally:114115```ts116// app.config.ts117export default defineAppConfig({118ui: {119icons: {120loading: 'i-lucide-refresh-cw',121close: 'i-lucide-x',122check: 'i-lucide-check',123chevronDown: 'i-lucide-chevron-down',124chevronRight: 'i-lucide-chevron-right',125arrowLeft: 'i-lucide-arrow-left',126arrowRight: 'i-lucide-arrow-right'127}128}129})130```131132## Slot patterns133134Most components follow consistent slot naming:135136| Slot | Used by | Purpose |137|---|---|---|138| `#header` | Card, Modal, Slideover, DashboardPanel | Top section |139| `#body` | DashboardPanel | Scrollable content area |140| `#footer` | Card, Modal, Slideover, DashboardPanel | Bottom section |141| `#left` | Page, DashboardNavbar | Left sidebar or content |142| `#right` | Page, DashboardNavbar, Header | Right sidebar or content |143| `#leading` | Input, Button, Alert | Before main content (icon area) |144| `#trailing` | Input, Button | After main content (icon area) |145| `#content` | Modal, Slideover, Popover, Tooltip | Full content override |146| `#default` | Most components | Main content area |147148## Items arrays149150Many components accept an `items` prop. Two patterns:151152**Flat array** — plain list:153154```ts155const items = [156{ label: 'Edit', icon: 'i-lucide-pencil' },157{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }158]159```160161**Nested array** — groups with automatic separators between them:162163```ts164const items = [165[166{ label: 'Edit', icon: 'i-lucide-pencil' },167{ label: 'Duplicate', icon: 'i-lucide-copy' }168],169[170{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }171]172]173```174175Components supporting nested arrays: `UDropdownMenu`, `UContextMenu`, `UCommandPalette`, `UNavigationMenu`.176177## Composables178179### useToast180181```ts182const toast = useToast()183184toast.add({185title: 'Success',186description: 'Item saved',187color: 'success',188icon: 'i-lucide-check-circle',189duration: 5000,190actions: [{ label: 'Undo', onClick: () => {} }]191})192193toast.remove('toast-id')194toast.clear()195```196197### useOverlay198199Programmatic modals, slideovers, drawers — no template `v-model` needed. See [overlays recipe](../recipes/overlays.md) for full patterns.200201```ts202const overlay = useOverlay()203const modal = overlay.create(MyComponent)204const instance = modal.open({ title: 'Confirm?' })205if (await instance.result) { /* confirmed */ }206```207208### defineShortcuts209210```ts211defineShortcuts({212meta_k: () => openSearch(),213escape: () => close(),214meta_enter: {215handler: () => submit(),216whenever: [isFormValid]217}218})219```220221Keys: `meta` (Cmd/Ctrl), `ctrl`, `alt`, `shift`. Separator: `_`.222223### extractShortcuts224225Wire up keyboard shortcuts from menu items:226227```ts228const items = [229{ label: 'New file', kbds: ['meta', 'n'], onSelect: () => newFile() },230{ label: 'Save', kbds: ['meta', 's'], onSelect: () => save() }231]232233defineShortcuts(extractShortcuts(items))234```235236### Internationalization (i18n)237238Nuxt UI supports 50+ locales. Set the locale on `UApp` — all components inherit it.239240#### Static locale241242```vue243<script setup lang="ts">244import { fr } from '@nuxt/ui/locale'245</script>246247<template>248<UApp :locale="fr">249<NuxtPage />250</UApp>251</template>252```253254#### Extend a built-in locale255256`extendLocale` is auto-imported. Override specific messages or the `code` (affects date/time formatting in Calendar, InputDate, InputTime):257258```ts259import { en } from '@nuxt/ui/locale'260261const locale = extendLocale(en, {262code: 'en-AU',263messages: {264commandPalette: { placeholder: 'Search a component...' }265}266})267```268269#### Custom locale from scratch270271```ts272import type { Messages } from '@nuxt/ui'273274const locale = defineLocale<Messages>({275name: 'My locale',276code: 'en',277dir: 'ltr',278messages: {279// all component message keys280}281})282```283284#### Dynamic locale with @nuxtjs/i18n285286```ts287// nuxt.config.ts288export default defineNuxtConfig({289modules: ['@nuxt/ui', '@nuxtjs/i18n'],290i18n: {291locales: [292{ code: 'en', name: 'English' },293{ code: 'fr', name: 'Français' },294{ code: 'ar', name: 'العربية' }295]296}297})298```299300```vue301<script setup lang="ts">302import * as locales from '@nuxt/ui/locale'303304const { locale } = useI18n()305306const lang = computed(() => locales[locale.value]?.code)307const dir = computed(() => locales[locale.value]?.dir)308309useHead({310htmlAttrs: { lang, dir }311})312</script>313314<template>315<UApp :locale="locales[locale]">316<NuxtPage />317</UApp>318</template>319```320321Each locale has a `dir` property (`'ltr'` or `'rtl'`). `UApp` uses it to set directionality on all components. Use `useHead` to propagate `lang` and `dir` to the `<html>` element.322323## Color mode324325Nuxt UI registers `@nuxtjs/color-mode` automatically. Built-in components for switching:326- `UColorModeButton` — single button toggle (light/dark)327- `UColorModeSwitch` — toggle switch328- `UColorModeSelect` — dropdown with system/light/dark options329- `UColorModeAvatar` — displays different avatar per mode330- `UColorModeImage` — displays different image per mode331332For custom color mode UI, use `useColorMode` with `ClientOnly` to avoid hydration mismatch:333334```vue335<script setup lang="ts">336const colorMode = useColorMode()337338const isDark = computed({339get: () => colorMode.value === 'dark',340set: (v) => { colorMode.preference = v ? 'dark' : 'light' }341})342</script>343344<template>345<ClientOnly>346<USwitch v-model="isDark" />347<template #fallback>348<div class="size-8" />349</template>350</ClientOnly>351</template>352```353354## Official templates355356Bootstrap a project from a template instead of starting from scratch:357358```bash359npx nuxi@latest init -t ui # Starter360npx nuxi@latest init -t ui/dashboard # Dashboard361npx nuxi@latest init -t ui/docs # Docs (Nuxt Content)362npx nuxi@latest init -t ui/landing # Landing page363npx nuxi@latest init -t ui/saas # SaaS (landing + pricing + docs + blog)364npx nuxi@latest init -t ui/chat # AI chat (Vercel AI SDK)365npx nuxi@latest init -t ui/editor # Rich text editor366npx nuxi@latest init -t ui/portfolio # Portfolio367npx nuxi@latest init -t ui/changelog # Changelog368```369370## Responsive patterns371372- Dashboard sidebar hides on mobile, shows a slideover/drawer via `UDashboardSidebar` `mode` prop373- `UHeader` body slot is the mobile menu content (shown when hamburger is tapped)374- Most components handle responsiveness automatically — avoid manual breakpoint classes unless needed375- Use `UPageAside` for sidebars that should hide below `lg` breakpoint376