nvim
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
# Conventions
|
||||
|
||||
Coding patterns specific to Nuxt UI.
|
||||
|
||||
## Auto-registered modules
|
||||
|
||||
Nuxt 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`:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui'],
|
||||
icon: { /* @nuxt/icon options */ },
|
||||
fonts: { /* @nuxt/fonts options */ },
|
||||
colorMode: { /* @nuxtjs/color-mode options */ }
|
||||
})
|
||||
```
|
||||
|
||||
Disable any of them: `ui: { fonts: false }`, `ui: { colorMode: false }`.
|
||||
|
||||
## Content module integration
|
||||
|
||||
When using `@nuxt/content`, it **must** come after `@nuxt/ui` in the `modules` array — otherwise prose components won't be available:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui', '@nuxt/content']
|
||||
})
|
||||
```
|
||||
|
||||
Add `@source` in your CSS so Tailwind generates classes used in markdown/MDC:
|
||||
|
||||
```css
|
||||
/* app/assets/css/main.css */
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@source "../../../content/**/*";
|
||||
```
|
||||
|
||||
Use `mapContentNavigation` to transform content navigation for components like `UBreadcrumb`:
|
||||
|
||||
```ts
|
||||
import { mapContentNavigation } from '@nuxt/ui/utils/content'
|
||||
import { findPageBreadcrumb } from '@nuxt/content/utils'
|
||||
|
||||
const breadcrumb = computed(() =>
|
||||
mapContentNavigation(findPageBreadcrumb(navigation.value, page.value?.path))
|
||||
)
|
||||
```
|
||||
|
||||
## IDE setup
|
||||
|
||||
Recommended `.vscode/settings.json` for Tailwind IntelliSense autocomplete with Nuxt UI:
|
||||
|
||||
```json
|
||||
{
|
||||
"files.associations": { "*.css": "tailwindcss" },
|
||||
"editor.quickSuggestions": { "strings": "on" },
|
||||
"tailwindCSS.classAttributes": ["class", "ui"],
|
||||
"tailwindCSS.classFunctions": ["defineAppConfig"]
|
||||
}
|
||||
```
|
||||
|
||||
## UApp wrapper
|
||||
|
||||
Always wrap your app in `UApp` — it provides:
|
||||
|
||||
- Toast container (`useToast`)
|
||||
- Tooltip provider
|
||||
- Programmatic overlay context (`useOverlay`)
|
||||
- i18n locale support
|
||||
|
||||
```vue
|
||||
<UApp :locale="fr">
|
||||
<NuxtPage /> <!-- or <RouterView /> for Vue -->
|
||||
</UApp>
|
||||
```
|
||||
|
||||
## Icons
|
||||
|
||||
Nuxt UI registers `@nuxt/icon` automatically. Format: `i-{collection}-{name}`. Prefer `lucide` collection.
|
||||
|
||||
```vue
|
||||
<UIcon name="i-lucide-sun" class="size-5" />
|
||||
<UButton icon="i-lucide-plus" label="Add" />
|
||||
<UAlert icon="i-lucide-info" title="Heads up" />
|
||||
```
|
||||
|
||||
Install collections locally for reliable SSR and best performance:
|
||||
|
||||
```bash
|
||||
pnpm i @iconify-json/lucide
|
||||
pnpm i @iconify-json/simple-icons
|
||||
```
|
||||
|
||||
Custom local collections (Nuxt only):
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
icon: {
|
||||
customCollections: [{
|
||||
prefix: 'custom',
|
||||
dir: './app/assets/icons'
|
||||
}]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Default icon overrides
|
||||
|
||||
Components like `Modal`, `Select`, `Accordion`, etc. use default icons from `appConfig.ui.icons`. Override them globally:
|
||||
|
||||
```ts
|
||||
// app.config.ts
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
icons: {
|
||||
loading: 'i-lucide-refresh-cw',
|
||||
close: 'i-lucide-x',
|
||||
check: 'i-lucide-check',
|
||||
chevronDown: 'i-lucide-chevron-down',
|
||||
chevronRight: 'i-lucide-chevron-right',
|
||||
arrowLeft: 'i-lucide-arrow-left',
|
||||
arrowRight: 'i-lucide-arrow-right'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Slot patterns
|
||||
|
||||
Most components follow consistent slot naming:
|
||||
|
||||
| Slot | Used by | Purpose |
|
||||
| ----------- | -------------------------------------- | ------------------------------- |
|
||||
| `#header` | Card, Modal, Slideover, DashboardPanel | Top section |
|
||||
| `#body` | DashboardPanel | Scrollable content area |
|
||||
| `#footer` | Card, Modal, Slideover, DashboardPanel | Bottom section |
|
||||
| `#left` | Page, DashboardNavbar | Left sidebar or content |
|
||||
| `#right` | Page, DashboardNavbar, Header | Right sidebar or content |
|
||||
| `#leading` | Input, Button, Alert | Before main content (icon area) |
|
||||
| `#trailing` | Input, Button | After main content (icon area) |
|
||||
| `#content` | Modal, Slideover, Popover, Tooltip | Full content override |
|
||||
| `#default` | Most components | Main content area |
|
||||
|
||||
## Items arrays
|
||||
|
||||
Many components accept an `items` prop. Two patterns:
|
||||
|
||||
**Flat array** — plain list:
|
||||
|
||||
```ts
|
||||
const items = [
|
||||
{ label: 'Edit', icon: 'i-lucide-pencil' },
|
||||
{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }
|
||||
]
|
||||
```
|
||||
|
||||
**Nested array** — groups with automatic separators between them:
|
||||
|
||||
```ts
|
||||
const items = [
|
||||
[
|
||||
{ label: 'Edit', icon: 'i-lucide-pencil' },
|
||||
{ label: 'Duplicate', icon: 'i-lucide-copy' }
|
||||
],
|
||||
[
|
||||
{ label: 'Delete', icon: 'i-lucide-trash', color: 'error' }
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Components supporting nested arrays: `UDropdownMenu`, `UContextMenu`, `UNavigationMenu`.
|
||||
`UCommandPalette` uses a `groups` prop instead, with an `items` array on each group.
|
||||
|
||||
## Composables
|
||||
|
||||
### useToast
|
||||
|
||||
```ts
|
||||
const toast = useToast()
|
||||
|
||||
toast.add({
|
||||
title: 'Success',
|
||||
description: 'Item saved',
|
||||
color: 'success',
|
||||
icon: 'i-lucide-check-circle',
|
||||
duration: 5000,
|
||||
actions: [{ label: 'Undo', onClick: () => {} }]
|
||||
})
|
||||
|
||||
toast.remove('toast-id')
|
||||
toast.clear()
|
||||
```
|
||||
|
||||
### useOverlay
|
||||
|
||||
Programmatic modals, slideovers, drawers — no template `v-model` needed. See [overlays recipe](../recipes/overlays.md) for full patterns.
|
||||
|
||||
```ts
|
||||
const overlay = useOverlay()
|
||||
const modal = overlay.create(MyComponent)
|
||||
const instance = modal.open({ title: 'Confirm?' })
|
||||
if (await instance.result) { /* confirmed */ }
|
||||
```
|
||||
|
||||
### defineShortcuts
|
||||
|
||||
```ts
|
||||
defineShortcuts({
|
||||
meta_k: () => openSearch(),
|
||||
escape: () => close(),
|
||||
meta_enter: {
|
||||
handler: () => submit(),
|
||||
whenever: [isFormValid]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Keys: `meta` (Cmd/Ctrl), `ctrl`, `alt`, `shift`. Separator: `_`.
|
||||
|
||||
### extractShortcuts
|
||||
|
||||
Wire up keyboard shortcuts from menu items:
|
||||
|
||||
```ts
|
||||
const items = [
|
||||
{ label: 'New file', kbds: ['meta', 'n'], onSelect: () => newFile() },
|
||||
{ label: 'Save', kbds: ['meta', 's'], onSelect: () => save() }
|
||||
]
|
||||
|
||||
defineShortcuts(extractShortcuts(items))
|
||||
```
|
||||
|
||||
### Internationalization (i18n)
|
||||
|
||||
Nuxt UI supports 50+ locales. Set the locale on `UApp` — all components inherit it.
|
||||
|
||||
#### Static locale
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { fr } from '@nuxt/ui/locale'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="fr">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### Extend a built-in locale
|
||||
|
||||
`extendLocale` is auto-imported. Override specific messages or the `code` (affects date/time formatting in Calendar, InputDate, InputTime):
|
||||
|
||||
```ts
|
||||
import { en } from '@nuxt/ui/locale'
|
||||
|
||||
const locale = extendLocale(en, {
|
||||
code: 'en-AU',
|
||||
messages: {
|
||||
commandPalette: { placeholder: 'Search a component...' }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Custom locale from scratch
|
||||
|
||||
```ts
|
||||
import type { Messages } from '@nuxt/ui'
|
||||
|
||||
const locale = defineLocale<Messages>({
|
||||
name: 'My locale',
|
||||
code: 'en',
|
||||
dir: 'ltr',
|
||||
messages: {
|
||||
// all component message keys
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Dynamic locale with @nuxtjs/i18n
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui', '@nuxtjs/i18n'],
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'fr', name: 'Français' },
|
||||
{ code: 'ar', name: 'العربية' }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import * as locales from '@nuxt/ui/locale'
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
const lang = computed(() => locales[locale.value]?.code)
|
||||
const dir = computed(() => locales[locale.value]?.dir)
|
||||
|
||||
useHead({
|
||||
htmlAttrs: { lang, dir }
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UApp :locale="locales[locale]">
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
|
||||
Each 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.
|
||||
|
||||
## Color mode
|
||||
|
||||
Nuxt UI registers `@nuxtjs/color-mode` automatically. Built-in components for switching:
|
||||
|
||||
- `UColorModeButton` — single button toggle (light/dark)
|
||||
- `UColorModeSwitch` — toggle switch
|
||||
- `UColorModeSelect` — dropdown with system/light/dark options
|
||||
- `UColorModeAvatar` — displays different avatar per mode
|
||||
- `UColorModeImage` — displays different image per mode
|
||||
|
||||
For custom color mode UI, use `useColorMode` with `ClientOnly` to avoid hydration mismatch:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const isDark = computed({
|
||||
get: () => colorMode.value === 'dark',
|
||||
set: (v) => { colorMode.preference = v ? 'dark' : 'light' }
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<USwitch v-model="isDark" />
|
||||
<template #fallback>
|
||||
<div class="size-8" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Official templates
|
||||
|
||||
Bootstrap a project from a template instead of starting from scratch:
|
||||
|
||||
```bash
|
||||
npx nuxi@latest init -t ui # Starter
|
||||
npx nuxi@latest init -t ui/dashboard # Dashboard
|
||||
npx nuxi@latest init -t ui/docs # Docs (Nuxt Content)
|
||||
npx nuxi@latest init -t ui/landing # Landing page
|
||||
npx nuxi@latest init -t ui/saas # SaaS (landing + pricing + docs + blog)
|
||||
npx nuxi@latest init -t ui/chat # AI chat (Vercel AI SDK)
|
||||
npx nuxi@latest init -t ui/editor # Rich text editor
|
||||
npx nuxi@latest init -t ui/portfolio # Portfolio
|
||||
npx nuxi@latest init -t ui/changelog # Changelog
|
||||
npx nuxi@latest init -t ui/calendar # Calendar
|
||||
```
|
||||
|
||||
## Responsive patterns
|
||||
|
||||
- Dashboard sidebar hides on mobile, shows a slideover/drawer via `UDashboardSidebar` `mode` prop
|
||||
- `UHeader` body slot is the mobile menu content (shown when hamburger is tapped)
|
||||
- Most components handle responsiveness automatically — avoid manual breakpoint classes unless needed
|
||||
- Use `UPageAside` for sidebars that should hide below `lg` breakpoint
|
||||
Reference in New Issue
Block a user