# Introduction ## What is Nuxt UI? A modern UI library built on [Reka UI](https://reka-ui.com/){rel="nofollow"}, [Tailwind CSS](https://tailwindcss.com/){rel="nofollow"}, and [Tailwind Variants](https://www.tailwind-variants.org/){rel="nofollow"} to ship beautiful and accessible applications with 100+ production-ready components. ::card-group :::card{icon="i-lucide-sparkles" title="Developer Experience First"} Intuitive APIs, excellent TypeScript support, auto-completion, and comprehensive docs. ::: :::card{icon="i-lucide-palette" title="Beautiful by Default"} A modern, clean design out of the box with a theme you can adapt in minutes. ::: :::card{icon="i-lucide-accessibility" title="Accessible by Default"} WAI-ARIA compliant with keyboard navigation, focus management, and screen reader support. ::: :::card{icon="i-lucide-blocks" title="Production Ready"} 100+ battle-tested components used by thousands of applications in production. ::: :: ## What's new in v4? Nuxt UI v4 marks a major milestone: Nuxt UI and Nuxt UI Pro are now unified into a single, fully open-source and free library of 100+ production-ready components and a complete Figma Kit. The migration from v3 to v4 will be much smoother than from v2 to v3. Read more in the [migration guide](https://ui.nuxt.com/docs/getting-started/migration/v4). ::note{to="https://ui.nuxt.com/docs/getting-started/migration/v3"} If you are migrating from v2, you can read more in this **migration guide**. :: ## Core technologies ### Reka UI Nuxt UI is built on top of [Reka UI](https://reka-ui.com/){rel="nofollow"} as a foundation for the components: - **WAI-ARIA Compliance**: Follows [WAI-ARIA authoring practices](https://reka-ui.com/docs/overview/accessibility){rel="nofollow"} with proper semantics and roles - **Keyboard Navigation**: Built-in keyboard support for complex components like tabs and dialogs - **Focus Management**: Intelligent focus handling that moves focus based on user interactions - **Accessible Labels**: Abstractions to simplify labeling controls for screen readers ### Tailwind CSS Nuxt UI integrates the latest [Tailwind CSS](https://tailwindcss.com/){rel="nofollow"} v4, bringing significant improvements: - **5x Faster Builds**: Full builds up to 5x faster, incremental builds over 100x faster - **Unified Toolchain**: Built-in import handling, vendor prefixing, and syntax transforms - **CSS-first Configuration**: Customize and extend directly in CSS instead of JavaScript - **Modern Web Features**: Container queries, cascade layers, wide-gamut colors, and more ### Tailwind Variants Nuxt UI takes advantage of [Tailwind Variants](https://www.tailwind-variants.org/){rel="nofollow"} to provide a powerful theming system: - **Dynamic Styling**: Flexible component variants with a powerful API - **Type Safety**: Full TypeScript support with auto-completion - **Conflict Resolution**: Efficient merging of conflicting styles ## Key features ### Ecosystem integration Nuxt UI integrates with the Nuxt ecosystem to provide a seamless development experience: - [**Icons**](https://ui.nuxt.com/docs/getting-started/integrations/icons): Access 200,000+ icons from Iconify - [**Fonts**](https://ui.nuxt.com/docs/getting-started/integrations/fonts): Plug-and-play web font optimization and configuration - [**Color Mode**](https://ui.nuxt.com/docs/getting-started/integrations/color-mode): Dark and Light mode with auto detection - [**i18n**](https://ui.nuxt.com/docs/getting-started/integrations/i18n): Internationalize your components with 50+ languages - [**Content**](https://ui.nuxt.com/docs/getting-started/integrations/content): Beautiful typography out of the box ### Vue Compatibility Nuxt UI works with any Vue project. Simply add the Vite and Vue plugins to your configuration: - **Auto-imports**: Components and composables are automatically imported and available globally - **Theming System**: Full theming support with customizable colors, sizes, variants, and more - **Developer Experience**: Complete TypeScript support with IntelliSense and auto-completion ::tip --- ariaLabel: Vue installation guide to: https://ui.nuxt.com/docs/getting-started/installation/vue --- Learn how to install and configure Nuxt UI in a Vue project in the **Vue installation guide**. :: ### TypeScript Support Nuxt UI provides comprehensive TypeScript integration for a superior developer experience: - **Auto-completion**: For all component props, slots, and events - **Generic Components**: Using [Vue Generics](https://vuejs.org/api/sfc-script-setup.html#generics){rel="nofollow"} - **Type-safe Theming**: In `app.config.ts` - **IntelliSense**: Throughout your entire codebase ## FAQ ::accordion :::accordion-item{label="Is Nuxt UI free to use?"} Yes! Nuxt UI is completely free and open source under the MIT license. All 100+ components are available to everyone. ::: :::accordion-item{label="Can I use Nuxt UI with Vue without Nuxt?"} Yes! While optimized for Nuxt, Nuxt UI works perfectly with standalone Vue projects via our Vite plugin. You can follow the [installation guide](https://ui.nuxt.com/docs/getting-started/installation/vue) to get started. ::: :::accordion-item --- label: Will Nuxt UI work with other CSS frameworks like UnoCSS? --- No. Nuxt UI is designed exclusively for Tailwind CSS. UnoCSS support would require significant architecture changes due to different class naming conventions. ::: :::accordion-item{label="How does Nuxt UI handle accessibility?"} Through [Reka UI](https://reka-ui.com/docs/overview/accessibility){rel="nofollow"} integration, Nuxt UI provides automatic ARIA attributes, keyboard navigation, focus management, and screen reader support. While offering a strong foundation, testing in your specific use case remains important. ::: :::accordion-item{label="How is Nuxt UI tested?"} Nuxt UI ensures reliability with 1000+ Vitest tests covering core functionality and accessibility. ::: :::accordion-item{label="Is Nuxt UI production-ready?"} Yes! Nuxt UI is used in production by thousands of applications with extensive tests, regular updates, and active maintenance. ::: :: # Installation ::callout --- class: hidden icon: i-logos-vue to: https://ui.nuxt.com/docs/getting-started/installation/vue --- Looking for the **Vue** version? :: ## Setup ### Add to a Nuxt project ::steps{level="4"} #### Install the Nuxt UI package :::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxt/ui ``` ```bash [yarn] yarn add @nuxt/ui ``` ```bash [npm] npm install @nuxt/ui ``` ```bash [bun] bun add @nuxt/ui ``` ::: :::warning If you're using **pnpm**, ensure that you either set [`shamefully-hoist=true`](https://pnpm.io/npmrc#shamefully-hoist){rel="nofollow"} in your `.npmrc` file or install `tailwindcss` in your project's root directory. ::: #### Add the Nuxt UI module in your `nuxt.config.ts`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'] }) ``` #### Import Tailwind CSS and Nuxt UI in your CSS :::code-group ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; ``` ```ts [nuxt.config.ts] {3} export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'] }) ``` ::: :::callout{icon="i-simple-icons-visualstudiocode"} It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss){rel="nofollow"} extension for VSCode and add the following settings: ```json [.vscode/settings.json] { "files.associations": { "*.css": "tailwindcss" }, "editor.quickSuggestions": { "strings": "on" }, "tailwindCSS.classAttributes": ["class", "ui"], "tailwindCSS.experimental.classRegex": [ ["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"] ] } ``` ::: #### Wrap your app with App component ```vue [app.vue] ``` :::note{to="https://ui.nuxt.com/docs/components/app"} The `App` component provides global configurations and is required for **Toast**, **Tooltip** components to work as well as **Programmatic Overlays**. ::: :: ### Use a Nuxt template To quickly get started with Nuxt UI, you can use the [starter template](https://github.com/nuxt-ui-templates/starter){rel="nofollow"} by running: ```bash [Terminal] npm create nuxt@latest -- -t ui ``` You can also get started with one of our [official templates](https://ui.nuxt.com/templates): ::card-group :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Starter to: https://github.com/nuxt-ui-templates/starter --- A minimal template to get started with Nuxt UI. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Landing to: https://github.com/nuxt-ui-templates/landing --- A modern landing page template powered by Nuxt Content. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Docs to: https://github.com/nuxt-ui-templates/docs --- A documentation template powered by Nuxt Content. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: SaaS to: https://github.com/nuxt-ui-templates/saas --- A SaaS template with landing, pricing, docs and blog powered by Nuxt Content. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Dashboard to: https://github.com/nuxt-ui-templates/dashboard variant: subtle --- A dashboard template with multi-column layout for building sophisticated admin interfaces. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Chat to: https://github.com/nuxt-ui-templates/chat --- An AI chatbot template to build your own chatbot powered by Nuxt MDC and Vercel AI SDK. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Portfolio to: https://github.com/nuxt-ui-templates/portfolio --- A sleek portfolio template to showcase your work, skills and blog powered by Nuxt Content. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Changelog to: https://github.com/nuxt-ui-templates/changelog --- A changelog template to display your repository releases notes from GitHub powered by Nuxt MDC. ::: :: You can use the `Use this template` button on GitHub to create a new repository or use the CLI: ::code-group ```bash [Starter] npm create nuxt@latest -- -t ui ``` ```bash [Landing] npm create nuxt@latest -- -t ui/landing ``` ```bash [Docs] npm create nuxt@latest -- -t ui/docs ``` ```bash [SaaS] npm create nuxt@latest -- -t ui/saas ``` ```bash [Dashboard] npm create nuxt@latest -- -t ui/dashboard ``` ```bash [Chat] npm create nuxt@latest -- -t ui/chat ``` ```bash [Portfolio] npm create nuxt@latest -- -t ui/portfolio ``` ```bash [Changelog] npm create nuxt@latest -- -t ui/changelog ``` :: ## Options You can customize Nuxt UI by providing options in your `nuxt.config.ts`. ### `prefix` Use the `prefix` option to change the prefix of the components. - Default: `U`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { prefix: 'Nuxt' } }) ``` ### `fonts` Use the `fonts` option to enable or disable the [`@nuxt/fonts`](https://github.com/nuxt/fonts){rel="nofollow"} module. - Default: `true`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { fonts: false } }) ``` ### `colorMode` Use the `colorMode` option to enable or disable the [`@nuxt/color-mode`](https://github.com/nuxt-modules/color-mode){rel="nofollow"} module. - Default: `true`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { colorMode: false } }) ``` ### `theme.colors` Use the `theme.colors` option to define the dynamic color aliases used to generate components theme. - Default: `['primary', 'secondary', 'success', 'info', 'warning', 'error']`{.inline,language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { theme: { colors: ['primary', 'error'] } } }) ``` ::tip{to="https://ui.nuxt.com/docs/getting-started/theme/design-system#colors"} Learn more about color customization and theming in the Theme section. :: ### `theme.transitions` Use the `theme.transitions` option to enable or disable transitions on components. - Default: `true`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { theme: { transitions: false } } }) ``` ::note This option adds the `transition-colors` class on components with hover or active states. :: ### `theme.defaultVariants` Use the `theme.defaultVariants` option to override the default `color` and `size` variants for components. - Default: `{ color: 'primary', size: 'md' }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { theme: { defaultVariants: { color: 'neutral', size: 'sm' } } } }) ``` ### `mdc` Use the `mdc` option to force the import of Nuxt UI `` components even if `@nuxtjs/mdc` or `@nuxt/content` is not installed. - Default: `false`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { mdc: true } }) ``` ### `content` Use the `content` option to force the import of Nuxt UI `` and `` components even if `@nuxt/content` is not installed (`@nuxtjs/mdc` is installed by `@nuxt/content`). - Default: `false`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { content: true } }) ``` ### `experimental.componentDetection` :badge{label="4.1+"} Use the `experimental.componentDetection` option to enable automatic component detection for tree-shaking. This feature scans your source code to detect which components are actually used and only generates the necessary CSS for those components (including their dependencies). - Default: `false`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - Type: `boolean | string[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} **Enable automatic detection:** ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { experimental: { componentDetection: true } } }) ``` **Include additional components for dynamic usage:** ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { experimental: { componentDetection: ['Modal', 'Dropdown', 'Popover'] } } }) ``` ::note When providing an array of component names, automatic detection is enabled and these components (along with their dependencies) are guaranteed to be included. This is useful for dynamic components like `` that can't be statically analyzed. :: ## Continuous releases Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new){rel="nofollow"} for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases. Automatic preview releases are created for all commits and PRs to the `v4` branch. Use them by replacing your package version with the specific commit hash or PR number. ```diff [package.json] { "dependencies": { - "@nuxt/ui": "^4.0.0", + "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@4c96909", } } ``` ::note **pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes. :: # Installation ::callout --- class: hidden icon: i-logos-nuxt-icon to: https://ui.nuxt.com/docs/getting-started/installation/nuxt --- Looking for the **Nuxt** version? :: ## Setup ### Add to a Vue project ::steps{level="4"} #### Install the Nuxt UI package :::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxt/ui ``` ```bash [yarn] yarn add @nuxt/ui ``` ```bash [npm] npm install @nuxt/ui ``` ```bash [bun] bun add @nuxt/ui ``` ::: :::warning If you're using **pnpm**, ensure that you either set [`shamefully-hoist=true`](https://pnpm.io/npmrc#shamefully-hoist){rel="nofollow"} in your `.npmrc` file or install `tailwindcss`, `vue-router` and `@unhead/vue` in your project's root directory. ::: #### Add the Nuxt UI Vite plugin in your `vite.config.ts`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} :::code-group{sync="vite"} ```ts [vite.config.ts (Vite)] {3,8} import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import ui from '@nuxt/ui/vite' export default defineConfig({ plugins: [ vue(), ui() ] }) ``` ```ts [vite.config.ts (Laravel Inertia)] {3,20-22} import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import ui from '@nuxt/ui/vite' import laravel from 'laravel-vite-plugin' export default defineConfig({ plugins: [ laravel({ input: ['resources/js/app.ts'], refresh: true }), vue({ template: { transformAssetUrls: { base: null, includeAbsolute: false } } }), ui({ inertia: true }) ] }) ``` ```ts [vite.config.ts (AdonisJS Inertia)] {3,15-17} import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import ui from '@nuxt/ui/vite' import adonisjs from '@adonisjs/vite/client' import inertia from '@adonisjs/inertia/client' export default defineConfig({ plugins: [ adonisjs({ entrypoints: ['inertia/app/app.ts'], reload: ['resources/views/**/*.edge'] }), inertia(), vue(), ui({ inertia: true }) ] }) ``` ::: :::tip Nuxt UI registers `unplugin-auto-import` and `unplugin-vue-components`, which will generate `auto-imports.d.ts` and `components.d.ts` type declaration files. You will likely want to gitignore these, and add them to your `tsconfig`. ```json [tsconfig.app.json] { "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "auto-imports.d.ts", "components.d.ts"] } ``` ```bash [.gitignore] # Auto-generated type declarations auto-imports.d.ts components.d.ts ``` ::: :::tip Internally, Nuxt UI relies on custom alias to resolve the theme types. If you're using TypeScript, you should add an alias to your `tsconfig` to enable auto-completion in your `vite.config.ts`. ```json [tsconfig.node.json] { "compilerOptions": { "paths": { "#build/ui": [ "./node_modules/.nuxt-ui/ui" ] } } } ``` ::: #### Use the Nuxt UI Vue plugin :::code-group{sync="vite"} ```ts [src/main.ts (Vite)] {3,14} 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') ``` ```ts [resources/js/app.ts (Laravel Inertia)] {3,19} import type { DefineComponent } from 'vue' import { createInertiaApp } from '@inertiajs/vue3' import ui from '@nuxt/ui/vue-plugin' import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers' import { createApp, h } from 'vue' const appName = import.meta.env.VITE_APP_NAME || 'Laravel x Nuxt UI' createInertiaApp({ title: title => (title ? `${title} - ${appName}` : appName), resolve: name => resolvePageComponent( `./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue') ), setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(ui) .mount(el) } }) ``` ```ts [inertia/app/app.ts (AdonisJS Inertia)] {3,19} import type { DefineComponent } from 'vue' import { createInertiaApp } from '@inertiajs/vue3' import ui from '@nuxt/ui/vue-plugin' import { resolvePageComponent } from '@adonisjs/inertia/helpers' import { createApp, h } from 'vue' const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS x Nuxt UI' createInertiaApp({ title: title => (title ? `${title} - ${appName}` : appName), resolve: name => resolvePageComponent( `../pages/${name}.vue`, import.meta.glob('../pages/**/*.vue') ), setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(ui) .mount(el) } }) ``` ::: #### Import Tailwind CSS and Nuxt UI in your CSS :::code-group{sync="vite"} ```css [assets/main.css (Vite)] @import "tailwindcss"; @import "@nuxt/ui"; ``` ```css [resources/css/app.css (Laravel Inertia)] @import "tailwindcss"; @import "@nuxt/ui"; ``` ```css [inertia/css/app.css (AdonisJS Inertia)] @import "tailwindcss"; @import "@nuxt/ui"; ``` ::: :::tip Import the CSS file in your entrypoint. ::::code-group{sync="vite"} ```ts [src/main.ts] {1} 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') ``` ```ts [resources/js/app.ts (Laravel Inertia)] {1} import '../css/app.css' import type { DefineComponent } from 'vue' import { createInertiaApp } from '@inertiajs/vue3' import ui from '@nuxt/ui/vue-plugin' import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers' import { createApp, h } from 'vue' const appName = import.meta.env.VITE_APP_NAME || 'Laravel x Nuxt UI' createInertiaApp({ title: title => (title ? `${title} - ${appName}` : appName), resolve: name => resolvePageComponent( `./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue') ), setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(ui) .mount(el) } }) ``` ```ts [inertia/app/app.ts (AdonisJS Inertia)] {1} import '../css/app.css' import type { DefineComponent } from 'vue' import { createInertiaApp } from '@inertiajs/vue3' import ui from '@nuxt/ui/vue-plugin' import { resolvePageComponent } from '@adonisjs/inertia/helpers' import { createApp, h } from 'vue' const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS x Nuxt UI' createInertiaApp({ title: title => (title ? `${title} - ${appName}` : appName), resolve: name => resolvePageComponent( `../pages/${name}.vue`, import.meta.glob('../pages/**/*.vue') ), setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(ui) .mount(el) } }) ``` :::: ::: :::callout{icon="i-simple-icons-visualstudiocode"} It's recommended to install the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss){rel="nofollow"} extension for VSCode and add the following settings: ```json [.vscode/settings.json] { "files.associations": { "*.css": "tailwindcss" }, "editor.quickSuggestions": { "strings": "on" }, "tailwindCSS.classAttributes": ["class", "ui"], "tailwindCSS.experimental.classRegex": [ ["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"] ] } ``` ::: #### Wrap your app with App component :::code-group{sync="vite"} ```vue [src/App.vue (Vite)] ``` ```vue [resources/js/pages/index.vue (Laravel Inertia)] ``` ```vue [inertia/pages/index.vue (AdonisJS Inertia)] ``` ::: :::note{to="https://ui.nuxt.com/docs/components/app"} The `App` component sets up global config and is required for **Toast**, **Tooltip** and **programmatic overlays**. ::: #### Add the `isolate` class to your root container :::code-group{sync="vite"} ```html [index.html (Vite)] {9} Nuxt UI
``` ```blade [resources/views/app.blade.php (Laravel Inertia)] {10} @inertiaHead @vite('resources/js/app.ts')
@inertia
``` ```edge [resources/views/inertia_layout.edge (AdonisJS Inertia)] {10} @inertiaHead() @vite(['inertia/app/app.ts', `inertia/pages/${page.component}.vue`]) @inertia({ class: 'isolate' }) ``` ::: :::note This ensures styles are scoped to your app and prevents issues with overlays and stacking contexts. ::: :: ### Use a Vue template To quickly get started with Nuxt UI, you can use the [starter template](https://github.com/nuxt-ui-templates/starter-vue){rel="nofollow"} by running: ```bash [Terminal] npm create nuxt@latest -- --no-modules -t ui-vue ``` You can also get started with one of our [official templates](https://ui.nuxt.com/templates): ::card-group :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Starter to: https://github.com/nuxt-ui-templates/starter-vue --- A minimal template to get started with Nuxt UI. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Dashboard to: https://github.com/nuxt-ui-templates/dashboard-vue variant: subtle --- A dashboard template with multi-column layout for building sophisticated admin interfaces. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Starter Adonis to: https://github.com/nuxt-ui-templates/starter-adonis --- A minimal Nuxt UI template for AdonisJS using Inertia.js. ::: :::card --- color: neutral icon: i-simple-icons-github target: _blank title: Starter Laravel to: https://github.com/nuxt-ui-templates/starter-laravel --- A minimal Nuxt UI template for Laravel using Inertia.js. ::: :: You can use the `Use this template` button on GitHub to create a new repository or use the CLI: ::code-group ```bash [Starter] npm create nuxt@latest -- --no-modules -t ui-vue ``` ```bash [Dashboard] npm create nuxt@latest -- --no-modules -t ui-vue/dashboard ``` :: ## Options You can customize Nuxt UI by providing options in your `vite.config.ts`. ### `prefix` Use the `prefix` option to change the prefix of the components. - Default: `U`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [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({ prefix: 'Nuxt' }) ] }) ``` ### `ui` Use the `ui` option to provide configuration for Nuxt UI. ```ts [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: { colors: { primary: 'green', neutral: 'slate' } } }) ] }) ``` ### `colorMode` Use the `colorMode` option to enable or disable the color mode integration from `@vueuse/core`. - Default: `true`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [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({ colorMode: false }) ] }) ``` ### `theme.colors` Use the `theme.colors` option to define the dynamic color aliases used to generate components theme. - Default: `['primary', 'secondary', 'success', 'info', 'warning', 'error']`{.inline,language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [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({ theme: { colors: ['primary', 'error'] } }) ] }) ``` ::tip{to="https://ui.nuxt.com/docs/getting-started/theme/design-system#colors"} Learn more about color customization and theming in the Theme section. :: ### `theme.transitions` Use the `theme.transitions` option to enable or disable transitions on components. - Default: `true`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [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({ theme: { transitions: false } }) ] }) ``` ::note This option adds the `transition-colors` class on components with hover or active states. :: ### `theme.defaultVariants` Use the `theme.defaultVariants` option to override the default `color` and `size` variants for components. - Default: `{ color: 'primary', size: 'md' }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [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({ theme: { defaultVariants: { color: 'neutral', size: 'sm' } } }) ] }) ``` ### `inertia` Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/){rel="nofollow"}. ```ts [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({ inertia: true }) ] }) ``` ::note When using this option, `vue-router` is not required as Inertia.js provides its own routing system. The components that would normally use `RouterLink` will automatically use Inertia's `InertiaLink` component instead. :: ## Continuous releases Nuxt UI uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new){rel="nofollow"} for continuous preview releases, providing developers with instant access to the latest features and bug fixes without waiting for official releases. Automatic preview releases are created for all commits and PRs to the `v4` branch. Use them by replacing your package version with the specific commit hash or PR number. ```diff [package.json] { "dependencies": { - "@nuxt/ui": "^4.0.0", + "@nuxt/ui": "https://pkg.pr.new/@nuxt/ui@4c96909", } } ``` ::note **pkg.pr.new** will automatically comment on PRs with the installation URL, making it easy to test changes. :: # Migration to v4 Nuxt UI v4 marks a major milestone: **Nuxt UI and Nuxt UI Pro are now unified into a single, fully open-source and free library**. You now have access to 100+ production-ready components, all available in the `@nuxt/ui` package. ::note Nuxt UI v4 requires **Nuxt 4** due to some dependencies. Make sure to upgrade to Nuxt 4 before migrating to Nuxt UI v4. :: This guide provides step-by-step instructions to migrate your application from v3 to v4. ## Migrate your project ### From Nuxt UI Pro 1. Replace `@nuxt/ui-pro` with `@nuxt/ui` in your `package.json`: ::code-group{sync="pm"} ```bash [pnpm] pnpm remove @nuxt/ui-pro pnpm add @nuxt/ui ``` ```bash [yarn] yarn remove @nuxt/ui-pro yarn add @nuxt/ui ``` ```bash [npm] npm uninstall @nuxt/ui-pro npm install @nuxt/ui ``` ```bash [bun] bun remove @nuxt/ui-pro bun add @nuxt/ui ``` :: ::framework-only #nuxt :::div 2. Replace `@nuxt/ui-pro` with `@nuxt/ui` in your `nuxt.config.ts`: ```diff [nuxt.config.ts] export default defineNuxtConfig({ modules: [ - '@nuxt/ui-pro', + '@nuxt/ui' ] }) ``` ::: #vue :::div 2. Replace `@nuxt/ui-pro` with `@nuxt/ui` in your `vite.config.ts`: ```diff [vite.config.ts] import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' - import uiPro from '@nuxt/ui-pro/vite' + import ui from '@nuxt/ui/vite' export default defineConfig({ plugins: [ vue(), - uiPro({ + ui({ ui: { colors: { primary: 'green', neutral: 'slate' } } }) ] }) ``` ::: :: ::framework-only #nuxt :::div 3. Use the `ui` key instead of `uiPro` in your `app.config.ts`: ```diff [app.config.ts] export default defineAppConfig({ ui: { colors: { primary: 'green', neutral: 'slate' }, + pageCard: { + slots: { + root: 'rounded-xl', + } + } }, - uiPro: { - pageCard: { - slots: { - root: 'rounded-xl', - } - } - } }) ``` ::: #vue :::div 3. Use the `ui` key instead of `uiPro` in your `vite.config.ts`: ```diff [vite.config.ts] export default defineConfig({ plugins: [ vue(), ui({ ui: { colors: { primary: 'green', neutral: 'slate' }, + pageCard: { + slots: { + root: 'rounded-xl', + } + } }, - uiPro: { - pageCard: { - slots: { - root: 'rounded-xl', - } - } - } }) ] }) ``` ::: :: 4. Replace `@nuxt/ui-pro` with `@nuxt/ui` in your CSS: ::framework-only #nuxt :::div ```diff [app/assets/css/main.css] @import "tailwindcss"; - @import "@nuxt/ui-pro"; + @import "@nuxt/ui"; ``` ::::warning If you are upgrading to Nuxt 4 at the same time as Nuxt UI v4, make sure to update the `@source` directive to match the new directory structure. ```diff [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; - @source "../../content/**/*"; + @source "../../../content/**/*"; ``` :::: ::: #vue :::div ```diff [src/assets/css/main.css] @import "tailwindcss"; - @import "@nuxt/ui-pro"; + @import "@nuxt/ui"; ``` ::: :: 5. Replace `@nuxt/ui-pro` with `@nuxt/ui` in your imports: ```diff - import type { BannerProps } from '@nuxt/ui-pro' + import type { BannerProps } from '@nuxt/ui' ``` ### From Nuxt UI 1. When upgrading from Nuxt UI v3, you simply need to update to v4: ::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxt/ui ``` ```bash [yarn] yarn add @nuxt/ui ``` ```bash [npm] npm install @nuxt/ui ``` ```bash [bun] bun add @nuxt/ui ``` :: ## Changes from v3 After upgrading to Nuxt UI v4, please note the following important changes: ### Renamed ButtonGroup The `ButtonGroup` component has been renamed to [`FieldGroup`](https://ui.nuxt.com/docs/components/field-group): ```diff ``` ### Renamed PageMarquee The `PageMarquee` component has been renamed to [`Marquee`](https://ui.nuxt.com/docs/components/marquee): ```diff ``` ### Removed PageAccordion The `PageAccordion` component has been removed in favor of [`Accordion`](https://ui.nuxt.com/docs/components/accordion): ```diff ``` ::note The `PageAccordion` component was a wrapper that set `unmount-on-hide` to `false` and customized the `ui` prop. :: ### Renamed model modifiers The `modelModifiers` shape used by [`Input`](https://ui.nuxt.com/docs/components/input), [`InputNumber`](https://ui.nuxt.com/docs/components/input-number) and [`Textarea`](https://ui.nuxt.com/docs/components/textarea) has changed in v4: 1. The `nullify` modifier was renamed to `nullable` (it converts empty/blank values to `null`). 2. A new `optional` modifier was added (it converts empty/blank values to `undefined`). ```diff - + ``` ```diff - + ``` Use `nullable` when you want empty values as `null`, and `optional` when you prefer `undefined` for absent values. ### Changes to Form component The `Form` component has been improved in v4 with better state management and nested form handling. Here are the key changes you need to be aware of: 1. Schema **transformations will only** be applied to the **`@submit` data** and will no longer mutate the form's state. This provides better predictability and prevents unexpected state mutations. 2. **Nested forms must be enabled explicitly** using the `nested` prop. This makes the component behavior more explicit and prevents accidental nested form creation. 3. **Nested forms should now provide a `name`** prop (similar to `UFormField`) and will automatically inherit their state from their parent form. ```diff ``` ### Removed deprecated utilities Some **Nuxt Content utilities** that were previously available in Nuxt UI Pro have been **removed** in v4: - `findPageBreadcrumb` - `findPageHeadline` These are now fully provided by Nuxt Content. Make sure to update your imports and usage accordingly. ```diff - import { findPageHeadline } from '@nuxt/ui-pro/utils/content' + import { findPageHeadline } from '@nuxt/content/utils' - import { findPageBreadcrumb } from '@nuxt/ui-pro/utils/content' + import { findPageBreadcrumb } from '@nuxt/content/utils' ``` ### AI SDK v5 migration (optional) This section only applies if you're using the AI SDK and chat components (`ChatMessage`, `ChatMessages`, `ChatPrompt`, `ChatPromptSubmit`, `ChatPalette`). If you're not using AI features, you can skip this section. 1. Update `@ai-sdk/vue` and `ai` dependencies in your `package.json`: ```diff { "dependencies": { - "@ai-sdk/vue": "^1.2.x", + "@ai-sdk/vue": "^2.0.x", - "ai": "^4.3.x" + "ai": "^5.0.x" } } ``` 2. `useChat` composable has been replaced with the new `Chat` class: ```diff ``` 3. Messages now use `parts` instead of `content`: ```diff // When manually creating messages - setMessages([{ + messages.push({ id: '1', role: 'user', - content: 'Hello world' + parts: [{ type: 'text', text: 'Hello world' }] - }]) + }) // In templates ``` 4. Some methods have been renamed: ```diff // Regenerate the last message - reload() + chat.regenerate() // Access chat state - :messages="messages" - :status="status" + :messages="chat.messages" + :status="chat.status" ``` 5. New `getTextFromMessage` utility to extract text from AI SDK v5 message parts: ```vue ``` ::note --- target: _blank to: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0 --- For more details on AI SDK v5 changes, review the **official AI SDK v5 migration guide**. :: ::tip{target="_blank" to="https://github.com/nuxt/ui/pull/4698"} View all changes from AI SDK v4 to v5 **in the upgrade PR** for a detailed migration reference. :: # Migration to v3 Nuxt UI v3 is a new major version rebuilt from the ground up, introducing a modern architecture with significant performance improvements and an enhanced developer experience. This major release includes several breaking changes alongside powerful new features and capabilities: - **Tailwind CSS v4**: Migration from JavaScript to CSS-based configuration - **Reka UI**: Replacing Headless UI as the underlying component library - **Tailwind Variants**: New styling API for component variants This guide provides step by step instructions to migrate your application from v2 to v3. ## Migrate your project ::steps ### Update Tailwind CSS Tailwind CSS v4 introduces significant changes to its configuration approach. The official Tailwind upgrade tool will help automate most of the migration process. :::note --- target: _blank to: https://tailwindcss.com/docs/upgrade-guide#changes-from-v3 --- For a detailed walkthrough of all changes, refer to the official **Tailwind CSS v4 upgrade guide**. ::: 1. Create a `main.css` file and import it in your `nuxt.config.ts` file: :::code-group ```css [app/assets/css/main.css] @import "tailwindcss"; ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ css: ['~/assets/css/main.css'] }) ``` ::: 2. Run the Tailwind CSS upgrade tool: ```bash npx @tailwindcss/upgrade ``` ### Update Nuxt UI 3. Install the latest version of the package: :::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxt/ui ``` ```bash [yarn] yarn add @nuxt/ui ``` ```bash [npm] npm install @nuxt/ui ``` ```bash [bun] bun add @nuxt/ui ``` ::: 4. Import it in your CSS: ```css [app/assets/css/main.css] {2} @import "tailwindcss"; @import "@nuxt/ui"; ``` 5. Wrap your app with the [App](https://ui.nuxt.com/docs/components/app) component: ```vue [app.vue] {2,4} ``` :: ## Changes from v2 Now that you have updated your project, you can start migrating your code. Here's a comprehensive list of all the breaking changes in Nuxt UI v3. ### Updated design system In Nuxt UI v2, we had a mix between a design system with `primary`, `gray`, `error` aliases and all the colors from Tailwind CSS. We've replaced it with a proper [design system](https://ui.nuxt.com/docs/getting-started/theme/design-system) with 7 color aliases: | Color | Default | Description | | ------------------------------ | -------- | ----------------------------------------------------------- | | `primary`{color="primary"} | `green` | Main brand color, used as the default color for components. | | `secondary`{color="secondary"} | `blue` | Secondary color to complement the primary color. | | `success`{color="success"} | `green` | Used for success states. | | `info`{color="info"} | `blue` | Used for informational states. | | `warning`{color="warning"} | `yellow` | Used for warning states. | | `error`{color="error"} | `red` | Used for form error validation states. | | `neutral` | `slate` | Neutral color for backgrounds, text, etc. | This change introduces several breaking changes that you need to be aware of: - The `gray` color has been renamed to `neutral` ```diff ``` ::note You can also use the new [design tokens](https://ui.nuxt.com/docs/getting-started/theme/css-variables) to handle light and dark mode: ```diff ``` :: - The `gray`, `black` and `white` in the `color` props have been removed in favor of `neutral`: ```diff - + - + - + ``` - You can no longer use Tailwind CSS colors in the `color` props, use the new aliases instead: ```diff - + ``` ::note{to="https://ui.nuxt.com/docs/getting-started/theme/design-system#colors"} Learn how to extend the design system to add new color aliases. :: - The color configuration in `app.config.ts` has been moved into a `colors` object: ```diff export default defineAppConfig({ ui: { - primary: 'green', - gray: 'cool' + colors: { + primary: 'green', + neutral: 'slate' + } } }) ``` ### Updated theming system Nuxt UI components are now styled using the [Tailwind Variants API](https://ui.nuxt.com/docs/getting-started/theme/components), which makes all the overrides you made using the `app.config.ts` and the `ui` prop obsolete. - Update your [`app.config.ts`](https://ui.nuxt.com/docs/getting-started/theme/components#global-config) to override components with their new theme: ```diff export default defineAppConfig({ ui: { button: { - font: 'font-bold', - default: { - size: 'md', - color: 'primary' - } + slots: { + base: 'font-medium' + }, + defaultVariants: { + size: 'md', + color: 'primary' + } } } }) ``` - Update your [`ui` props](https://ui.nuxt.com/docs/getting-started/theme/components#ui-prop) to override each component's slots using their new theme: ```diff ``` ::tip{to="https://ui.nuxt.com/docs/components/button#theme"} We can't detail all the changes here but you can check each component's theme in the **Theme** section. :: ### Renamed components We've renamed some Nuxt UI components to align with the Reka UI naming convention: | v2 | v3 | | ---------------------- | ------------------------------------------------------------------------------------------------------- | | `Divider` | [`Separator`](https://ui.nuxt.com/docs/components/separator) | | `Dropdown` | [`DropdownMenu`](https://ui.nuxt.com/docs/components/dropdown-menu) | | `FormGroup` | [`FormField`](https://ui.nuxt.com/docs/components/form-field) | | `Range` | [`Slider`](https://ui.nuxt.com/docs/components/slider) | | `Toggle` | [`Switch`](https://ui.nuxt.com/docs/components/switch) | | `Notification` | [`Toast`](https://ui.nuxt.com/docs/components/toast) | | `VerticalNavigation` | [`NavigationMenu`](https://ui.nuxt.com/docs/components/navigation-menu) with `orientation="vertical"` | | `HorizontalNavigation` | [`NavigationMenu`](https://ui.nuxt.com/docs/components/navigation-menu) with `orientation="horizontal"` | Here are the Nuxt UI Pro components that have been renamed or removed: | v1 | v3 | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `BlogList` | [`BlogPosts`](https://ui.nuxt.com/docs/components/blog-posts) | | `ColorModeToggle` | [`ColorModeSwitch`](https://ui.nuxt.com/docs/components/color-mode-switch) | | `DashboardCard` | Removed (use [`PageCard`](https://ui.nuxt.com/docs/components/page-card) instead) | | `DashboardLayout` | [`DashboardGroup`](https://ui.nuxt.com/docs/components/dashboard-group) | | `DashboardModal` | Removed (use [`Modal`](https://ui.nuxt.com/docs/components/modal) instead) | | `DashboardNavbarToggle` | [`DashboardSidebarToggle`](https://ui.nuxt.com/docs/components/dashboard-sidebar-toggle) | | `DashboardPage` | Removed | | `DashboardPanelContent` | Removed (use `#body` slot instead) | | `DashboardPanelHandle` | [`DashboardResizeHandle`](https://ui.nuxt.com/docs/components/dashboard-resize-handle) | | `DashboardSection` | Removed (use [`PageCard`](https://ui.nuxt.com/docs/components/page-card) instead) | | `DashboardSidebarLinks` | Removed (use [`NavigationMenu`](https://ui.nuxt.com/docs/components/navigation-menu) instead) | | `DashboardSlideover` | Removed (use [`Slideover`](https://ui.nuxt.com/docs/components/slideover) instead) | | `FooterLinks` | Removed (use [`NavigationMenu`](https://ui.nuxt.com/docs/components/navigation-menu) instead) | | `HeaderLinks` | Removed (use [`NavigationMenu`](https://ui.nuxt.com/docs/components/navigation-menu) instead) | | `LandingCard` | Removed (use [`PageCard`](https://ui.nuxt.com/docs/components/page-card) instead) | | `LandingCTA` | [`PageCTA`](https://ui.nuxt.com/docs/components/page-cta) | | `LandingFAQ` | Removed (use [`Accordion`](https://ui.nuxt.com/docs/components/accordion) instead) | | `LandingGrid` | Removed (use [`PageGrid`](https://ui.nuxt.com/docs/components/page-grid) instead) | | `LandingHero` | Removed (use [`PageHero`](https://ui.nuxt.com/docs/components/page-hero) instead) | | `LandingLogos` | [`PageLogos`](https://ui.nuxt.com/docs/components/page-logos) | | `LandingSection` | [`PageSection`](https://ui.nuxt.com/docs/components/page-section) | | `LandingTestimonial` | Removed (use [`PageCard`](https://ui.nuxt.com/docs/components/page-card#as-a-testimonial) instead) | | `NavigationAccordion` | [`ContentNavigation`](https://ui.nuxt.com/docs/components/content-navigation) | | `NavigationLinks` | [`ContentNavigation`](https://ui.nuxt.com/docs/components/content-navigation) | | `NavigationTree` | [`ContentNavigation`](https://ui.nuxt.com/docs/components/content-navigation) | | `PageError` | [`Error`](https://ui.nuxt.com/docs/components/error) | | `PricingCard` | [`PricingPlan`](https://ui.nuxt.com/docs/components/pricing-plan) | | `PricingGrid` | [`PricingPlans`](https://ui.nuxt.com/docs/components/pricing-plans) | | `PricingSwitch` | Removed (use [`Switch`](https://ui.nuxt.com/docs/components/switch) or [`Tabs`](https://ui.nuxt.com/docs/components/tabs) instead) | ### Changed components In addition to the renamed components, there are lots of changes to the components API. Let's detail the most important ones: - The `links` and `options` props have been renamed to `items` for consistency: ```diff ``` ::note This change affects the following components: `Breadcrumb`, `HorizontalNavigation`, `InputMenu`, `RadioGroup`, `Select`, `SelectMenu`, `VerticalNavigation`. :: - The `click` field in different components has been removed in favor of the native Vue `onClick` event: ```diff ``` ::note This change affects the `Toast` component as well as all component that have `items` links like `NavigationMenu`, `DropdownMenu`, `CommandPalette`, etc. :: - The global `Modals`, `Slideovers` and `Notifications` components have been removed in favor the [App](https://ui.nuxt.com/docs/components/app) component: ```diff [app.vue] ``` - The `v-model:open` directive and `default-open` prop are now used to control visibility: ```diff ``` ::note This change affects the following components: `ContextMenu`, `Modal` and `Slideover` and enables controlling visibility for `InputMenu`, `Select`, `SelectMenu` and `Tooltip`. :: - The default slot is now used for the trigger and the content goes inside the `#content` slot (you don't need to use a `v-model:open` directive with this method): ```diff ``` ::note This change affects the following components: `Modal`, `Popover`, `Slideover`, `Tooltip`. :: - A `#header`, `#body` and `#footer` slots have been added inside the `#content` slot like the `Card` component: ```diff ``` ::note This change affects the following components: `Modal`, `Slideover`. :: ### Changed composables - The `useToast()` composable `timeout` prop has been renamed to `duration`: ```diff ``` - The `useModal` and `useSlideover` composables have been removed in favor of a more generic `useOverlay` composable: Some important differences: - The `useOverlay` composable is now used to create overlay instances - Overlays that are opened, can be awaited for their result - Overlays can no longer be close using `modal.close()` or `slideover.close()`, rather, they close automatically: either when a `close` event is fired explicitly from the opened component OR when the overlay closes itself (clicking on backdrop, pressing the ESC key, etc) - To capture the return value in the parent component you must explictly emit a `close` event with the desired value ```diff ``` Props are now passed through a props attribute: ```diff ``` Closing a modal is now done through the `close` event. The `modal.open` method now returns an instance that can be used to await for the result of the modal whenever the modal is closed: ```diff ``` ### Changed form validation - The error object property for targeting form fields has been renamed from `path` to `name`: ```diff ``` --- ::warning This page is a work in progress, we'll improve it regularly. :: # Contribution Nuxt UI thrives thanks to its incredible community ❤️. We welcome all contributions through bug reports, pull requests, and feedback to help make this library even better. ::caution Before reporting a bug or requesting a feature, make sure that you have read through our [documentation](https://ui.nuxt.com/){rel="nofollow"} and existing [issues](https://github.com/nuxt/ui/issues?q=is%3Aissue%20is%3Aopen%20sort%3Aupdated-desc){rel="nofollow"}. :: ## Project structure Here's an overview of the key directories and files in the Nuxt UI project structure: ### Documentation The documentation lives in the `docs` folder as a Nuxt app using `@nuxt/content` to generate pages from Markdown files. See the [Nuxt Content documentation](https://content.nuxt.com/docs/getting-started){rel="nofollow"} for details on how it works. Here's a breakdown of its structure: ```bash ├── app/ │ ├── assets/ │ ├── components/ │ │ └── content/ │ │ └── examples # Components used in documentation as examples │ ├── composables/ │ └── ... ├── content/ │ ├── 1.getting-started │ ├── 2.composables │ └── 3.components # Components documentation ``` ### Module The module code resides in the `src` folder. Here's a breakdown of its structure: ```bash ├── plugins/ ├── runtime/ │ ├── components/ # Where all the components are located │ │ ├── Accordion.vue │ │ ├── Alert.vue │ │ └── ... │ ├── composables/ │ ├── locale/ │ ├── plugins/ │ ├── types/ │ ├── utils/ │ └── vue/ │ ├── components/ │ └── plugins/ ├── theme/ # This where the theme for each component is located │ ├── accordion.ts # Theme for Accordion component │ ├── alert.ts │ └── ... └── module.ts ``` ## CLI To make development easier, we've created a CLI that you can use to generate components and locales. You can access it using the `nuxt-ui make` command. First, you need to link the CLI to your global environment: ```sh npm link ``` ### Components You can create new components using the following command: ```sh nuxt-ui make component [options] ``` Available options: - `--primitive` Create a primitive component - `--prose` Create a prose component - `--content` Create a content component - `--template` Only generate specific template (available templates: `playground`, `docs`, `test`, `theme`, `component`) Example: ```sh # Create a basic component nuxt-ui make component my-component # Create a prose component nuxt-ui make component heading --prose # Create a content component nuxt-ui make component block --content # Generate only documentation template nuxt-ui make component my-component --template=docs ``` ::note When creating a new component, the CLI will automatically generate all the necessary files like the component itself, theme, tests, and documentation. :: ### Locales You can create new locales using the following command: ```sh nuxt-ui make locale --code --name ``` ::note --- to: https://ui.nuxt.com/docs/getting-started/integrations/i18n/nuxt#supported-languages --- Learn more about **i18n** in the documentation. :: ## Submit a Pull Request (PR) Before you start, check if there's an existing issue describing the problem or feature request you're working on. If there is, please leave a comment on the issue to let us know you're working on it. If there isn't, open a new issue to discuss the problem or feature. ### Local development To begin local development, follow these steps: ::steps{level="4"} #### Clone the `nuxt/ui` repository to your local machine ```sh git clone -b v4 https://github.com/nuxt/ui.git ``` #### Enable [Corepack](https://github.com/nodejs/corepack){rel="nofollow"} ```sh corepack enable ``` #### Install dependencies ```sh pnpm install ``` #### Generate type stubs ```sh pnpm run dev:prepare ``` #### Start development - To work on the **documentation** located in the `docs` folder, run: ```sh pnpm run docs ``` - To test the Nuxt components using the **playground**, run: ```sh pnpm run dev ``` - To test the Vue components using the **playground**, run: ```sh pnpm run dev:vue ``` :: ::note{to="https://ui.nuxt.com/#cli"} If you're working on implementing a new component, check the **CLI** section to kickstart the process. :: ### IDE Setup We recommend using VSCode alongside the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint){rel="nofollow"}. You can enable auto-fix and formatting when saving your code. Here's how: ```json { "editor.codeActionsOnSave": { "source.fixAll": false, "source.fixAll.eslint": true } } ``` ::warning Since ESLint is already configured to format the code, there's no need for duplicating functionality with **Prettier**. If you have it installed in your editor, we recommend disabling it to avoid conflicts. :: ### Linting You can use the `lint` command to check for linting errors: ```sh pnpm run lint # check for linting errors pnpm run lint:fix # fix linting errors ``` ### Type checking We use TypeScript for type checking. You can use the `typecheck` command to check for type errors: ```sh pnpm run typecheck ``` ### Testing Before submitting a PR, ensure that you run the tests for both `nuxt` and `vue`: ```sh pnpm run test # for Nuxt pnpm run test:vue # for Vue ``` ::tip If you have to update the snapshots, press `u` after the tests have finished running. :: ### Commit conventions We use [Conventional Commits](https://www.conventionalcommits.org/){rel="nofollow"} for commit messages, which allows a changelog to be auto-generated based on the commits. Please read the [guide](https://www.conventionalcommits.org/en/v1.0.0/#summary){rel="nofollow"} through if you aren't familiar with it already. - Use `fix` and `feat` for code changes that affect functionality or logic - Use `docs` for documentation changes and `chore` for maintenance tasks ### Making a Pull Request - Follow along the [instructions](https://github.com/nuxt/ui/blob/v4/.github/PULL_REQUEST_TEMPLATE.md?plain=1){rel="nofollow"} provided when creating a PR - Ensure your PR's title adheres to the [Conventional Commits](https://www.conventionalcommits.org/){rel="nofollow"} since it will be used once the code is merged. - Multiple commits are fine; no need to rebase or force push. We'll use `Squash and Merge` when merging. - Ensure `lint`, `typecheck` and `tests` work before submitting the PR. Avoid making unrelated changes. We'll review it promptly. If assigned to a maintainer, they'll review it carefully. Ignore the red text; it's for tracking purposes. ## Thanks Thank you again for being interested in this project! You are awesome! ❤️ # Design System ## Tailwind CSS Tailwind CSS uses a CSS-first configuration, letting you define your design tokens with the [`@theme`](https://tailwindcss.com/docs/functions-and-directives#theme-directive){rel="nofollow"} directive directly in your CSS. This makes your theme portable, maintainable and easy to customize. ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @theme { /* Your custom design tokens go here */ } ``` ::note{target="_blank" to="https://tailwindcss.com/docs/theme"} Check the Tailwind CSS documentation for all available theme variable customization options. :: ### Fonts Use the `--font-*` theme variables to [customize the font family utilities](https://tailwindcss.com/docs/font-family#customizing-your-theme){rel="nofollow"} in your project. ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @theme { --font-sans: 'Public Sans', system-ui, sans-serif; --font-mono: 'JetBrains Mono', monospace; } ``` ::framework-only #nuxt :::note{to="https://ui.nuxt.com/docs/getting-started/integrations/fonts"} Fonts defined here are automatically loaded and optimized by the `@nuxt/fonts` module. ::: :: ### Colors Use the `--color-*` theme variables to [customize your colors](https://tailwindcss.com/docs/colors#customizing-your-colors){rel="nofollow"} or [override default colors](https://tailwindcss.com/docs/colors#overriding-default-colors){rel="nofollow"}. ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @theme static { /* Override default green color */ --color-green-50: #EFFDF5; --color-green-100: #D9FBE8; --color-green-200: #B3F5D1; --color-green-300: #75EDAE; --color-green-400: #00DC82; --color-green-500: #00C16A; --color-green-600: #00A155; --color-green-700: #007F45; --color-green-800: #016538; --color-green-900: #0A5331; --color-green-950: #052E16; /* Define new custom color */ --color-brand-50: #fef2f2; --color-brand-100: #fee2e2; --color-brand-200: #fecaca; --color-brand-300: #fca5a5; --color-brand-400: #f87171; --color-brand-500: #ef4444; --color-brand-600: #dc2626; --color-brand-700: #b91c1c; --color-brand-800: #991b1b; --color-brand-900: #7f1d1d; --color-brand-950: #450a0a; } ``` ::warning When adding custom colors, make sure to define all shades from `50` to `950` for each color. :: ### Breakpoints Use the `--breakpoint-*` theme variables to [customize your breakpoints](https://tailwindcss.com/docs/responsive-design#customizing-your-theme){rel="nofollow"}. ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @theme { --breakpoint-3xl: 1920px; --breakpoint-4xl: 2560px; --breakpoint-5xl: 3840px; } ``` ## Colors Nuxt UI's color system is designed around **semantic naming** rather than specific color values. This approach makes your UI more maintainable and allows for easy theme switching. ### Semantic colors Nuxt UI provides semantic color aliases that describe the **purpose** of the color. Each alias is defined based on a color from your `@theme` configuration, which can be any color you define in addition to the [default Tailwind CSS palette](https://tailwindcss.com/docs/colors){rel="nofollow"}. | Color | Default | Description | | ------------------------------ | -------- | ----------------------------------------------------------------- | | `primary`{color="primary"} | `green` | Main CTAs, active navigation, brand elements, important links | | `secondary`{color="secondary"} | `blue` | Secondary buttons, alternative actions, complementary UI elements | | `success`{color="success"} | `green` | Success messages, completed states, positive confirmations | | `info`{color="info"} | `blue` | Info alerts, tooltips, help text, neutral notifications | | `warning`{color="warning"} | `yellow` | Warning messages, pending states, attention-needed items | | `error`{color="error"} | `red` | Error messages, validation errors, destructive actions | | `neutral` | `slate` | Text, borders, backgrounds, disabled states | These semantic colors are available in the `color` prop of Nuxt UI components: ```vue ``` ::note Try the :prose-icon{.text-primary name="i-lucide-swatch-book"} theme picker in the header to instantly see how different color schemes affect the entire UI! :: ### Runtime configuration ::framework-only #nuxt :::div You can configure these colors at runtime in your [`app.config.ts`](https://nuxt.com/docs/guide/directory-structure/app-config#app-config-file){rel="nofollow"} file under the `ui.colors` key, allowing for dynamic theme customization without restarting the server: ```ts [app.config.ts] export default defineAppConfig({ ui: { colors: { primary: 'blue', secondary: 'purple', neutral: 'zinc' } } }) ``` ::: #vue :::div You can configure these colors at runtime in your `vite.config.ts` file under the `ui.colors` key, allowing for dynamic theme customization: ```ts [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: { colors: { primary: 'blue', secondary: 'purple', neutral: 'zinc' } } }) ] }) ``` ::: :: ::caution You can only use colors that exist in your theme. Either: - Use [Tailwind's default colors](https://tailwindcss.com/docs/colors){rel="nofollow"} (like `blue`, `green`, `zinc`) - Define custom colors first using the `@theme` directive (like `brand` in our example above) :: ### Extend colors You may want to define extra semantic colors beyond the defaults, such as adding a `tertiary` color: ::framework-only #nuxt :::div First, register the new color in your `nuxt.config.ts` under the `ui.theme.colors` key: ```ts [nuxt.config.ts] {7} export default defineNuxtConfig({ ui: { theme: { colors: [ 'primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error' ] } } }) ``` Then, assign it in your `app.config.ts` under the `ui.colors` key: ```ts [app.config.ts] {6} export default defineAppConfig({ ui: { colors: { primary: 'blue', secondary: 'purple', tertiary: 'indigo' } } }) ``` ::: #vue :::div Register and assign the new color in your `vite.config.ts` file: ```ts [vite.config.ts] {13,24} import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import ui from '@nuxt/ui/vite' export default defineConfig({ plugins: [ vue(), ui({ theme: { colors: [ 'primary', 'secondary', 'tertiary', 'info', 'success', 'warning', 'error' ] }, ui: { colors: { primary: 'blue', secondary: 'purple', tertiary: 'indigo' } } }) ] }) ``` ::: :: Finally, use this new color in components that support the `color` prop or [as a class](https://ui.nuxt.com/docs/getting-started/theme/css-variables): ```vue Special Action ``` # CSS Variables ## Colors Nuxt UI provides Tailwind CSS utility classes for each [semantic color](https://ui.nuxt.com/docs/getting-started/theme/design-system#semantic-colors) you define, allowing you to use class names like `text-error` or `bg-success`: ::code-preview [Primary]{.text-primary.text-sm.px-4.inline-block class="py-1.5"}[Secondary]{.text-secondary.text-sm.px-4.inline-block class="py-1.5"}[Success]{.text-success.text-sm.px-4.inline-block class="py-1.5"}[Info]{.text-info.text-sm.px-4.inline-block class="py-1.5"}[Warning]{.text-warning.text-sm.px-4.inline-block class="py-1.5"}[Error]{.text-error.text-sm.px-4.inline-block class="py-1.5"} #code ```vue ``` :: Each utility class uses a CSS variable to set its color for light and dark modes: ::code-group ```css [Light] :root { --ui-primary: var(--ui-color-primary-500); --ui-secondary: var(--ui-color-secondary-500); --ui-success: var(--ui-color-success-500); --ui-info: var(--ui-color-info-500); --ui-warning: var(--ui-color-warning-500); --ui-error: var(--ui-color-error-500); } ``` ```css [Dark] .dark { --ui-primary: var(--ui-color-primary-400); --ui-secondary: var(--ui-color-secondary-400); --ui-success: var(--ui-color-success-400); --ui-info: var(--ui-color-info-400); --ui-warning: var(--ui-color-warning-400); --ui-error: var(--ui-color-error-400); } ``` :: ::tip You can adjust which shade each utility class uses for light and dark mode in your `main.css` file: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-primary: var(--ui-color-primary-700); } .dark { --ui-primary: var(--ui-color-primary-200); } ``` :: ::warning You can't use `primary: 'black'` in your [**config**](https://ui.nuxt.com/docs/getting-started/theme/design-system#runtime-configuration) because `black` doesn't have multiple shades. To use solid black or white as your primary color, set it directly in your `main.css` file: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-primary: black; } .dark { --ui-primary: white; } ``` :: ## Text Nuxt UI provides Tailwind CSS utility classes for text colors, allowing you to use class names like `text-dimmed` or `text-muted`: ::code-preview [Dimmed]{.text-dimmed.text-sm.px-4.inline-block.rounded-md class="py-1.5"}[Muted]{.text-muted.text-sm.px-4.inline-block.rounded-md class="py-1.5"}[Toned]{.text-toned.text-sm.px-4.inline-block.rounded-md class="py-1.5"}[Text]{.text-default.text-sm.px-4.inline-block.rounded-md class="py-1.5"}[Highlighted]{.text-highlighted.text-sm.px-4.inline-block.rounded-md class="py-1.5"}[Inverted]{.text-inverted.bg-inverted.text-sm.px-4.inline-block.rounded-md class="py-1.5"} #code ```vue ``` :: Each utility class uses a CSS variable to set its color for light and dark modes: ::code-group ```css [Light] :root { --ui-text-dimmed: var(--ui-color-neutral-400); --ui-text-muted: var(--ui-color-neutral-500); --ui-text-toned: var(--ui-color-neutral-600); --ui-text: var(--ui-color-neutral-700); --ui-text-highlighted: var(--ui-color-neutral-900); --ui-text-inverted: var(--color-white); } ``` ```css [Dark] .dark { --ui-text-dimmed: var(--ui-color-neutral-500); --ui-text-muted: var(--ui-color-neutral-400); --ui-text-toned: var(--ui-color-neutral-300); --ui-text: var(--ui-color-neutral-200); --ui-text-highlighted: var(--color-white); --ui-text-inverted: var(--ui-color-neutral-900); } ``` :: ::tip You can customize these CSS variables in your `main.css` file: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-text: var(--ui-color-neutral-900); } .dark { --ui-text: var(--color-white); } ``` :: ## Background Nuxt UI provides Tailwind CSS utility classes for background colors, allowing you to use class names like `bg-default` or `bg-muted`: ::code-preview [Default]{.bg-default.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Muted]{.bg-muted.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Elevated]{.bg-elevated.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Accented]{.bg-accented.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Inverted]{.bg-inverted.text-inverted.text-sm.px-4.inline-block.rounded-md class="py-1.5"} #code ```vue ``` :: Each utility class uses a CSS variable to set its color for light and dark modes: ::code-group ```css [Light] :root { --ui-bg: var(--color-white); --ui-bg-muted: var(--ui-color-neutral-50); --ui-bg-elevated: var(--ui-color-neutral-100); --ui-bg-accented: var(--ui-color-neutral-200); --ui-bg-inverted: var(--ui-color-neutral-900); } ``` ```css [Dark] .dark { --ui-bg: var(--ui-color-neutral-900); --ui-bg-muted: var(--ui-color-neutral-800); --ui-bg-elevated: var(--ui-color-neutral-800); --ui-bg-accented: var(--ui-color-neutral-700); --ui-bg-inverted: var(--color-white); } ``` :: ::tip You can customize these CSS variables in your `main.css` file: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-bg: var(--ui-color-neutral-50); } .dark { --ui-bg: var(--ui-color-neutral-950); } ``` :: ## Border Nuxt UI provides Tailwind CSS utility classes for border colors, allowing you to use class names like `border-default` or `border-muted`: ::code-preview [Default]{.border-2.border-default.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Muted]{.border-2.border-muted.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Accented]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[Inverted]{.border-2.border-inverted.text-sm.px-4.inline-block.rounded-md class="py-1.5"} #code ```vue ``` :: Each utility class uses a CSS variable to set its color for light and dark modes: ::code-group ```css [Light] :root { --ui-border: var(--ui-color-neutral-200); --ui-border-muted: var(--ui-color-neutral-200); --ui-border-accented: var(--ui-color-neutral-300); --ui-border-inverted: var(--ui-color-neutral-900); } ``` ```css [Dark] .dark { --ui-border: var(--ui-color-neutral-800); --ui-border-muted: var(--ui-color-neutral-700); --ui-border-accented: var(--ui-color-neutral-700); --ui-border-inverted: var(--color-white); } ``` :: ::tip You can customize these CSS variables in your `main.css` file: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-border: var(--ui-color-neutral-100); } .dark { --ui-border: var(--ui-color-neutral-900); } ``` :: ## Radius Nuxt UI overrides Tailwind CSS's default `rounded-*` utilities with a unified border radius system, allowing you to use regular [border radius utilities](https://tailwindcss.com/docs/border-radius){rel="nofollow"} like `rounded-xs` or `rounded-2xl`: ::code-preview [xs]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-xs.mr-2 class="py-1.5"}[sm]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-sm.mr-2 class="py-1.5"}[md]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-md.mr-2 class="py-1.5"}[lg]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-lg.mr-2 class="py-1.5"}[xl]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-xl.mr-2 class="py-1.5"}[2xl]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-2xl.mr-2 class="py-1.5"}[3xl]{.border-2.border-accented.text-sm.px-4.inline-block.rounded-3xl.mr-2 class="py-1.5"} #code ```vue ``` :: These utility classes are calculated based on a global `--ui-radius` CSS variable, which defines the base radius value applied across all components for a consistent look. ```css :root { --ui-radius: 0.25rem; } ``` ::tip You can customize the base radius value in your `main.css` file: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-radius: 0.5rem; } ``` :: ::note Try the :prose-icon{.text-primary name="i-lucide-swatch-book"} theme picker in the header above to change the base radius value. :: ## Container Nuxt UI provides a `--ui-container` CSS variable that controls the maximum width of the [Container](https://ui.nuxt.com/docs/components/container) component. ```css :root { --ui-container: var(--container-7xl); } ``` ::tip You can customize this value in your `main.css` file to adjust container widths consistently throughout your application: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @theme { --container-8xl: 90rem; } :root { --ui-container: var(--container-8xl); } ``` :: ## Header Nuxt UI provides a `--ui-header-height` CSS variable that controls the height of the [Header](https://ui.nuxt.com/docs/components/header) component. ```css :root { --ui-header-height: --spacing(16); } ``` ::tip You can customize this value in your `main.css` to adjust header height consistently throughout your application: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; :root { --ui-header-height: --spacing(24); } ``` :: ## Body Nuxt UI applies default classes on the ``{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="html"} element of your app for consistent theming across light and dark modes: ```css body { @apply antialiased text-default bg-default scheme-light dark:scheme-dark; } ``` # Customize components ## Tailwind Variants Nuxt UI components are styled using the [Tailwind Variants](https://www.tailwind-variants.org/){rel="nofollow"} API, which provides a powerful way to create variants and manage component styles. ### Slots Components can have multiple `slots`, each representing a distinct HTML element or section within the component. These slots allow for flexible content insertion and styling. Let's take the [Card](https://ui.nuxt.com/docs/components/card) component as an example which has multiple slots: ::code-group ```ts [src/theme/card.ts] export default { slots: { root: 'bg-default ring ring-default divide-y divide-default rounded-lg', header: 'p-4 sm:px-6', body: 'p-4 sm:p-6', footer: 'p-4 sm:px-6' } } ``` ```vue [src/runtime/components/Card.vue] ``` :: Some components don't have slots, they are just composed of a single root element. In this case, the theme only defines the `base` slot like the [Container](https://ui.nuxt.com/docs/components/container) component for example: ::code-group ```ts [src/theme/container.ts] export default { base: 'max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8' } ``` ```vue [src/runtime/components/Container.vue] ``` :: ::warning Components without slots don't have a [`ui` prop](https://ui.nuxt.com/#ui-prop), only the [`class` prop](https://ui.nuxt.com/#class-prop) is available to override styles. :: ### Variants Components support `variants`, which allow you to dynamically adjust the styles of different `slots` based on component props. For example, the [Avatar](https://ui.nuxt.com/docs/components/avatar) component uses a `size` variant to control its appearance: ```ts [src/theme/avatar.ts] {6-18} export default { slots: { root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-elevated', image: 'h-full w-full rounded-[inherit] object-cover' }, variants: { size: { sm: { root: 'size-7 text-sm' }, md: { root: 'size-8 text-base' }, lg: { root: 'size-9 text-lg' } } }, defaultVariants: { size: 'md' } } ``` This way, the `size` prop will apply the corresponding styles to the `root` slot: ```vue ``` ### Default Variants The `defaultVariants` property sets the default value for each variant when no prop is passed. For example, the [Avatar](https://ui.nuxt.com/docs/components/avatar) component has its default size set to `md`: ```ts [src/theme/avatar.ts] {19-21} export default { slots: { root: 'inline-flex items-center justify-center shrink-0 select-none overflow-hidden rounded-full align-middle bg-elevated', image: 'h-full w-full rounded-[inherit] object-cover' }, variants: { size: { sm: { root: 'size-7 text-sm' }, md: { root: 'size-8 text-base' }, lg: { root: 'size-9 text-lg' } } }, defaultVariants: { size: 'md' } } ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/installation/nuxt#themedefaultvariants --- You can use the `theme.defaultVariants` option in your `nuxt.config.ts` to override the default values for `size` and `color` for all components at once. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/installation/vue#themedefaultvariants --- You can use the `theme.defaultVariants` option in your `vite.config.ts` to override the default values for `size` and `color` for all components at once. ::: :: ### Compound Variants Some components use the `compoundVariants` property to apply classes when multiple variant conditions are met at the same time. For example, the [Button](https://ui.nuxt.com/docs/components/button) component uses the `compoundVariants` property to apply classes for a specific `color` and `variant` combination: ```ts [src/theme/button.ts] {27-31} import type { ModuleOptions } from '../module' export default (options: Required) => ({ 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', options.theme.transitions && 'transition-colors'] }, variants: { color: { ...Object.fromEntries((options.theme.colors || []).map((color: string) => [color, ''])), neutral: '' }, variant: { solid: '', outline: '', soft: '', subtle: '', ghost: '', link: '' } }, compoundVariants: [ ...(options.theme.colors || []).map((color: string) => ({ color, variant: 'outline', class: `ring ring-inset ring-${color}/50 text-${color} hover:bg-${color}/10 active:bg-${color}/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-${color}` })), { 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' } ], defaultVariants: { color: 'primary', variant: 'solid' } }) ``` ## Customize theme You have multiple ways to customize the appearance of Nuxt UI components, you can do it for all components at once or on a per-component basis. ::note Tailwind Variants uses [`tailwind-merge`](https://github.com/dcastil/tailwind-merge){rel="nofollow"} under the hood to merge classes so you don't have to worry about conflicting classes. :: ::tip You can explore the theme for each component in two ways: - Check the `Theme` section in the documentation of each individual component. - Browse the source code directly in the GitHub repository at [`src/theme`](https://github.com/nuxt/ui/tree/v4/src/theme){rel="nofollow"}. :: ### Global config ::framework-only #nuxt You can override the theme of components globally inside your `app.config.ts` by using the exact same structure as the theme object. #vue You can override the theme of components globally inside your `vite.config.ts` by using the exact same structure as the theme object. :: You can customize the [`slots`](https://ui.nuxt.com/#slots), [`variants`](https://ui.nuxt.com/#variants), [`compoundVariants`](https://ui.nuxt.com/#compound-variants) and [`defaultVariants`](https://ui.nuxt.com/#default-variants) of a component to change the default theme of a component: ::framework-only #nuxt :::div ```ts [app.config.ts] export default defineAppConfig({ ui: { button: { slots: { base: 'font-bold' }, variants: { size: { md: { leadingIcon: 'size-4' } } }, compoundVariants: [{ color: 'neutral', variant: 'outline', class: 'ring-default hover:bg-accented' }], defaultVariants: { color: 'neutral', variant: 'outline' } } } }) ``` ::: #vue :::div ```ts [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: 'font-bold' }, variants: { size: { md: { leadingIcon: 'size-4' } } }, compoundVariants: [{ color: 'neutral', variant: 'outline', class: 'ring-default hover:bg-accented' }], defaultVariants: { color: 'neutral', variant: 'outline' } } } }) ] }) ``` ::: :: ::note In this example, `font-bold` overrides `font-medium` on all buttons, `size-4` overrides `size-5` class on the leading icon when `size="md"` and `ring-default hover:bg-accented` overrides `ring-accented hover:bg-elevated` when `color="neutral"` and `variant="outline"`. The buttons now defaults to `color="neutral"` and `variant="outline"`. :: ### `ui` prop You can also override a component's **slots** using the `ui` prop. This takes priority over both global config and resolved `variants`. ```vue ``` ::note In this example, the `trailingIcon` slot is overwritten with `size-3` even though the `md` size variant would apply a `size-5` class to it. :: ### `class` prop The `class` prop allows you to override the classes of the `root` or `base` slot. This takes priority over both global config and resolved `variants`. ```vue ``` ::note In this example, the `font-bold` class will override the default `font-medium` class on this button. :: # Icons ::callout --- class: hidden icon: i-logos-vue to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue --- Looking for the **Vue** version? :: ## Usage Nuxt UI automatically registers the [`@nuxt/icon`](https://github.com/nuxt/icon){rel="nofollow"} module for you, so there's no additional setup required. ### Icon component You can use the [Icon](https://ui.nuxt.com/docs/components/icon) component with a `name` prop to display an icon: ```vue ``` ::note You can use any name from the {rel="nofollow"} collection. :: ### Component props Some components also have an `icon` prop to display an icon, like the [Button](https://ui.nuxt.com/docs/components/button) for example: ```vue ``` ## Collections ### Iconify dataset It's highly recommended to install the icon data locally with: ::code-group{sync="pm"} ```bash [pnpm] pnpm i @iconify-json/{collection_name} ``` ```bash [yarn] yarn add @iconify-json/{collection_name} ``` ```bash [npm] npm install @iconify-json/{collection_name} ``` :: For example, to use the `i-uil-github` icon, install its collection with `@iconify-json/uil`. This way the icons can be served locally or from your serverless functions, which is faster and more reliable on both SSR and client-side. ::note --- target: _blank to: https://github.com/nuxt/icon?tab=readme-ov-file#iconify-dataset --- Read more about this in the `@nuxt/icon` documentation. :: ### Custom local collections You can use local SVG files to create a custom Iconify collection. For example, place your icons' SVG files under a folder of your choice, for example, `./app/assets/icons`: ```bash assets/icons ├── add.svg └── remove.svg ``` In your `nuxt.config.ts`, add an item in `icon.customCollections`: ```ts export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], icon: { customCollections: [{ prefix: 'custom', dir: './app/assets/icons' }] } }) ``` Then you can use the icons like this: ```vue ``` ::note --- target: _blank to: https://github.com/nuxt/icon?tab=readme-ov-file#custom-local-collections --- Read more about this in the `@nuxt/icon` documentation. :: ## Theme You can change the default icons used by components in your `app.config.ts`: ::icons-theme :: # Icons ::callout --- class: hidden icon: i-logos-nuxt-icon to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt --- Looking for the **Nuxt** version? :: ## Usage ### Icon component You can use the [Icon](https://ui.nuxt.com/docs/components/icon) component with a `name` prop to display an icon: ```vue ``` ::note You can use any name from the {rel="nofollow"} collection. :: ::warning When using collections with a dash (`-`), you need to separate the icon name from the collection name with a colon (`:`) as `@iconify/vue` does not handle this case like `@nuxt/icon`. For example, instead of `i-simple-icons-github` you need to write `i-simple-icons:github` or `simple-icons:github`. Learn more about the [Iconify naming convention](https://iconify.design/docs/icon-components/vue/#icon){rel="nofollow"}. :: ### Component props Some components also have an `icon` prop to display an icon, like the [Button](https://ui.nuxt.com/docs/components/button) for example: ```vue ``` ## Theme You can change the default icons used by Nuxt UI components in your `vite.config.ts`: ::icons-theme :: # Fonts ## Usage Nuxt UI automatically registers the [`@nuxt/fonts`](https://github.com/nuxt/fonts){rel="nofollow"} module for you, so there's no additional setup required. ### Declaration To use a font in your Nuxt UI application, you can simply declare it in your CSS. It will be automatically loaded and optimized for you. ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @theme { --font-sans: 'Public Sans', sans-serif; } ``` ### Configuration You can disable the `@nuxt/fonts` module with the `ui.fonts` option in your `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ ui: { fonts: false } }) ``` # Color Mode ::callout --- class: hidden icon: i-logos-vue to: https://ui.nuxt.com/docs/getting-started/integrations/color-mode/vue --- Looking for the **Vue** version? :: ## Usage Nuxt UI automatically registers the [`@nuxtjs/color-mode`](https://github.com/nuxt-modules/color-mode){rel="nofollow"} module for you, so there's no additional setup required. ### Components You can use the built-in [ColorModeAvatar](https://ui.nuxt.com/docs/components/color-mode-avatar) or [ColorModeImage](https://ui.nuxt.com/docs/components/color-mode-image) components to display different images for light and dark mode and the [ColorModeButton](https://ui.nuxt.com/docs/components/color-mode-button), [ColorModeSwitch](https://ui.nuxt.com/docs/components/color-mode-switch) or [ColorModeSelect](https://ui.nuxt.com/docs/components/color-mode-select) components to switch between light and dark modes. You can also use the [useColorMode](https://color-mode.nuxtjs.org/#usage){rel="nofollow"} composable to build your own custom component: ```vue [ColorModeButton.vue] ``` ### Configuration You can disable the `@nuxtjs/color-mode` module with the `ui.colorMode` option in your `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxt/ui'], css: ['~/assets/css/main.css'], ui: { colorMode: false } }) ``` # Color Mode ::callout --- class: hidden icon: i-logos-nuxt-icon to: https://ui.nuxt.com/docs/getting-started/integrations/color-mode/nuxt --- Looking for the **Nuxt** version? :: ## Usage Nuxt UI automatically registers the [useDark](https://vueuse.org/core/useDark){rel="nofollow"} composable as a Vue plugin, so there's no additional setup required. ### Components You can use the built-in [ColorModeAvatar](https://ui.nuxt.com/docs/components/color-mode-avatar) or [ColorModeImage](https://ui.nuxt.com/docs/components/color-mode-image) components to display different images for light and dark mode and the [ColorModeButton](https://ui.nuxt.com/docs/components/color-mode-button), [ColorModeSwitch](https://ui.nuxt.com/docs/components/color-mode-switch) or [ColorModeSelect](https://ui.nuxt.com/docs/components/color-mode-select) components to switch between light and dark modes. You can also use the [useColorMode](https://vueuse.org/core/useColorMode){rel="nofollow"} composable to build your own custom component: ```vue [ColorModeButton.vue] ``` ### Configuration You can disable this plugin with the `colorMode` option in your `vite.config.ts`: ```ts [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({ colorMode: false }) ] }) ``` # Internationalization (i18n) ::callout --- class: hidden icon: i-logos-vue to: https://ui.nuxt.com/docs/getting-started/integrations/i18n/vue --- Looking for the **Vue** version? :: ## Usage ::note{to="https://ui.nuxt.com/docs/components/app"} Nuxt UI provides an **App** component that wraps your app to provide global configurations. :: ### Locale Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`: ```vue [app.vue] ``` ### Custom locale You can create your own locale using the `defineLocale` composable: ```vue [app.vue] ``` ::tip Look at the `code` parameter, there you need to pass the iso code of the language. Example: - `hi` Hindi (language) - `de-AT`: German (language) as used in Austria (region) :: ### Extend locale You can customize an existing locale by overriding its `messages` or `code` using the `extendLocale` composable: ```vue [app.vue] ``` ### Dynamic locale To dynamically switch between languages, you can use the [Nuxt I18n](https://i18n.nuxtjs.org/){rel="nofollow"} module. ::steps{level="4"} #### Install the Nuxt I18n package :::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxtjs/i18n ``` ```bash [yarn] yarn add @nuxtjs/i18n ``` ```bash [npm] npm install @nuxtjs/i18n ``` ```bash [bun] bun add @nuxtjs/i18n ``` ::: #### Add the Nuxt I18n module in your `nuxt.config.ts`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: [ '@nuxt/ui', '@nuxtjs/i18n' ], css: ['~/assets/css/main.css'], i18n: { locales: [{ code: 'de', name: 'Deutsch' }, { code: 'en', name: 'English' }, { code: 'fr', name: 'Français' }] } }) ``` #### Set the `locale` prop using `useI18n` ```vue [app.vue] ``` :: ### Dynamic direction Each locale has a `dir` property which will be used by the `App` component to set the directionality of all components. In a multilingual application, you might want to set the `lang` and `dir` attributes on the `` element dynamically based on the user's locale, which you can do with the [useHead](https://nuxt.com/docs/api/composables/use-head){rel="nofollow"} composable: ```vue [app.vue] ``` ## Supported languages ::supported-languages :: # Internationalization (i18n) ::callout --- class: hidden icon: i-logos-nuxt-icon to: https://ui.nuxt.com/docs/getting-started/integrations/i18n/nuxt --- Looking for the **Nuxt** version? :: ## Usage ::note{to="https://ui.nuxt.com/docs/components/app"} Nuxt UI provides an **App** component that wraps your app to provide global configurations. :: ### Locale Use the `locale` prop with the locale you want to use from `@nuxt/ui/locale`: ```vue [App.vue] ``` ### Custom locale You can create your own locale using the `defineLocale` composable: ```vue [App.vue] ``` ::tip Look at the `code` parameter, there you need to pass the iso code of the language. Example: - `hi` Hindi (language) - `de-AT`: German (language) as used in Austria (region) :: ### Extend locale You can customize an existing locale by overriding its `messages` or `code` using the `extendLocale` composable: ```vue [App.vue] ``` ### Dynamic locale To dynamically switch between languages, you can use the [Vue I18n](https://vue-i18n.intlify.dev/){rel="nofollow"} plugin. ::steps{level="4"} #### Install the Vue I18n package :::code-group{sync="pm"} ```bash [pnpm] pnpm add vue-i18n@11 ``` ```bash [yarn] yarn add vue-i18n@11 ``` ```bash [npm] npm install vue-i18n@11 ``` ```bash [bun] bun add vue-i18n@11 ``` ::: #### Use the Vue I18n plugin in your `main.ts` ```ts [main.ts] {3,14-26,29} import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import { createI18n } from 'vue-i18n' import ui from '@nuxt/ui/vue-plugin' import App from './App.vue' const app = createApp(App) const router = createRouter({ routes: [], history: createWebHistory() }) const i18n = createI18n({ legacy: false, locale: 'en', availableLocales: ['en', 'de'], messages: { en: { // ... }, de: { // ... } } }) app.use(router) app.use(i18n) app.use(ui) app.mount('#app') ``` #### Set the `locale` prop using `useI18n` ```vue [App.vue] ``` :: ### Dynamic direction Each locale has a `dir` property which will be used by the `App` component to set the directionality of all components. In a multilingual application, you might want to set the `lang` and `dir` attributes on the `` element dynamically based on the user's locale, which you can do with the [useHead](https://unhead.unjs.io/usage/composables/use-head){rel="nofollow"} composable: ```vue [App.vue] ``` ## Supported languages ::supported-languages :: # Content ## Installation To get started, you can follow the official [guide](https://content.nuxt.com/docs/getting-started/installation){rel="nofollow"} or in summary: ::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxt/content ``` ```bash [yarn] yarn add @nuxt/content ``` ```bash [npm] npm install @nuxt/content ``` ```bash [bun] bun add @nuxt/content ``` :: Then, add the `@nuxt/content` module in your `nuxt.config.ts`: ```ts [nuxt.config.ts] {4} export default defineNuxtConfig({ modules: [ '@nuxt/ui', '@nuxt/content' ], css: ['~/assets/css/main.css'] }) ``` ::caution You need to register `@nuxt/content` after `@nuxt/ui` in the `modules` array, otherwise the prose components will not be available. :: ## Configuration When using Tailwind CSS classes in your markdown content files, you need to ensure Tailwind can detect and generate the necessary utility classes. By default, Tailwind's automatic content detection might not pick up classes written in markdown files. To fix this, use the [`@source` directive](https://tailwindcss.com/docs/functions-and-directives#source-directive){rel="nofollow"} in your CSS file to explicitly include your content directory: ```css [app/assets/css/main.css] @import "tailwindcss"; @import "@nuxt/ui"; @source "../../../content/**/*"; ``` This ensures that: - Tailwind scans all markdown files in your content directory - Any utility classes used in your markdown (like `text-primary`) are included in the final CSS - Dynamic classes in MDC components or custom Vue components within your content work properly ::tip You can also use glob patterns to be more specific about which files to scan: - `@source "../../../content/docs/**/*.md"` - Only scan markdown in the docs folder - `@source "../../../content/**/*.{md,yml}"` - Include both markdown and YAML files :: ::note --- target: _blank to: https://tailwindcss.com/docs/detecting-classes-in-source-files --- Learn more about Tailwind's automatic content detection and best practices for optimizing build performance. :: ## Components You might be using `@nuxt/content` to build a documentation. To help you with that, we've built some components that you can use in your pages: - a built-in full-text search command palette with [ContentSearch](https://ui.nuxt.com/docs/components/content-search), replacing the need for Algolia DocSearch - a navigation tree with the [ContentNavigation](https://ui.nuxt.com/docs/components/content-navigation) component - a sticky Table of Contents with the [ContentToc](https://ui.nuxt.com/docs/components/content-toc) component - a prev / next navigation with the [ContentSurround](https://ui.nuxt.com/docs/components/content-surround) component ## Typography Nuxt UI provides its own custom implementations of all prose components for seamless integration with `@nuxt/content`. This approach ensures consistent styling, complete control over typography, and perfect alignment with the Nuxt UI design system so your content always looks and feels cohesive out of the box. ::note{to="https://ui.nuxt.com/docs/typography"} Discover the full **Typography** system and explore all available prose components for rich, consistent content presentation. :: ## Utils ### `mapContentNavigation` This util will map the navigation from `queryCollectionNavigation` and transform it recursively into an array of objects that can be used by various components. `mapContentNavigation(navigation, options?)` - `navigation`: The navigation tree (array of ContentNavigationItem). - `options` (optional): - `labelAttribute`: (string) Which field to use as label (`title` by default) - `deep`: (number or undefined) Controls how many levels of navigation are included (`undefined` by default : includes all levels) **Example:** As shown in the breadcrumb example below, it's commonly used to transform the navigation data into the correct format. ```vue [app.vue] ``` # MCP Server ## What is MCP? MCP (Model Context Protocol) is a standardized protocol that enables AI assistants to access external data sources and tools. Nuxt UI provides an MCP server that allows AI assistants like Claude Code, Cursor, and Windsurf to access component information, source code, and usage examples directly. The MCP server provides structured access to our component library, making it easy for AI tools to understand and assist with Nuxt UI development. ## Available Resources The Nuxt UI MCP server provides the following resources for discovery: - **`resource://nuxt-ui/components`**: Browse all available components with categories - **`resource://nuxt-ui/composables`**: Browse all available composables with categories - **`resource://nuxt-ui/examples`**: Browse all available code examples - **`resource://nuxt-ui/templates`**: Browse all available project templates - **`resource://nuxt-ui/documentation-pages`**: Browse all available documentation pages You're able to access these resources with tools like Claude Code by using `@`. ## Available Tools The Nuxt UI MCP server provides the following tools organized by category: ### Component Tools - **`list_components`**: Lists all available Nuxt UI components with their categories and basic information - **`list_composables`**: Lists all available Nuxt UI composables with their categories and basic information - **`get_component`**: Retrieves component documentation and details - **`get_component_metadata`**: Retrieves detailed metadata for a component including props, slots, and events - **`search_components_by_category`**: Searches components by category or text filter ### Template Tools - **`list_templates`**: Lists all available Nuxt UI templates with optional category filtering - **`get_template`**: Retrieves template details and setup instructions ### Documentation Tools - **`list_documentation_pages`**: Lists all documentation pages - **`get_documentation_page`**: Retrieves documentation page content by URL path - **`list_getting_started_guides`**: Lists all getting started guides and installation instructions ### Example Tools - **`list_examples`**: Lists all available UI examples and code demonstrations - **`get_example`**: Retrieves specific UI example implementation code and details ### Migration Tools - **`get_migration_guide`**: Retrieves version-specific migration guides and upgrade instructions ## Available Prompts The Nuxt UI MCP server provides guided prompts for common workflows: - **`find_component_for_usecase`**: Find the best component for your specific use case - **`implement_component_with_props`**: Generate complete component implementation with proper props - **`setup_project_with_template`**: Get guided setup instructions for project templates You're able to access these resources with tools like Claude Code by using `/`. ## Configuration The Nuxt UI MCP server uses HTTP transport and can be configured in different AI assistants. ### ChatGPT ::note{icon="i-lucide-info"} **Custom connectors using MCP are available on ChatGPT for Pro and Plus accounts** on the web. :: Follow these steps to set up Nuxt UI as a connector within ChatGPT: 1. **Enable Developer mode:** - Go to Settings → Connectors → Advanced settings → Developer mode 2. **Open ChatGPT settings** 3. **In the Connectors tab, Create a new connector:** - Give it a name: `Nuxt UI` - MCP server URL: `https://ui.nuxt.com/mcp` - Authentication: `None` 4. **Click Create** The Nuxt UI connector will appear in the composer's "Developer mode" tool later during conversations. ### Claude Code ::note{icon="i-lucide-info"} **Ensure Claude Code is installed** - Visit [Anthropic's documentation](https://docs.anthropic.com/en/docs/claude-code/quickstart){rel="nofollow"} for installation instructions. :: Add the server using the CLI command: ```bash claude mcp add --transport http nuxt-ui-remote https://ui.nuxt.com/mcp ``` ### Cursor #### Quick Install Click the button below to install the Nuxt UI MCP server directly in Cursor: ::u-button --- color: neutral icon: i-custom-cursor label: Install MCP Server to: cursor://anysphere.cursor-deeplink/mcp/install?name=nuxt-ui&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vdWkubnV4dC5jb20vbWNwIn0%3D --- :: #### Manual Setup Instructions: 1. Open Cursor and go to "Settings" > "Tools & MCP" 2. Add the Nuxt UI MCP server configuration Or manually create/update `.cursor/mcp.json` in your project root: ```json [.cursor/mcp.json] { "mcpServers": { "nuxt-ui": { "type": "http", "url": "https://ui.nuxt.com/mcp" } } } ``` ### Le Chat Mistral #### Setup Instructions: 1. Navigate to "Intelligence" > "Connectors" 2. Click on "Add Connector" button, then select "Custom MCP Connector" 3. Create your Custom MCP Connector: - Connector Name : `NuxtUI` - Connector Server : `https://ui.nuxt.com/mcp` ### Visual Studio Code ::note{icon="i-lucide-info"} **Install required extensions** - Ensure you have [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot){rel="nofollow"} and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat){rel="nofollow"} extensions installed. :: #### Setup Instructions: 1. Open VS Code and access the Command Palette (Ctrl/Cmd + Shift + P) 2. Type "Preferences: Open Workspace Settings (JSON)" and select it 3. Navigate to your project's `.vscode` folder or create one if it doesn't exist 4. Create or edit the `mcp.json` file with the following configuration: ```json [.vscode/mcp.json] { "servers": { "nuxt-ui": { "type": "http", "url": "https://ui.nuxt.com/mcp" } } } ``` ### Windsurf #### Setup Instructions: 1. Open Windsurf and navigate to "Settings" > "Windsurf Settings" > "Cascade" 2. Click the "Manage MCPs" button, then select the "View raw config" option 3. Add the following configuration to your MCP settings: ```json [.codeium/windsurf/mcp_config.json] { "mcpServers": { "nuxt-ui": { "type": "http", "url": "https://ui.nuxt.com/mcp" } } } ``` ### Zed #### Setup Instructions: 1. Open Zed and go to "Settings" > "Open Settings" 2. Navigate to the JSON settings file 3. Add the following context server configuration to your settings: ```json [.config/zed/settings.json] { "context_servers": { "nuxt-ui": { "source": "custom", "command": "npx", "args": ["mcp-remote", "https://ui.nuxt.com/mcp"], "env": {} } } } ``` ### Opencode #### Setup Instructions: 1. In your project root, create `opencode.json` 2. Add the following configuration: ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "nuxt-ui": { "type": "remote", "url": "https://ui.nuxt.com/mcp", "enabled": true }, } } ``` ## Usage Examples Once configured, you can ask your AI assistant questions like: - "List all available Nuxt UI components" - "Get Button component documentation" - "What props does Input accept?" - "Find form-related components" - "List dashboard templates" - "Get template setup instructions" - "Show installation guide" - "Get v4 migration guide" - "List all examples" - "Get ContactForm example code" The AI assistant will use the MCP server to fetch structured JSON data and provide guided assistance for Nuxt UI during development. # LLMs.txt ## What is LLMs.txt? LLMs.txt is a structured documentation format specifically designed for large language models (LLMs). Nuxt UI provides LLMs.txt files that contain comprehensive information about our component library, making it easy for AI tools to understand and assist with Nuxt UI development. These files are optimized for AI consumption and contain structured information about components, APIs, usage patterns, and best practices. ## Available routes We provide LLMs.txt routes to help AI tools access our documentation: - **`/llms.txt`** - Contains a structured overview of all components and their documentation links (\~5K tokens) - **`/llms-full.txt`** - Provides comprehensive documentation including implementation details, examples, theming, composables, and migration guidance (\~1M+ tokens) ## Choosing the Right File ::note{icon="i-lucide-info"} **Most users should start with `/llms.txt`** - it contains all essential information and works with standard LLM context windows. Use `/llms-full.txt` only if you need comprehensive implementation examples and your AI tool supports large contexts (200K+ tokens). :: ## Important usage notes ::warning{icon="i-lucide-alert-triangle"} **@-symbol must be typed manually** - When using tools like Cursor or Windsurf, the `@` symbol must be typed by hand in the chat interface. Copy-pasting breaks the tool's ability to recognize it as a context reference. :: ## Usage with AI Tools ### Cursor Nuxt UI provides specialized LLMs.txt files that you can reference in Cursor for better AI assistance with component development. #### How to use: 1. **Direct reference**: Mention the LLMs.txt URLs when asking questions 2. Add these specific URLs to your project context using `@docs` [Read more about Cursor Web and Docs Search](https://docs.cursor.com/en/context/@-symbols/@-docs){rel="nofollow"} ### Windsurf Windsurf can directly access the Nuxt UI LLMs.txt files to understand component usage and best practices. #### Using LLMs.txt with Windsurf: - Use `@docs` to reference specific LLMs.txt URLs - Create persistent rules referencing these URLs in your workspace [Read more about Windsurf Web and Docs Search](https://docs.windsurf.com/windsurf/cascade/web-search){rel="nofollow"} ### Other AI Tools Any AI tool that supports LLMs.txt can use these routes to better understand Nuxt UI. #### Examples for ChatGPT, Claude, or other LLMs: - "Using Nuxt UI documentation from {rel="nofollow"}" - "Follow complete Nuxt UI guidelines from {rel="nofollow"}" # Accordion ## Usage Use the Accordion component to display a list of collapsible items. ```vue ``` ### Items Use the `items` prop as an array of objects with the following properties: - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `trailingIcon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `content?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `value?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `disabled?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot) - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue, header?: ClassNameValue, trigger?: ClassNameValue, leadingIcon?: ClassNameValue, label?: ClassNameValue, trailingIcon?: ClassNameValue, content?: ClassNameValue, body?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue ``` ### Multiple Set the `type` prop to `multiple` to allow multiple items to be active at the same time. Defaults to `single`. ```vue ``` ### Collapsible When `type` is `single`, you can set the `collapsible` prop to `false` to prevent the active item from collapsing. ```vue ``` ### Unmount Use the `unmount-on-hide` prop to prevent the content from being unmounted when the accordion is collapsed. Defaults to `true`. ```vue ``` ::note You can inspect the DOM to see each item's content being rendered. :: ### Disabled Use the `disabled` property to disable the Accordion. You can also disable a specific item by using the `disabled` property in the item object. ```vue ``` ### Trailing Icon Use the `trailing-icon` prop to customize the trailing [Icon](https://ui.nuxt.com/docs/components/icon) of each item. Defaults to `i-lucide-chevron-down`. ::tip You can also set an icon for a specific item by using the `trailingIcon` property in the item object. :: ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronDown` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronDown` key. ::: :: ## Examples ### Control active item(s) You can control the active item(s) by using the `default-value` prop or the `v-model` directive with the index of the item. ```vue [AccordionModelValueExample.vue] ``` ::tip You can also pass the `value` of one of the items if provided. :: ::caution When `type="multiple"`, ensure to pass an array to the `default-value` prop or the `v-model` directive. :: ### With drag and drop Use the [`useSortable`](https://vueuse.org/integrations/useSortable/){rel="nofollow"} composable from [`@vueuse/integrations`](https://vueuse.org/integrations/README.html){rel="nofollow"} to enable drag and drop functionality on the Accordion. This integration wraps [Sortable.js](https://sortablejs.github.io/Sortable/){rel="nofollow"} to provide a seamless drag and drop experience. ```vue [AccordionDragAndDropExample.vue] ``` ### With body slot Use the `#body` slot to customize the body of each item. ```vue [AccordionBodySlotExample.vue] ``` ::tip The `#body` slot includes some pre-defined styles, use the [`#content` slot](https://ui.nuxt.com/#with-content-slot) if you want to start from scratch. :: ### With content slot Use the `#content` slot to customize the content of each item. ```vue [AccordionContentSlotExample.vue] ``` ### With custom slot Use the `slot` property to customize a specific item. You will have access to the following slots: - `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-body`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue [AccordionCustomSlotExample.vue] ``` ### With markdown content You can use the [MDC](https://github.com/nuxt-modules/mdc?tab=readme-ov-file#mdc){rel="nofollow"} component from `@nuxtjs/mdc` to render markdown in the accordion items. ```vue [AccordionMarkdownExample.vue] ``` ## API ### Props ```ts /** * Props for the Accordion component */ interface AccordionProps { /** * The element or component this component should render as. */ as?: any; items?: AccordionItem[] | undefined; /** * The icon displayed on the right side of the trigger. */ trailingIcon?: string | object | undefined; /** * The key used to get the label from the item. * @default "\"label\"" */ labelKey?: GetItemKeys | undefined; ui?: { root?: ClassNameValue; item?: ClassNameValue; header?: ClassNameValue; trigger?: ClassNameValue; content?: ClassNameValue; body?: ClassNameValue; leadingIcon?: ClassNameValue; trailingIcon?: ClassNameValue; label?: ClassNameValue; } | undefined; /** * When type is "single", allows closing content when clicking trigger for an open item. * When type is "multiple", this prop has no effect. * @default "true" */ collapsible?: boolean | undefined; /** * The default active value of the item(s). * * Use when you do not need to control the state of the item(s). */ defaultValue?: string | string[] | undefined; /** * The controlled value of the active item(s). * * Use this when you need to control the state of the items. Can be binded with `v-model` */ modelValue?: string | string[] | undefined; /** * Determines whether a "single" or "multiple" items can be selected at a time. * * This prop will overwrite the inferred type from `modelValue` and `defaultValue`. * @default "\"single\"" */ type?: SingleOrMultipleType | undefined; /** * When `true`, prevents the user from interacting with the accordion and all its items */ disabled?: boolean | undefined; /** * When `true`, the element will be unmounted on closed state. * @default "true" */ unmountOnHide?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the Accordion component */ interface AccordionSlots { leading(): any; default(): any; trailing(): any; content(): any; body(): any; } ``` ### Emits ```ts /** * Emitted events for the Accordion component */ interface AccordionEmits { update:modelValue: (payload: [value: string | string[] | undefined]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { accordion: { slots: { root: 'w-full', item: 'border-b border-default last:border-b-0', header: 'flex', trigger: 'group flex-1 flex items-center gap-1.5 font-medium text-sm py-3.5 focus-visible:outline-primary min-w-0', content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none', body: 'text-sm pb-3.5', leadingIcon: 'shrink-0 size-5', trailingIcon: 'shrink-0 size-5 ms-auto group-data-[state=open]:rotate-180 transition-transform duration-200', label: 'text-start break-words' }, variants: { disabled: { true: { trigger: 'cursor-not-allowed opacity-75' } } } } } }) ``` ## Changelog ::component-changelog :: # Alert ## Usage ### Title Use the `title` prop to set the title of the Alert. ```vue ``` ### Description Use the `description` prop to set the description of the Alert. ```vue ``` ### Icon Use the `icon` prop to show an [Icon](https://ui.nuxt.com/docs/components/icon). ```vue ``` ### Avatar Use the `avatar` prop to show an [Avatar](https://ui.nuxt.com/docs/components/avatar). ```vue ``` ### Color Use the `color` prop to change the color of the Alert. ```vue ``` ### Variant Use the `variant` prop to change the variant of the Alert. ```vue ``` ### Close Use the `close` prop to display a [Button](https://ui.nuxt.com/docs/components/button) to dismiss the Alert. ::tip An `update:open` event will be emitted when the close button is clicked. :: ```vue ``` You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue ``` ### Close Icon Use the `close-icon` prop to customize the close button [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-x`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key. ::: :: ### Actions Use the `actions` prop to add some [Button](https://ui.nuxt.com/docs/components/button) actions to the Alert. ```vue ``` ### Orientation Use the `orientation` prop to change the orientation of the Alert. ```vue ``` ## Examples ### `class` prop Use the `class` prop to override the base styles of the Alert. ```vue ``` ### `ui` prop Use the `ui` prop to override the slots styles of the Alert. ```vue ``` ## API ### Props ```ts /** * Props for the Alert component */ interface AlertProps { /** * The element or component this component should render as. */ as?: any; title?: string | undefined; description?: string | undefined; icon?: string | object | undefined; avatar?: AvatarProps | undefined; color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; variant?: "solid" | "outline" | "soft" | "subtle" | undefined; /** * The orientation between the content and the actions. * @default "\"vertical\"" */ orientation?: "vertical" | "horizontal" | undefined; /** * Display a list of actions: * - under the title and description when orientation is `vertical` * - next to the close button when orientation is `horizontal` * `{ size: 'xs' }`{lang="ts-type"} */ actions?: ButtonProps[] | undefined; /** * Display a close button to dismiss the alert. * `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"} */ close?: boolean | Partial | undefined; /** * The icon displayed in the close button. */ closeIcon?: string | object | undefined; ui?: { root?: ClassNameValue; wrapper?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; icon?: ClassNameValue; avatar?: ClassNameValue; avatarSize?: ClassNameValue; actions?: ClassNameValue; close?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the Alert component */ interface AlertSlots { leading(): any; title(): any; description(): any; actions(): any; close(): any; } ``` ### Emits ```ts /** * Emitted events for the Alert component */ interface AlertEmits { update:open: (payload: [value: boolean]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { alert: { slots: { root: 'relative overflow-hidden w-full rounded-lg p-4 flex gap-2.5', wrapper: 'min-w-0 flex-1 flex flex-col', title: 'text-sm font-medium', description: 'text-sm opacity-90', icon: 'shrink-0 size-5', avatar: 'shrink-0', avatarSize: '2xl', actions: 'flex flex-wrap gap-1.5 shrink-0', close: 'p-0' }, variants: { color: { primary: '', secondary: '', success: '', info: '', warning: '', error: '', neutral: '' }, variant: { solid: '', outline: '', soft: '', subtle: '' }, orientation: { horizontal: { root: 'items-center', actions: 'items-center' }, vertical: { root: 'items-start', actions: 'items-start mt-2.5' } }, title: { true: { description: 'mt-1' } } }, compoundVariants: [ { color: 'primary', variant: 'solid', class: { root: 'bg-primary text-inverted' } }, { color: 'secondary', variant: 'solid', class: { root: 'bg-secondary text-inverted' } }, { color: 'success', variant: 'solid', class: { root: 'bg-success text-inverted' } }, { color: 'info', variant: 'solid', class: { root: 'bg-info text-inverted' } }, { color: 'warning', variant: 'solid', class: { root: 'bg-warning text-inverted' } }, { color: 'error', variant: 'solid', class: { root: 'bg-error text-inverted' } }, { color: 'primary', variant: 'outline', class: { root: 'text-primary ring ring-inset ring-primary/25' } }, { color: 'secondary', variant: 'outline', class: { root: 'text-secondary ring ring-inset ring-secondary/25' } }, { color: 'success', variant: 'outline', class: { root: 'text-success ring ring-inset ring-success/25' } }, { color: 'info', variant: 'outline', class: { root: 'text-info ring ring-inset ring-info/25' } }, { color: 'warning', variant: 'outline', class: { root: 'text-warning ring ring-inset ring-warning/25' } }, { color: 'error', variant: 'outline', class: { root: 'text-error ring ring-inset ring-error/25' } }, { color: 'primary', variant: 'soft', class: { root: 'bg-primary/10 text-primary' } }, { color: 'secondary', variant: 'soft', class: { root: 'bg-secondary/10 text-secondary' } }, { color: 'success', variant: 'soft', class: { root: 'bg-success/10 text-success' } }, { color: 'info', variant: 'soft', class: { root: 'bg-info/10 text-info' } }, { color: 'warning', variant: 'soft', class: { root: 'bg-warning/10 text-warning' } }, { color: 'error', variant: 'soft', class: { root: 'bg-error/10 text-error' } }, { color: 'primary', variant: 'subtle', class: { root: 'bg-primary/10 text-primary ring ring-inset ring-primary/25' } }, { color: 'secondary', variant: 'subtle', class: { root: 'bg-secondary/10 text-secondary ring ring-inset ring-secondary/25' } }, { color: 'success', variant: 'subtle', class: { root: 'bg-success/10 text-success ring ring-inset ring-success/25' } }, { color: 'info', variant: 'subtle', class: { root: 'bg-info/10 text-info ring ring-inset ring-info/25' } }, { color: 'warning', variant: 'subtle', class: { root: 'bg-warning/10 text-warning ring ring-inset ring-warning/25' } }, { color: 'error', variant: 'subtle', class: { root: 'bg-error/10 text-error ring ring-inset ring-error/25' } }, { color: 'neutral', variant: 'solid', class: { root: 'text-inverted bg-inverted' } }, { color: 'neutral', variant: 'outline', class: { root: 'text-highlighted bg-default ring ring-inset ring-default' } }, { color: 'neutral', variant: 'soft', class: { root: 'text-highlighted bg-elevated/50' } }, { color: 'neutral', variant: 'subtle', class: { root: 'text-highlighted bg-elevated/50 ring ring-inset ring-accented' } } ], defaultVariants: { color: 'primary', variant: 'solid' } } } }) ``` ## Changelog ::component-changelog :: # App ## Usage This component implements Reka UI [ConfigProvider](https://reka-ui.com/docs/utilities/config-provider){rel="nofollow"} to provide global configuration to all components: - Enables all primitives to inherit global reading direction. - Enables changing the behavior of scroll body when setting body lock. - Much more controls to prevent layout shifts. It's also using [ToastProvider](https://reka-ui.com/docs/components/toast#provider){rel="nofollow"} and [TooltipProvider](https://reka-ui.com/docs/components/tooltip#provider){rel="nofollow"} to provide global toasts and tooltips, as well as programmatic modals and slideovers. Wrap your entire application with the App component in your `app.vue` file: ```vue [app.vue] ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/i18n/nuxt#locale --- Learn how to use the `locale` prop to change the locale of your app. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/i18n/vue#locale --- Learn how to use the `locale` prop to change the locale of your app. ::: :: ## API ### Props ```ts /** * Props for the App component */ interface AppProps { tooltip?: TooltipProviderProps | undefined; toaster?: ToasterProps | null | undefined; locale?: Locale | undefined; /** * @default "\"body\"" */ portal?: string | boolean | HTMLElement | undefined; /** * The global scroll body behavior of your application. This will be inherited by the related primitives. */ scrollBody?: boolean | ScrollBodyOption | undefined; /** * The global `nonce` value of your application. This will be inherited by the related primitives. */ nonce?: string | undefined; } ``` ### Slots ```ts /** * Slots for the App component */ interface AppSlots { default(): any; } ``` ## Changelog ::component-changelog :: # AuthForm ## Usage Built on top of the [Form](https://ui.nuxt.com/docs/components/form) component, the `AuthForm` component can be used in your pages or wrapped in a [PageCard](https://ui.nuxt.com/docs/components/page-card). ```vue [AuthFormExample.vue] ``` ### Fields The Form will construct itself based on the `fields` prop and the state will be handled internally. Use the `fields` prop as an array of objects with the following properties: - `name?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `type?: 'text' | 'password' | 'email' | 'number' | 'checkbox' | 'select' | 'otp'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} Each field must include a `type` property, which determines the input component and any additional props applied: `checkbox` fields use [Checkbox](https://ui.nuxt.com/docs/components/checkbox#props) props, `select` fields use [SelectMenu](https://ui.nuxt.com/docs/components/select-menu#props) props, `otp` fields use [PinInput](https://ui.nuxt.com/docs/components/pin-input#props) props, and all other types use [Input](https://ui.nuxt.com/docs/components/input#props) props. You can also pass any property from the [FormField](https://ui.nuxt.com/docs/components/form-field#props) component to each field. ```vue ``` ### Title Use the `title` prop to set the title of the Form. ```vue ``` ### Description Use the `description` prop to set the description of the Form. ```vue ``` ### Icon Use the `icon` prop to set the icon of the Form. ```vue ``` ### Providers Use the `providers` prop to add providers to the form. You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component such as `variant`, `color`, `to`, etc. ```vue ``` ### Separator Use the `separator` prop to customize the [Separator](https://ui.nuxt.com/docs/components/separator) between the providers and the fields. Defaults to `or`. ```vue ``` You can pass any property from the [Separator](https://ui.nuxt.com/docs/components/separator#props) component to customize it. ```vue ``` ### Submit Use the `submit` prop to change the submit button of the Form. You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component such as `variant`, `color`, `to`, etc. ```vue ``` ## Examples ### Within a page You can wrap the `AuthForm` component with the [PageCard](https://ui.nuxt.com/docs/components/page-card) component to display it within a `login.vue` page for example. ```vue [AuthFormPageExample.vue] ``` ## API ### Props ```ts /** * Props for the AuthForm component */ interface AuthFormProps { /** * The element or component this component should render as. */ as?: any; /** * The icon displayed above the title. */ icon?: string | object | undefined; title?: string | undefined; description?: string | undefined; fields?: (AuthFormInputField<"number"> | AuthFormCheckboxField | AuthFormSelectField | AuthFormOtpField | AuthFormInputField<"password"> | AuthFormInputField<"text"> | AuthFormInputField<"email">)[] | undefined; /** * Display a list of Button under the description. * `{ color: 'neutral', variant: 'subtle', block: true }`{lang="ts-type"} */ providers?: ButtonProps[] | undefined; /** * The text displayed in the separator. * @default "\"or\"" */ separator?: string | SeparatorProps | undefined; /** * Display a submit button at the bottom of the form. * `{ label: 'Continue', block: true }`{lang="ts-type"} */ submit?: ButtonProps | undefined; schema?: FormSchema | undefined; validate?: ((state: Partial) => FormError[] | Promise[]>) | undefined; validateOn?: FormInputEvents[] | undefined; validateOnInputDelay?: number | undefined; disabled?: boolean | undefined; loading?: boolean | undefined; loadingAuto?: boolean | undefined; ui?: { root?: ClassNameValue; header?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; body?: ClassNameValue; providers?: ClassNameValue; checkbox?: ClassNameValue; select?: ClassNameValue; password?: ClassNameValue; otp?: ClassNameValue; input?: ClassNameValue; separator?: ClassNameValue; form?: ClassNameValue; footer?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the AuthForm component */ interface AuthFormSlots { header(): any; leading(): any; title(): any; description(): any; providers(): any; validation(): any; submit(): any; footer(): any; } ``` ### Emits ```ts /** * Emitted events for the AuthForm component */ interface AuthFormEmits { submit: (payload: [payload: FormSubmitEvent]) => void; } ``` ### Expose You can access the typed component instance (exposing formRef and state) using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref){rel="nofollow"}. For example, in a separate form (e.g. a "reset" form) you can do: ```vue ``` This gives you access to the following (exposed) properties: | Name | Type | | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `formRef`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | | `state`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Reactive`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { authForm: { slots: { root: 'w-full space-y-6', header: 'flex flex-col text-center', leading: 'mb-2', leadingIcon: 'size-8 shrink-0 inline-block', title: 'text-xl text-pretty font-semibold text-highlighted', description: 'mt-1 text-base text-pretty text-muted', body: 'gap-y-6 flex flex-col', providers: 'space-y-3', checkbox: '', select: 'w-full', password: 'w-full', otp: 'w-full', input: 'w-full', separator: '', form: 'space-y-5', footer: 'text-sm text-center text-muted mt-2' } } } }) ``` ## Changelog ::component-changelog :: # Avatar ## Usage The Avatar uses the `` component when [`@nuxt/image`](https://github.com/nuxt/image){rel="nofollow"} is installed, falling back to `img` otherwise. ```vue ``` ::note You can pass any property from the HTML `` element such as `alt`, `loading`, etc. :: ### Src Use the `src` prop to set the image URL. ```vue ``` ### Size Use the `size` prop to set the size of the Avatar. ```vue ``` ::note The `` element's `width` and `height` are automatically set based on the `size` prop. :: ### Icon Use the `icon` prop to display a fallback [Icon](https://ui.nuxt.com/docs/components/icon). ```vue ``` ### Text Use the `text` prop to display a fallback text. ```vue ``` ### Alt When no icon or text is provided, the **initials** of the `alt` prop is used as fallback. ```vue ``` ::note The `alt` prop is passed to the `img` element as the `alt` attribute. :: ### Chip Use the `chip` prop to display a chip around the Avatar. ```vue ``` ## Examples ### With tooltip You can use a [Tooltip](https://ui.nuxt.com/docs/components/tooltip) component to display a tooltip when hovering the Avatar. ```vue [AvatarTooltipExample.vue] ``` ### With mask You can use a CSS mask to display an Avatar with a custom shape instead of a simple circle. ```vue [AvatarMaskExample.vue] ``` ## API ### Props ```ts /** * Props for the Avatar component */ interface AvatarProps { /** * The element or component this component should render as. */ as?: any; src?: string | undefined; alt?: string | undefined; icon?: string | object | undefined; text?: string | undefined; size?: "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "3xs" | "2xl" | "3xl" | undefined; chip?: boolean | ChipProps | undefined; ui?: { root?: ClassNameValue; image?: ClassNameValue; fallback?: ClassNameValue; icon?: ClassNameValue; } | undefined; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { avatar: { slots: { root: 'inline-flex items-center justify-center shrink-0 select-none rounded-full align-middle bg-elevated', image: 'h-full w-full rounded-[inherit] object-cover', fallback: 'font-medium leading-none text-muted truncate', icon: 'text-muted shrink-0' }, variants: { size: { '3xs': { root: 'size-4 text-[8px]' }, '2xs': { root: 'size-5 text-[10px]' }, xs: { root: 'size-6 text-xs' }, sm: { root: 'size-7 text-sm' }, md: { root: 'size-8 text-base' }, lg: { root: 'size-9 text-lg' }, xl: { root: 'size-10 text-xl' }, '2xl': { root: 'size-11 text-[22px]' }, '3xl': { root: 'size-12 text-2xl' } } }, defaultVariants: { size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # AvatarGroup ## Usage Wrap multiple [Avatar](https://ui.nuxt.com/docs/components/avatar) within an AvatarGroup to stack them. ```vue ``` ### Size Use the `size` prop to change the size of all the avatars. ```vue ``` ### Max Use the `max` prop to limit the number of avatars displayed. The rest is displayed as an `+X` avatar. ```vue ``` ## Examples ### With tooltip Wrap each avatar with a [Tooltip](https://ui.nuxt.com/docs/components/tooltip) to display a tooltip on hover. ```vue [AvatarGroupTooltipExample.vue] ``` ### With chip Wrap each avatar with a [Chip](https://ui.nuxt.com/docs/components/chip) to display a chip around the avatar. ```vue [AvatarGroupChipExample.vue] ``` ### With link Wrap each avatar with a [Link](https://ui.nuxt.com/docs/components/link) to make them clickable. ```vue [AvatarGroupLinkExample.vue] ``` ### With mask Wrap an avatar with a CSS mask to display it with a custom shape. ```vue [AvatarGroupMaskExample.vue] ``` ::warning The `chip` prop does not work correctly when using a mask. Chips may be cut depending on the mask shape. :: ## API ### Props ```ts /** * Props for the AvatarGroup component */ interface AvatarGroupProps { /** * The element or component this component should render as. */ as?: any; size?: "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "3xs" | "2xl" | "3xl" | undefined; /** * The maximum number of avatars to display. */ max?: string | number | undefined; ui?: { root?: ClassNameValue; base?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the AvatarGroup component */ interface AvatarGroupSlots { default(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { avatarGroup: { slots: { root: 'inline-flex flex-row-reverse justify-end', base: 'relative rounded-full ring-bg first:me-0' }, variants: { size: { '3xs': { base: 'ring -me-0.5' }, '2xs': { base: 'ring -me-0.5' }, xs: { base: 'ring -me-0.5' }, sm: { base: 'ring-2 -me-1.5' }, md: { base: 'ring-2 -me-1.5' }, lg: { base: 'ring-2 -me-1.5' }, xl: { base: 'ring-3 -me-2' }, '2xl': { base: 'ring-3 -me-2' }, '3xl': { base: 'ring-3 -me-2' } } }, defaultVariants: { size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # Badge ## Usage Use the default slot to set the label of the Badge. ```vue ``` ### Label Use the `label` prop to set the label of the Badge. ```vue ``` ### Color Use the `color` prop to change the color of the Badge. ```vue ``` ### Variant Use the `variant` props to change the variant of the Badge. ```vue ``` ### Size Use the `size` prop to change the size of the Badge. ```vue ``` ### Icon Use the `icon` prop to show an [Icon](https://ui.nuxt.com/docs/components/icon) inside the Badge. ```vue ``` Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position. ```vue ``` ### Avatar Use the `avatar` prop to show an [Avatar](https://ui.nuxt.com/docs/components/avatar) inside the Badge. ```vue ``` ## Examples ### `class` prop Use the `class` prop to override the base styles of the Badge. ```vue ``` ## API ### Props ```ts /** * Props for the Badge component */ interface BadgeProps { /** * The element or component this component should render as. * @default "\"span\"" */ as?: any; label?: string | number | undefined; color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; variant?: "solid" | "outline" | "soft" | "subtle" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; /** * Render the badge with equal padding on all sides. */ square?: boolean | undefined; ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined; /** * Display an icon based on the `leading` and `trailing` props. */ icon?: string | object | undefined; /** * Display an avatar on the left side. */ avatar?: AvatarProps | undefined; /** * When `true`, the icon will be displayed on the left side. */ leading?: boolean | undefined; /** * Display an icon on the left side. */ leadingIcon?: string | object | undefined; /** * When `true`, the icon will be displayed on the right side. */ trailing?: boolean | undefined; /** * Display an icon on the right side. */ trailingIcon?: string | object | undefined; } ``` ### Slots ```ts /** * Slots for the Badge component */ interface BadgeSlots { leading(): any; default(): any; trailing(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { badge: { slots: { base: 'font-medium inline-flex items-center', label: 'truncate', leadingIcon: 'shrink-0', leadingAvatar: 'shrink-0', leadingAvatarSize: '', trailingIcon: 'shrink-0' }, variants: { fieldGroup: { 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: '' }, size: { xs: { base: 'text-[8px]/3 px-1 py-0.5 gap-1 rounded-sm', leadingIcon: 'size-3', leadingAvatarSize: '3xs', trailingIcon: 'size-3' }, sm: { base: 'text-[10px]/3 px-1.5 py-1 gap-1 rounded-sm', leadingIcon: 'size-3', leadingAvatarSize: '3xs', trailingIcon: 'size-3' }, md: { base: 'text-xs px-2 py-1 gap-1 rounded-md', leadingIcon: 'size-4', leadingAvatarSize: '3xs', trailingIcon: 'size-4' }, lg: { base: 'text-sm px-2 py-1 gap-1.5 rounded-md', leadingIcon: 'size-5', leadingAvatarSize: '2xs', trailingIcon: 'size-5' }, xl: { base: 'text-base px-2.5 py-1 gap-1.5 rounded-md', leadingIcon: 'size-6', leadingAvatarSize: '2xs', trailingIcon: 'size-6' } }, square: { true: '' } }, compoundVariants: [ { color: 'primary', variant: 'solid', class: 'bg-primary text-inverted' }, { color: 'secondary', variant: 'solid', class: 'bg-secondary text-inverted' }, { color: 'success', variant: 'solid', class: 'bg-success text-inverted' }, { color: 'info', variant: 'solid', class: 'bg-info text-inverted' }, { color: 'warning', variant: 'solid', class: 'bg-warning text-inverted' }, { color: 'error', variant: 'solid', class: 'bg-error text-inverted' }, { color: 'primary', variant: 'outline', class: 'text-primary ring ring-inset ring-primary/50' }, { color: 'secondary', variant: 'outline', class: 'text-secondary ring ring-inset ring-secondary/50' }, { color: 'success', variant: 'outline', class: 'text-success ring ring-inset ring-success/50' }, { color: 'info', variant: 'outline', class: 'text-info ring ring-inset ring-info/50' }, { color: 'warning', variant: 'outline', class: 'text-warning ring ring-inset ring-warning/50' }, { color: 'error', variant: 'outline', class: 'text-error ring ring-inset ring-error/50' }, { color: 'primary', variant: 'soft', class: 'bg-primary/10 text-primary' }, { color: 'secondary', variant: 'soft', class: 'bg-secondary/10 text-secondary' }, { color: 'success', variant: 'soft', class: 'bg-success/10 text-success' }, { color: 'info', variant: 'soft', class: 'bg-info/10 text-info' }, { color: 'warning', variant: 'soft', class: 'bg-warning/10 text-warning' }, { color: 'error', variant: 'soft', class: 'bg-error/10 text-error' }, { color: 'primary', variant: 'subtle', class: 'bg-primary/10 text-primary ring ring-inset ring-primary/25' }, { color: 'secondary', variant: 'subtle', class: 'bg-secondary/10 text-secondary ring ring-inset ring-secondary/25' }, { color: 'success', variant: 'subtle', class: 'bg-success/10 text-success ring ring-inset ring-success/25' }, { color: 'info', variant: 'subtle', class: 'bg-info/10 text-info ring ring-inset ring-info/25' }, { color: 'warning', variant: 'subtle', class: 'bg-warning/10 text-warning ring ring-inset ring-warning/25' }, { color: 'error', variant: 'subtle', class: 'bg-error/10 text-error ring ring-inset ring-error/25' }, { color: 'neutral', variant: 'solid', class: 'text-inverted bg-inverted' }, { color: 'neutral', variant: 'outline', class: 'ring ring-inset ring-accented text-default bg-default' }, { color: 'neutral', variant: 'soft', class: 'text-default bg-elevated' }, { color: 'neutral', variant: 'subtle', class: 'ring ring-inset ring-accented text-default bg-elevated' }, { size: 'xs', square: true, class: 'p-0.5' }, { size: 'sm', square: true, class: 'p-1' }, { size: 'md', square: true, class: 'p-1' }, { size: 'lg', square: true, class: 'p-1' }, { size: 'xl', square: true, class: 'p-1' } ], defaultVariants: { color: 'primary', variant: 'solid', size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # Banner ## Usage ### Title Use the `title` prop to display a title on the Banner. ```vue ``` ### Icon Use the `icon` prop to display an icon on the Banner. ```vue ``` ### Color Use the `color` prop to change the color of the Banner. ```vue ``` ### Close Use the `close` prop to display a [Button](https://ui.nuxt.com/docs/components/button) to dismiss the Banner. Defaults to `false`. ::tip A `close` event will be emitted when the close button is clicked. :: ```vue [BannerExample.vue] ``` ::note When closed, `banner-${id}` will be stored in the local storage to prevent it from being displayed again. :br For the example above, `banner-example` will be stored in the local storage. :: ### Close Icon Use the `close-icon` prop to customize the close button [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-x`. ```vue [BannerExample.vue] ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key. ::: :: ### Actions Use the `actions` prop to add some [Button](https://ui.nuxt.com/docs/components/button) actions to the Banner. ```vue ``` ::note The action buttons default to `color="neutral"` and `size="xs"`. You can customize these values by passing them directly to each action button. :: ### Link You can pass any property from the [``](https://nuxt.com/docs/api/components/nuxt-link){rel="nofollow"} component such as `to`, `target`, `rel`, etc. ```vue ``` ::note The `NuxtLink` component will inherit all other attributes you pass to the `User` component. :: ## Examples ### Within `app.vue` Use the Banner component in your `app.vue` or in a layout: ```vue [app.vue] {3} ``` ## API ### Props ```ts /** * Props for the Banner component */ interface BannerProps { /** * The element or component this component should render as. */ as?: any; /** * A unique id saved to local storage to remember if the banner has been dismissed. * Change this value to show the banner again. */ id?: string | undefined; /** * The icon displayed next to the title. */ icon?: string | object | undefined; title?: string | undefined; /** * Display a list of actions next to the title. * `{ color: 'neutral', size: 'xs' }`{lang="ts-type"} */ actions?: ButtonProps[] | undefined; to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined; target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined; color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; /** * Display a close button to dismiss the banner. * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ close?: boolean | Partial | undefined; /** * The icon displayed in the close button. */ closeIcon?: string | object | undefined; ui?: { root?: ClassNameValue; container?: ClassNameValue; left?: ClassNameValue; center?: ClassNameValue; right?: ClassNameValue; icon?: ClassNameValue; title?: ClassNameValue; actions?: ClassNameValue; close?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the Banner component */ interface BannerSlots { leading(): any; title(): any; actions(): any; close(): any; } ``` ### Emits ```ts /** * Emitted events for the Banner component */ interface BannerEmits { close: (payload: []) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { banner: { slots: { root: [ 'relative z-50 w-full', 'transition-colors' ], container: 'flex items-center justify-between gap-3 h-12', left: 'hidden lg:flex-1 lg:flex lg:items-center', center: 'flex items-center gap-1.5 min-w-0', right: 'lg:flex-1 flex items-center justify-end', icon: 'size-5 shrink-0 text-inverted pointer-events-none', title: 'text-sm text-inverted font-medium truncate', actions: 'flex gap-1.5 shrink-0 isolate', close: 'text-inverted hover:bg-default/10 focus-visible:bg-default/10 -me-1.5 lg:me-0' }, variants: { color: { primary: { root: 'bg-primary' }, secondary: { root: 'bg-secondary' }, success: { root: 'bg-success' }, info: { root: 'bg-info' }, warning: { root: 'bg-warning' }, error: { root: 'bg-error' }, neutral: { root: 'bg-inverted' } }, to: { true: '' } }, compoundVariants: [ { color: 'primary', to: true, class: { root: 'hover:bg-primary/90' } }, { color: 'secondary', to: true, class: { root: 'hover:bg-secondary/90' } }, { color: 'success', to: true, class: { root: 'hover:bg-success/90' } }, { color: 'info', to: true, class: { root: 'hover:bg-info/90' } }, { color: 'warning', to: true, class: { root: 'hover:bg-warning/90' } }, { color: 'error', to: true, class: { root: 'hover:bg-error/90' } }, { color: 'neutral', to: true, class: { root: 'hover:bg-inverted/90' } } ], defaultVariants: { color: 'primary' } } } }) ``` ## Changelog ::component-changelog :: # BlogPost ## Usage The BlogPost component provides a flexible way to display an `
` element with customizable content including title, description, image, etc. ::code-preview :::u-blog-post --- authors: - name: Anthony Fu description: antfu7 avatar: src: https://github.com/antfu.png to: https://github.com/antfu target: _blank class: w-96 date: 2024-11-25 description: Discover Nuxt Icon v1 - a modern, versatile, and customizable icon solution for your Nuxt projects. image: https://nuxt.com/assets/blog/nuxt-icon/cover.png target: _blank title: Introducing Nuxt Icon v1 to: https://nuxt.com/blog/nuxt-icon-v1-0 --- ::: :: ::tip{to="https://ui.nuxt.com/docs/components/blog-posts"} Use the [`BlogPosts`](https://ui.nuxt.com/docs/components/blog-posts) component to display multiple blog posts in a responsive grid layout. :: ### Title Use the `title` prop to display the title of the BlogPost. ```vue ``` ### Description Use the `description` prop to display the description of the BlogPost. ```vue ``` ### Date Use the `date` prop to display the date of the BlogPost. ::tip The date is automatically formatted to the [current locale](https://ui.nuxt.com/docs/getting-started/integrations/i18n/nuxt#locale). You can either pass a `Date` object or a string. :: ```vue ``` ### Badge Use the `badge` prop to display a [Badge](https://ui.nuxt.com/docs/components/badge) in the BlogPost. ```vue ``` You can pass any property from the [Badge](https://ui.nuxt.com/docs/components/badge#props) component to customize it. ```vue ``` ### Image Use the `image` prop to display an image in the BlogPost. ::note If [`@nuxt/image`](https://image.nuxt.com/get-started/installation){rel="nofollow"} is installed, the `` component will be used instead of the native `img` tag. :: ```vue ``` ### Authors Use the `authors` prop to display a list of [User](https://ui.nuxt.com/docs/components/user) in the BlogPost as an array of objects with the following properties: - `name?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `description?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `avatar?: Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `chip?: boolean | Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `size?: UserProps['size']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `orientation?: UserProps['orientation']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` When the `authors` prop has more than one item, the [AvatarGroup](https://ui.nuxt.com/docs/components/avatar-group) component is used. ```vue ``` ### Link You can pass any property from the [``](https://nuxt.com/docs/api/components/nuxt-link){rel="nofollow"} component such as `to`, `target`, `rel`, etc. ```vue ``` ### Variant Use the `variant` prop to change the style of the BlogPost. ```vue ``` ::note The styling will be different wether you provide a `to` prop or an `image`. :: ### Orientation Use the `orientation` prop to change the BlogPost orientation. Defaults to `vertical`. ```vue ``` ## API ### Props ```ts /** * Props for the BlogPost component */ interface BlogPostProps { /** * The element or component this component should render as. * @default "\"article\"" */ as?: any; title?: string | undefined; description?: string | undefined; /** * The date of the blog post. Can be a string or a Date object. */ date?: string | Date | undefined; /** * Display a badge on the blog post. * Can be a string or an object. * `{ color: 'neutral', variant: 'subtle' }`{lang="ts-type"} */ badge?: string | BadgeProps | undefined; /** * The authors of the blog post. */ authors?: UserProps[] | undefined; /** * The image of the blog post. Can be a string or an object. */ image?: string | (Partial & { [key: string]: any; }) | undefined; /** * The orientation of the blog post. * @default "\"vertical\"" */ orientation?: "vertical" | "horizontal" | undefined; variant?: "outline" | "soft" | "subtle" | "ghost" | "naked" | undefined; to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined; target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined; onClick?: ((event: MouseEvent) => void | Promise) | undefined; ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; image?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; authors?: ClassNameValue; avatar?: ClassNameValue; meta?: ClassNameValue; date?: ClassNameValue; badge?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the BlogPost component */ interface BlogPostSlots { date(): any; badge(): any; title(): any; description(): any; authors(): any; header(): any; body(): any; footer(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { blogPost: { slots: { root: 'relative group/blog-post flex flex-col rounded-lg overflow-hidden', header: 'relative overflow-hidden aspect-[16/9] w-full pointer-events-none', body: 'min-w-0 flex-1 flex flex-col', footer: '', image: 'object-cover object-top w-full h-full', title: 'text-xl text-pretty font-semibold text-highlighted', description: 'mt-1 text-base text-pretty', authors: 'pt-4 mt-auto flex flex-wrap gap-x-3 gap-y-1.5', avatar: '', meta: 'flex items-center gap-2 mb-2', date: 'text-sm', badge: '' }, variants: { orientation: { horizontal: { root: 'lg:grid lg:grid-cols-2 lg:items-center gap-x-8', body: 'justify-center p-4 sm:p-6 lg:px-0' }, vertical: { root: 'flex flex-col', body: 'p-4 sm:p-6' } }, variant: { outline: { root: 'bg-default ring ring-default', date: 'text-toned', description: 'text-muted' }, soft: { root: 'bg-elevated/50', date: 'text-muted', description: 'text-toned' }, subtle: { root: 'bg-elevated/50 ring ring-default', date: 'text-muted', description: 'text-toned' }, ghost: { date: 'text-toned', description: 'text-muted', header: 'shadow-lg rounded-lg' }, naked: { root: 'p-0 sm:p-0', date: 'text-toned', description: 'text-muted', header: 'shadow-lg rounded-lg' } }, to: { true: { root: [ 'transition' ], image: 'transform transition-transform duration-200 group-hover/blog-post:scale-110', avatar: 'transform transition-transform duration-200 hover:scale-115' } }, image: { true: '' } }, compoundVariants: [ { variant: 'outline', to: true, class: { root: 'hover:bg-elevated/50' } }, { variant: 'soft', to: true, class: { root: 'hover:bg-elevated' } }, { variant: 'subtle', to: true, class: { root: 'hover:bg-elevated hover:ring-accented' } }, { variant: 'ghost', to: true, class: { root: 'hover:bg-elevated/50', header: [ 'group-hover/blog-post:shadow-none', 'transition-all' ] } }, { variant: 'ghost', to: true, orientation: 'vertical', class: { header: 'group-hover/blog-post:rounded-b-none' } }, { variant: 'ghost', to: true, orientation: 'horizontal', class: { header: 'group-hover/blog-post:rounded-r-none' } }, { orientation: 'vertical', image: false, variant: 'naked', class: { body: 'p-0 sm:p-0' } } ], defaultVariants: { variant: 'outline' } } } }) ``` ## Changelog ::component-changelog :: # BlogPosts ## Usage The BlogPosts component provides a flexible layout to display a list of [BlogPost](https://ui.nuxt.com/docs/components/blog-post) components using either the default slot or the `posts` prop. ```vue {2,8} ``` ### Posts Use the `posts` prop as an array of objects with the properties of the [BlogPost](https://ui.nuxt.com/docs/components/blog-post#props) component. ```vue ``` ### Orientation Use the `orientation` prop to change the orientation of the BlogPosts. Defaults to `horizontal`. ```vue ``` ::tip When using the `posts` prop instead of the default slot, the `orientation` of the posts is automatically reversed, `horizontal` to `vertical` and vice versa. :: ## Examples ::note While these examples use [Nuxt Content](https://content.nuxt.com){rel="nofollow"}, the components can be integrated with any content management system. :: ### Within a page Use the BlogPosts component in a page to create a blog page: ```vue [pages/blog/index.vue] {11-18} ``` ::note In this example, the `posts` are fetched using `queryCollection` from the `@nuxt/content` module. :: ::tip The `to` prop is overridden here since `@nuxt/content` uses the `path` property. :: ## API ### Props ```ts /** * Props for the BlogPosts component */ interface BlogPostsProps { /** * The element or component this component should render as. */ as?: any; posts?: BlogPostProps[] | undefined; /** * The orientation of the blog posts. * @default "\"horizontal\"" */ orientation?: "horizontal" | "vertical" | undefined; } ``` ### Slots ```ts /** * Slots for the BlogPosts component */ interface BlogPostsSlots { date(): any; badge(): any; title(): any; description(): any; authors(): any; header(): any; body(): any; footer(): any; default(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { blogPosts: { base: 'flex flex-col gap-8 lg:gap-y-16', variants: { orientation: { horizontal: 'sm:grid sm:grid-cols-2 lg:grid-cols-3', vertical: '' } } } } }) ``` ## Changelog ::component-changelog :: # Breadcrumb ## Usage Use the Breadcrumb component to show the current page's location in your site's hierarchy. ```vue ``` ### Items Use the `items` prop as an array of objects with the following properties: - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `avatar?: AvatarProps`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot) - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue, link?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLeadingAvatar?: ClassNameValue, linkLabel?: ClassNameValue, separator?: ClassNameValue, separatorIcon?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` ::note A `span` is rendered instead of a link when the `to` property is not defined. :: ### Separator Icon Use the `separator-icon` prop to customize the [Icon](https://ui.nuxt.com/docs/components/icon) between each item. Defaults to `i-lucide-chevron-right`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronRight` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronRight` key. ::: :: ## Examples ### With separator slot Use the `#separator` slot to customize the separator between each item. ```vue [BreadcrumbSeparatorSlotExample.vue] ``` ### With custom slot Use the `slot` property to customize a specific item. You will have access to the following slots: - `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-leading`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-label`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-trailing`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue [BreadcrumbCustomSlotExample.vue] ``` ::tip{to="https://ui.nuxt.com/#slots"} You can also use the `#item`, `#item-leading`, `#item-label` and `#item-trailing` slots to customize all items. :: ## API ### Props ```ts /** * Props for the Breadcrumb component */ interface BreadcrumbProps { /** * The element or component this component should render as. * @default "\"nav\"" */ as?: any; items?: BreadcrumbItem[] | undefined; /** * The icon to use as a separator. */ separatorIcon?: string | object | undefined; /** * The key used to get the label from the item. * @default "\"label\"" */ labelKey?: GetItemKeys | undefined; ui?: { root?: ClassNameValue; list?: ClassNameValue; item?: ClassNameValue; link?: ClassNameValue; linkLeadingIcon?: ClassNameValue; linkLeadingAvatar?: ClassNameValue; linkLeadingAvatarSize?: ClassNameValue; linkLabel?: ClassNameValue; separator?: ClassNameValue; separatorIcon?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the Breadcrumb component */ interface BreadcrumbSlots { item(): any; item-leading(): any; item-label(): any; item-trailing(): any; separator(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { breadcrumb: { slots: { root: 'relative min-w-0', list: 'flex items-center gap-1.5', item: 'flex min-w-0', link: 'group relative flex items-center gap-1.5 text-sm min-w-0 focus-visible:outline-primary', linkLeadingIcon: 'shrink-0 size-5', linkLeadingAvatar: 'shrink-0', linkLeadingAvatarSize: '2xs', linkLabel: 'truncate', separator: 'flex', separatorIcon: 'shrink-0 size-5 text-muted' }, variants: { active: { true: { link: 'text-primary font-semibold' }, false: { link: 'text-muted font-medium' } }, disabled: { true: { link: 'cursor-not-allowed opacity-75' } }, to: { true: '' } }, compoundVariants: [ { disabled: false, active: false, to: true, class: { link: [ 'hover:text-default', 'transition-colors' ] } } ] } } }) ``` ## Changelog ::component-changelog :: # Button ## Usage Use the default slot to set the label of the Button. ```vue ``` ### Label Use the `label` prop to set the label of the Button. ```vue ``` ### Color Use the `color` prop to change the color of the Button. ```vue ``` ### Variant Use the `variant` prop to change the variant of the Button. ```vue ``` ### Size Use the `size` prop to change the size of the Button. ```vue ``` ### Icon Use the `icon` prop to show an [Icon](https://ui.nuxt.com/docs/components/icon) inside the Button. ```vue ``` Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position. ```vue ``` The `label` as prop or slot is optional so you can use the Button as an icon-only button. ```vue ``` ### Avatar Use the `avatar` prop to show an [Avatar](https://ui.nuxt.com/docs/components/avatar) inside the Button. ```vue ``` The `label` as prop or slot is optional so you can use the Button as an avatar-only button. ```vue ``` ### Link You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` When the Button is a link or when using the `active` prop, you can use the `active-color` and `active-variant` props to customize the active state. ```vue ``` You can also use the `active-class` and `inactive-class` props to customize the active state. ```vue ``` ::tip You can configure these styles globally in your `app.config.ts` file under the `ui.button.variants.active` key. ```ts export default defineAppConfig({ ui: { button: { variants: { active: { true: { base: 'font-bold' } } } } } }) ``` :: ### Loading Use the `loading` prop to show a loading icon and disable the Button. ```vue ``` Use the `loading-auto` prop to show the loading icon automatically while the `@click` promise is pending. ```vue [ButtonLoadingAutoExample.vue] ``` This also works with the [Form](https://ui.nuxt.com/docs/components/form) component. ```vue [ButtonLoadingAutoFormExample.vue] ``` ### Loading Icon Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key. ::: :: ### Disabled Use the `disabled` prop to disable the Button. ```vue ``` ## Examples ### `class` prop Use the `class` prop to override the base styles of the Button. ```vue ``` ### `ui` prop Use the `ui` prop to override the slots styles of the Button. ```vue ``` ## API ### Props ```ts /** * Props for the Button component */ interface ButtonProps { label?: string | undefined; color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; activeColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; variant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; activeVariant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; /** * Render the button with equal padding on all sides. */ square?: boolean | undefined; /** * Render the button full width. */ block?: boolean | undefined; /** * Set loading state automatically based on the `@click` promise state */ loadingAuto?: boolean | undefined; onClick?: ((event: MouseEvent) => void | Promise) | ((event: MouseEvent) => void | Promise)[] | undefined; ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined; /** * Display an icon based on the `leading` and `trailing` props. */ icon?: string | object | undefined; /** * Display an avatar on the left side. */ avatar?: AvatarProps | undefined; /** * When `true`, the icon will be displayed on the left side. */ leading?: boolean | undefined; /** * Display an icon on the left side. */ leadingIcon?: string | object | undefined; /** * When `true`, the icon will be displayed on the right side. */ trailing?: boolean | undefined; /** * Display an icon on the right side. */ trailingIcon?: string | object | undefined; /** * When `true`, the loading icon will be displayed. */ loading?: boolean | undefined; /** * The icon when the `loading` prop is `true`. */ loadingIcon?: string | object | undefined; /** * Route Location the link should navigate to when clicked on. */ to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined; /** * Class to apply when the link is active */ activeClass?: string | undefined; /** * Class to apply when the link is exact active */ exactActiveClass?: string | undefined; /** * Value passed to the attribute `aria-current` when the link is exact active. */ ariaCurrentValue?: "page" | "step" | "location" | "date" | "time" | "true" | "false" | undefined; /** * Pass the returned promise of `router.push()` to `document.startViewTransition()` if supported. */ viewTransition?: boolean | undefined; /** * Calls `router.replace` instead of `router.push`. */ replace?: boolean | undefined; /** * The element or component this component should render as when not a link. */ as?: any; /** * The type of the button when not a link. */ type?: "reset" | "submit" | "button" | undefined; disabled?: boolean | undefined; /** * Force the link to be active independent of the current route. */ active?: boolean | undefined; /** * Will only be active if the current route is an exact match. */ exact?: boolean | undefined; /** * Allows controlling how the current route query sets the link as active. */ exactQuery?: boolean | "partial" | undefined; /** * Will only be active if the current route hash is an exact match. */ exactHash?: boolean | undefined; /** * The class to apply when the link is inactive. */ inactiveClass?: string | undefined; /** * An alias for `to`. If used with `to`, `href` will be ignored */ href?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined; /** * Forces the link to be considered as external (true) or internal (false). This is helpful to handle edge-cases */ external?: boolean | undefined; /** * Where to display the linked URL, as the name for a browsing context. */ target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined; /** * A rel attribute value to apply on the link. Defaults to "noopener noreferrer" for external links. */ rel?: (string & {}) | "noopener" | "noreferrer" | "nofollow" | "sponsored" | "ugc" | null | undefined; /** * If set to true, no rel attribute will be added to the link */ noRel?: boolean | undefined; /** * A class to apply to links that have been prefetched. */ prefetchedClass?: string | undefined; /** * When enabled will prefetch middleware, layouts and payloads of links in the viewport. */ prefetch?: boolean | undefined; /** * Allows controlling when to prefetch links. By default, prefetch is triggered only on visibility. */ prefetchOn?: "visibility" | "interaction" | Partial<{ visibility: boolean; interaction: boolean; }> | undefined; /** * Escape hatch to disable `prefetch` attribute. */ noPrefetch?: boolean | undefined; } ``` ::callout --- icon: i-simple-icons-github to: https://github.com/nuxt/ui/blob/v4/src/runtime/components/Link.vue#L13 --- The `Button` component extends the `Link` component. Check out the source code on GitHub. :: ### Slots ```ts /** * Slots for the Button component */ interface ButtonSlots { leading(): any; default(): any; trailing(): any; } ``` ## Theme ```ts [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: { fieldGroup: { 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: 'secondary', variant: 'solid', class: 'text-inverted bg-secondary hover:bg-secondary/75 active:bg-secondary/75 disabled:bg-secondary aria-disabled:bg-secondary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary' }, { color: 'success', variant: 'solid', class: 'text-inverted bg-success hover:bg-success/75 active:bg-success/75 disabled:bg-success aria-disabled:bg-success focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success' }, { color: 'info', variant: 'solid', class: 'text-inverted bg-info hover:bg-info/75 active:bg-info/75 disabled:bg-info aria-disabled:bg-info focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info' }, { color: 'warning', variant: 'solid', class: 'text-inverted bg-warning hover:bg-warning/75 active:bg-warning/75 disabled:bg-warning aria-disabled:bg-warning focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning' }, { color: 'error', variant: 'solid', class: 'text-inverted bg-error hover:bg-error/75 active:bg-error/75 disabled:bg-error aria-disabled:bg-error focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-error' }, { 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: 'secondary', variant: 'outline', class: 'ring ring-inset ring-secondary/50 text-secondary hover:bg-secondary/10 active:bg-secondary/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-secondary' }, { color: 'success', variant: 'outline', class: 'ring ring-inset ring-success/50 text-success hover:bg-success/10 active:bg-success/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-success' }, { color: 'info', variant: 'outline', class: 'ring ring-inset ring-info/50 text-info hover:bg-info/10 active:bg-info/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-info' }, { color: 'warning', variant: 'outline', class: 'ring ring-inset ring-warning/50 text-warning hover:bg-warning/10 active:bg-warning/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-warning' }, { color: 'error', variant: 'outline', class: 'ring ring-inset ring-error/50 text-error hover:bg-error/10 active:bg-error/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-error' }, { 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: 'secondary', variant: 'soft', class: 'text-secondary bg-secondary/10 hover:bg-secondary/15 active:bg-secondary/15 focus:outline-none focus-visible:bg-secondary/15 disabled:bg-secondary/10 aria-disabled:bg-secondary/10' }, { color: 'success', variant: 'soft', class: 'text-success bg-success/10 hover:bg-success/15 active:bg-success/15 focus:outline-none focus-visible:bg-success/15 disabled:bg-success/10 aria-disabled:bg-success/10' }, { color: 'info', variant: 'soft', class: 'text-info bg-info/10 hover:bg-info/15 active:bg-info/15 focus:outline-none focus-visible:bg-info/15 disabled:bg-info/10 aria-disabled:bg-info/10' }, { color: 'warning', variant: 'soft', class: 'text-warning bg-warning/10 hover:bg-warning/15 active:bg-warning/15 focus:outline-none focus-visible:bg-warning/15 disabled:bg-warning/10 aria-disabled:bg-warning/10' }, { color: 'error', variant: 'soft', class: 'text-error bg-error/10 hover:bg-error/15 active:bg-error/15 focus:outline-none focus-visible:bg-error/15 disabled:bg-error/10 aria-disabled:bg-error/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: 'secondary', variant: 'subtle', class: 'text-secondary ring ring-inset ring-secondary/25 bg-secondary/10 hover:bg-secondary/15 active:bg-secondary/15 disabled:bg-secondary/10 aria-disabled:bg-secondary/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-secondary' }, { color: 'success', variant: 'subtle', class: 'text-success ring ring-inset ring-success/25 bg-success/10 hover:bg-success/15 active:bg-success/15 disabled:bg-success/10 aria-disabled:bg-success/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-success' }, { color: 'info', variant: 'subtle', class: 'text-info ring ring-inset ring-info/25 bg-info/10 hover:bg-info/15 active:bg-info/15 disabled:bg-info/10 aria-disabled:bg-info/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-info' }, { color: 'warning', variant: 'subtle', class: 'text-warning ring ring-inset ring-warning/25 bg-warning/10 hover:bg-warning/15 active:bg-warning/15 disabled:bg-warning/10 aria-disabled:bg-warning/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-warning' }, { color: 'error', variant: 'subtle', class: 'text-error ring ring-inset ring-error/25 bg-error/10 hover:bg-error/15 active:bg-error/15 disabled:bg-error/10 aria-disabled:bg-error/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-error' }, { 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: 'secondary', variant: 'ghost', class: 'text-secondary hover:bg-secondary/10 active:bg-secondary/10 focus:outline-none focus-visible:bg-secondary/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent' }, { color: 'success', variant: 'ghost', class: 'text-success hover:bg-success/10 active:bg-success/10 focus:outline-none focus-visible:bg-success/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent' }, { color: 'info', variant: 'ghost', class: 'text-info hover:bg-info/10 active:bg-info/10 focus:outline-none focus-visible:bg-info/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent' }, { color: 'warning', variant: 'ghost', class: 'text-warning hover:bg-warning/10 active:bg-warning/10 focus:outline-none focus-visible:bg-warning/10 disabled:bg-transparent aria-disabled:bg-transparent dark:disabled:bg-transparent dark:aria-disabled:bg-transparent' }, { color: 'error', variant: 'ghost', class: 'text-error hover:bg-error/10 active:bg-error/10 focus:outline-none focus-visible:bg-error/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: 'secondary', variant: 'link', class: 'text-secondary hover:text-secondary/75 active:text-secondary/75 disabled:text-secondary aria-disabled:text-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary' }, { color: 'success', variant: 'link', class: 'text-success hover:text-success/75 active:text-success/75 disabled:text-success aria-disabled:text-success focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success' }, { color: 'info', variant: 'link', class: 'text-info hover:text-info/75 active:text-info/75 disabled:text-info aria-disabled:text-info focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info' }, { color: 'warning', variant: 'link', class: 'text-warning hover:text-warning/75 active:text-warning/75 disabled:text-warning aria-disabled:text-warning focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning' }, { color: 'error', variant: 'link', class: 'text-error hover:text-error/75 active:text-error/75 disabled:text-error aria-disabled:text-error focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error' }, { 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' } } } }) ``` ## Changelog ::component-changelog :: # Calendar ## Usage Use the `v-model` directive to control the selected date. ```vue ``` Use the `default-value` prop to set the initial value when you do not need to control its state. ```vue ``` ::note This component relies on the [`@internationalized/date`](https://react-spectrum.adobe.com/internationalized/date/index.html){rel="nofollow"} package which provides objects and functions for representing and manipulating dates and times in a locale-aware manner. :: ### Multiple Use the `multiple` prop to allow multiple selections. ```vue ``` ### Range Use the `range` prop to select a range of dates. ```vue ``` ### Color Use the `color` prop to change the color of the calendar. ```vue ``` ### Variant Use the `variant` prop to change the variant of the calendar. ```vue ``` ### Size Use the `size` prop to change the size of the calendar. ```vue ``` ### Disabled Use the `disabled` prop to disable the calendar. ```vue ``` ### Number Of Months Use the `numberOfMonths` prop to change the number of months in the calendar. ```vue ``` ### Month Controls Use the `month-controls` prop to show the month controls. Defaults to `true`. ```vue ``` ### Year Controls Use the `year-controls` prop to show the year controls. Defaults to `true`. ```vue ``` ### Fixed Weeks Use the `fixed-weeks` prop to display the calendar with fixed weeks. ```vue ``` ## Examples ### With chip events Use the [Chip](https://ui.nuxt.com/docs/components/chip) component to add events to specific days. ```vue [CalendarEventsExample.vue] ``` ### With disabled dates Use the `is-date-disabled` prop with a function to mark specific dates as disabled. ```vue [CalendarDisabledDatesExample.vue] ``` ### With unavailable dates Use the `is-date-unavailable` prop with a function to mark specific dates as unavailable. ```vue [CalendarUnavailableDatesExample.vue] ``` ### With min/max dates Use the `min-value` and `max-value` props to limit the dates. ```vue [CalendarMinMaxDatesExample.vue] ``` ### With other calendar systems You can use other calenders from `@internationalized/date` to implement a different calendar system. ```vue [CalendarOtherSystemExample.vue] ``` ::note --- to: https://react-spectrum.adobe.com/internationalized/date/Calendar.html#implementations --- You can check all the available calendars on `@internationalized/date` docs. :: ### With external controls You can control the calendar with external controls by manipulating the date passed in the `v-model`. ```vue [CalendarExternalControlsExample.vue] ``` ### As a DatePicker Use a [Button](https://ui.nuxt.com/docs/components/button) and a [Popover](https://ui.nuxt.com/docs/components/popover) component to create a date picker. ```vue [CalendarDatePickerExample.vue] ``` ### As a DateRangePicker Use a [Button](https://ui.nuxt.com/docs/components/button) and a [Popover](https://ui.nuxt.com/docs/components/popover) component to create a date range picker. ```vue [CalendarDateRangePickerExample.vue] ``` ## API ### Props ```ts /** * Props for the Calendar component */ interface CalendarProps { /** * The element or component this component should render as. */ as?: any; /** * The icon to use for the next year control. */ nextYearIcon?: string | object | undefined; /** * Configure the next year button. * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ nextYear?: ButtonProps | undefined; /** * The icon to use for the next month control. */ nextMonthIcon?: string | object | undefined; /** * Configure the next month button. * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ nextMonth?: ButtonProps | undefined; /** * The icon to use for the previous year control. */ prevYearIcon?: string | object | undefined; /** * Configure the prev year button. * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ prevYear?: ButtonProps | undefined; /** * The icon to use for the previous month control. */ prevMonthIcon?: string | object | undefined; /** * Configure the prev month button. * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ prevMonth?: ButtonProps | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; variant?: "solid" | "outline" | "soft" | "subtle" | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; /** * Whether or not a range of dates can be selected */ range?: boolean | undefined; /** * Whether or not multiple dates can be selected */ multiple?: boolean | undefined; /** * Show month controls * @default "true" */ monthControls?: boolean | undefined; /** * Show year controls * @default "true" */ yearControls?: boolean | undefined; defaultValue?: DateValue | DateRange | DateValue[] | undefined; modelValue?: DateValue | DateRange | DateValue[] | null | undefined; ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; heading?: ClassNameValue; grid?: ClassNameValue; gridRow?: ClassNameValue; gridWeekDaysRow?: ClassNameValue; gridBody?: ClassNameValue; headCell?: ClassNameValue; cell?: ClassNameValue; cellTrigger?: ClassNameValue; } | undefined; /** * The default placeholder date */ defaultPlaceholder?: DateValue | undefined; /** * The placeholder date, which is used to determine what month to display when no date is selected. This updates as the user navigates the calendar and can be used to programmatically control the calendar view */ placeholder?: DateValue | undefined; /** * When combined with `isDateUnavailable`, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected. */ allowNonContiguousRanges?: boolean | undefined; /** * This property causes the previous and next buttons to navigate by the number of months displayed at once, rather than one month */ pagedNavigation?: boolean | undefined; /** * Whether or not to prevent the user from deselecting a date without selecting another date first */ preventDeselect?: boolean | undefined; /** * The maximum number of days that can be selected in a range */ maximumDays?: number | undefined; /** * The day of the week to start the calendar on */ weekStartsOn?: 0 | 1 | 2 | 4 | 5 | 3 | 6 | undefined; /** * The format to use for the weekday strings provided via the weekdays slot prop */ weekdayFormat?: WeekDayFormat | undefined; /** * Whether or not to always display 6 weeks in the calendar * @default "true" */ fixedWeeks?: boolean | undefined; /** * The maximum date that can be selected */ maxValue?: DateValue | undefined; /** * The minimum date that can be selected */ minValue?: DateValue | undefined; /** * The number of months to display at once */ numberOfMonths?: number | undefined; /** * Whether or not the calendar is disabled */ disabled?: boolean | undefined; /** * Whether or not the calendar is readonly */ readonly?: boolean | undefined; /** * If true, the calendar will focus the selected day, today, or the first day of the month depending on what is visible when the calendar is mounted */ initialFocus?: boolean | undefined; /** * A function that returns whether or not a date is disabled */ isDateDisabled?: Matcher | undefined; /** * A function that returns whether or not a date is unavailable */ isDateUnavailable?: Matcher | undefined; /** * A function that returns whether or not a date is hightable */ isDateHighlightable?: Matcher | undefined; /** * A function that returns the next page of the calendar. It receives the current placeholder as an argument inside the component. */ nextPage?: ((placeholder: DateValue) => DateValue) | undefined; /** * A function that returns the previous page of the calendar. It receives the current placeholder as an argument inside the component. */ prevPage?: ((placeholder: DateValue) => DateValue) | undefined; /** * Whether or not to disable days outside the current view. */ disableDaysOutsideCurrentView?: boolean | undefined; /** * Which part of the range should be fixed */ fixedDate?: "start" | "end" | undefined; } ``` ### Slots ```ts /** * Slots for the Calendar component */ interface CalendarSlots { heading(): any; day(): any; week-day(): any; } ``` ### Emits ```ts /** * Emitted events for the Calendar component */ interface CalendarEmits { update:modelValue: (payload: [date: DateValue | DateRange | DateValue[] | null | undefined]) => void; update:placeholder: (payload: [date: DateValue] & [date: DateValue]) => void; update:validModelValue: (payload: [date: DateRange]) => void; update:startValue: (payload: [date: DateValue | undefined]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { calendar: { slots: { root: '', header: 'flex items-center justify-between', body: 'flex flex-col space-y-4 pt-4 sm:flex-row sm:space-x-4 sm:space-y-0', heading: 'text-center font-medium truncate mx-auto', grid: 'w-full border-collapse select-none space-y-1 focus:outline-none', gridRow: 'grid grid-cols-7 place-items-center', gridWeekDaysRow: 'mb-1 grid w-full grid-cols-7', gridBody: 'grid', headCell: 'rounded-md', cell: 'relative text-center', cellTrigger: [ 'm-0.5 relative flex items-center justify-center rounded-full whitespace-nowrap focus-visible:ring-2 focus:outline-none data-disabled:text-muted data-unavailable:line-through data-unavailable:text-muted data-unavailable:pointer-events-none data-today:font-semibold data-[outside-view]:text-muted', 'transition' ] }, variants: { color: { primary: { headCell: 'text-primary', cellTrigger: 'focus-visible:ring-primary' }, secondary: { headCell: 'text-secondary', cellTrigger: 'focus-visible:ring-secondary' }, success: { headCell: 'text-success', cellTrigger: 'focus-visible:ring-success' }, info: { headCell: 'text-info', cellTrigger: 'focus-visible:ring-info' }, warning: { headCell: 'text-warning', cellTrigger: 'focus-visible:ring-warning' }, error: { headCell: 'text-error', cellTrigger: 'focus-visible:ring-error' }, neutral: { headCell: 'text-highlighted', cellTrigger: 'focus-visible:ring-inverted' } }, variant: { solid: '', outline: '', soft: '', subtle: '' }, size: { xs: { heading: 'text-xs', cell: 'text-xs', headCell: 'text-[10px]', cellTrigger: 'size-7', body: 'space-y-2 pt-2' }, sm: { heading: 'text-xs', headCell: 'text-xs', cell: 'text-xs', cellTrigger: 'size-7' }, md: { heading: 'text-sm', headCell: 'text-xs', cell: 'text-sm', cellTrigger: 'size-8' }, lg: { heading: 'text-md', headCell: 'text-md', cellTrigger: 'size-9 text-md' }, xl: { heading: 'text-lg', headCell: 'text-lg', cellTrigger: 'size-10 text-lg' } } }, compoundVariants: [ { color: 'primary', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-primary data-[selected]:text-inverted data-today:not-data-[selected]:text-primary data-[highlighted]:bg-primary/20 hover:not-data-[selected]:bg-primary/20' } }, { color: 'secondary', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-secondary data-[selected]:text-inverted data-today:not-data-[selected]:text-secondary data-[highlighted]:bg-secondary/20 hover:not-data-[selected]:bg-secondary/20' } }, { color: 'success', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-success data-[selected]:text-inverted data-today:not-data-[selected]:text-success data-[highlighted]:bg-success/20 hover:not-data-[selected]:bg-success/20' } }, { color: 'info', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-info data-[selected]:text-inverted data-today:not-data-[selected]:text-info data-[highlighted]:bg-info/20 hover:not-data-[selected]:bg-info/20' } }, { color: 'warning', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-warning data-[selected]:text-inverted data-today:not-data-[selected]:text-warning data-[highlighted]:bg-warning/20 hover:not-data-[selected]:bg-warning/20' } }, { color: 'error', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-error data-[selected]:text-inverted data-today:not-data-[selected]:text-error data-[highlighted]:bg-error/20 hover:not-data-[selected]:bg-error/20' } }, { color: 'primary', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-primary/50 data-[selected]:text-primary data-today:not-data-[selected]:text-primary data-[highlighted]:bg-primary/10 hover:not-data-[selected]:bg-primary/10' } }, { color: 'secondary', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-secondary/50 data-[selected]:text-secondary data-today:not-data-[selected]:text-secondary data-[highlighted]:bg-secondary/10 hover:not-data-[selected]:bg-secondary/10' } }, { color: 'success', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-success/50 data-[selected]:text-success data-today:not-data-[selected]:text-success data-[highlighted]:bg-success/10 hover:not-data-[selected]:bg-success/10' } }, { color: 'info', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-info/50 data-[selected]:text-info data-today:not-data-[selected]:text-info data-[highlighted]:bg-info/10 hover:not-data-[selected]:bg-info/10' } }, { color: 'warning', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-warning/50 data-[selected]:text-warning data-today:not-data-[selected]:text-warning data-[highlighted]:bg-warning/10 hover:not-data-[selected]:bg-warning/10' } }, { color: 'error', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-error/50 data-[selected]:text-error data-today:not-data-[selected]:text-error data-[highlighted]:bg-error/10 hover:not-data-[selected]:bg-error/10' } }, { color: 'primary', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-primary/10 data-[selected]:text-primary data-today:not-data-[selected]:text-primary data-[highlighted]:bg-primary/20 hover:not-data-[selected]:bg-primary/20' } }, { color: 'secondary', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-secondary/10 data-[selected]:text-secondary data-today:not-data-[selected]:text-secondary data-[highlighted]:bg-secondary/20 hover:not-data-[selected]:bg-secondary/20' } }, { color: 'success', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-success/10 data-[selected]:text-success data-today:not-data-[selected]:text-success data-[highlighted]:bg-success/20 hover:not-data-[selected]:bg-success/20' } }, { color: 'info', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-info/10 data-[selected]:text-info data-today:not-data-[selected]:text-info data-[highlighted]:bg-info/20 hover:not-data-[selected]:bg-info/20' } }, { color: 'warning', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-warning/10 data-[selected]:text-warning data-today:not-data-[selected]:text-warning data-[highlighted]:bg-warning/20 hover:not-data-[selected]:bg-warning/20' } }, { color: 'error', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-error/10 data-[selected]:text-error data-today:not-data-[selected]:text-error data-[highlighted]:bg-error/20 hover:not-data-[selected]:bg-error/20' } }, { color: 'primary', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-primary/10 data-[selected]:text-primary data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-primary/25 data-today:not-data-[selected]:text-primary data-[highlighted]:bg-primary/20 hover:not-data-[selected]:bg-primary/20' } }, { color: 'secondary', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-secondary/10 data-[selected]:text-secondary data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-secondary/25 data-today:not-data-[selected]:text-secondary data-[highlighted]:bg-secondary/20 hover:not-data-[selected]:bg-secondary/20' } }, { color: 'success', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-success/10 data-[selected]:text-success data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-success/25 data-today:not-data-[selected]:text-success data-[highlighted]:bg-success/20 hover:not-data-[selected]:bg-success/20' } }, { color: 'info', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-info/10 data-[selected]:text-info data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-info/25 data-today:not-data-[selected]:text-info data-[highlighted]:bg-info/20 hover:not-data-[selected]:bg-info/20' } }, { color: 'warning', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-warning/10 data-[selected]:text-warning data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-warning/25 data-today:not-data-[selected]:text-warning data-[highlighted]:bg-warning/20 hover:not-data-[selected]:bg-warning/20' } }, { color: 'error', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-error/10 data-[selected]:text-error data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-error/25 data-today:not-data-[selected]:text-error data-[highlighted]:bg-error/20 hover:not-data-[selected]:bg-error/20' } }, { color: 'neutral', variant: 'solid', class: { cellTrigger: 'data-[selected]:bg-inverted data-[selected]:text-inverted data-today:not-data-[selected]:text-highlighted data-[highlighted]:bg-inverted/20 hover:not-data-[selected]:bg-inverted/10' } }, { color: 'neutral', variant: 'outline', class: { cellTrigger: 'data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-accented data-[selected]:text-default data-[selected]:bg-default data-today:not-data-[selected]:text-highlighted data-[highlighted]:bg-inverted/10 hover:not-data-[selected]:bg-inverted/10' } }, { color: 'neutral', variant: 'soft', class: { cellTrigger: 'data-[selected]:bg-elevated data-[selected]:text-default data-today:not-data-[selected]:text-highlighted data-[highlighted]:bg-inverted/20 hover:not-data-[selected]:bg-inverted/10' } }, { color: 'neutral', variant: 'subtle', class: { cellTrigger: 'data-[selected]:bg-elevated data-[selected]:text-default data-[selected]:ring data-[selected]:ring-inset data-[selected]:ring-accented data-today:not-data-[selected]:text-highlighted data-[highlighted]:bg-inverted/20 hover:not-data-[selected]:bg-inverted/10' } } ], defaultVariants: { size: 'md', color: 'primary', variant: 'solid' } } } }) ``` ## Changelog ::component-changelog :: # Card ## Usage Use the `header`, `default` and `footer` slots to add content to the Card. ```vue [CardExample.vue] ``` ### Variant Use the `variant` prop to change the variant of the Card. ```vue ``` ## API ### Props ```ts /** * Props for the Card component */ interface CardProps { /** * The element or component this component should render as. */ as?: any; variant?: "solid" | "outline" | "soft" | "subtle" | undefined; ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the Card component */ interface CardSlots { header(): any; default(): any; footer(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { card: { slots: { root: 'rounded-lg overflow-hidden', header: 'p-4 sm:px-6', body: 'p-4 sm:p-6', footer: 'p-4 sm:px-6' }, variants: { variant: { solid: { root: 'bg-inverted text-inverted' }, outline: { root: 'bg-default ring ring-default divide-y divide-default' }, soft: { root: 'bg-elevated/50 divide-y divide-default' }, subtle: { root: 'bg-elevated/50 ring ring-default divide-y divide-default' } } }, defaultVariants: { variant: 'outline' } } } }) ``` ## Changelog ::component-changelog :: # Carousel ## Usage Use the Carousel component to display a list of items in a carousel. ```vue [CarouselItemsExample.vue] ``` ::note Use your mouse to drag the carousel horizontally on desktop. :: ### Items Use the `items` prop as an array and render each item using the default slot: ```vue [CarouselItemsExample.vue] ``` You can also pass an array of objects with the following properties: - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can control how many items are visible by using the [`basis`](https://tailwindcss.com/docs/flex-basis){rel="nofollow"} / [`width`](https://tailwindcss.com/docs/width){rel="nofollow"} utility classes on the `item`: ```vue [CarouselItemsMultipleExample.vue] ``` ### Orientation Use the `orientation` prop to change the orientation of the Progress. Defaults to `horizontal`. ::note Use your mouse to drag the carousel vertically on desktop. :: ```vue [CarouselOrientationExample.vue] ``` ::caution You need to specify a `height` on the container in vertical orientation. :: ### Arrows Use the `arrows` prop to display prev and next buttons. ```vue [CarouselArrowsExample.vue] ``` ### Prev / Next Use the `prev` and `next` props to customize the prev and next buttons with any [Button](https://ui.nuxt.com/docs/components/button) props. ```vue [CarouselPrevNextExample.vue] ``` ### Prev / Next Icons Use the `prev-icon` and `next-icon` props to customize the buttons [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-arrow-left` / `i-lucide-arrow-right`. ```vue [CarouselPrevNextIconExample.vue] ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize these icons globally in your `app.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize these icons globally in your `vite.config.ts` under `ui.icons.arrowLeft` / `ui.icons.arrowRight` key. ::: :: ### Dots Use the `dots` prop to display a list of dots to scroll to a specific slide. ```vue [CarouselDotsExample.vue] ``` The number of dots is based on the number of slides displayed in the view: ```vue [CarouselDotsMultipleExample.vue] ``` ## Plugins The Carousel component implements the official [Embla Carousel plugins](https://www.embla-carousel.com/plugins/){rel="nofollow"}. ### Autoplay This plugin is used to extend Embla Carousel with **autoplay** functionality. Use the `autoplay` prop as a boolean or an object to configure the [Autoplay plugin](https://www.embla-carousel.com/plugins/autoplay/){rel="nofollow"}. ```vue [CarouselAutoplayExample.vue] ``` ::note In this example, we're using the `loop` prop for an infinite carousel. :: ### Auto Scroll This plugin is used to extend Embla Carousel with **auto scroll** functionality. Use the `auto-scroll` prop as a boolean or an object to configure the [Auto Scroll plugin](https://www.embla-carousel.com/plugins/auto-scroll/){rel="nofollow"}. ```vue [CarouselAutoScrollExample.vue] ``` ::note In this example, we're using the `loop` prop for an infinite carousel. :: ### Auto Height This plugin is used to extend Embla Carousel with **auto height** functionality. It changes the height of the carousel container to fit the height of the highest slide in view. Use the `auto-height` prop as a boolean or an object to configure the [Auto Height plugin](https://www.embla-carousel.com/plugins/auto-height/){rel="nofollow"}. ```vue [CarouselAutoHeightExample.vue] ``` ::note In this example, we add the `transition-[height]` class on the container to animate the height change. :: ### Class Names Class Names is a **class name toggle** utility plugin for Embla Carousel that enables you to automate the toggling of class names on your carousel. Use the `class-names` prop as a boolean or an object to configure the [Class Names plugin](https://www.embla-carousel.com/plugins/class-names/){rel="nofollow"}. ```vue [CarouselClassNamesExample.vue] ``` ::note In this example, we add the `transition-opacity [&:not(.is-snapped)]:opacity-10` classes on the `item` to animate the opacity change. :: ### Fade This plugin is used to replace the Embla Carousel scroll functionality with **fade transitions**. Use the `fade` prop as a boolean or an object to configure the [Fade plugin](https://www.embla-carousel.com/plugins/fade/){rel="nofollow"}. ```vue [CarouselFadeExample.vue] ``` ### Wheel Gestures This plugin is used to extend Embla Carousel with the ability to **use the mouse/trackpad wheel** to navigate the carousel. Use the `wheel-gestures` prop as a boolean or an object to configure the [Wheel Gestures plugin](https://www.embla-carousel.com/plugins/wheel-gestures/){rel="nofollow"}. ::note Use your mouse wheel to scroll the carousel. :: ```vue [CarouselWheelGesturesExample.vue] ``` ## Examples ### With thumbnails You can use the [`emblaApi`](https://ui.nuxt.com/#expose) function [scrollTo](https://www.embla-carousel.com/api/methods/#scrollto){rel="nofollow"} to display thumbnails under the carousel that allows you to navigate to a specific slide. ```vue [CarouselThumbnailsExample.vue] ``` ## API ### Props ```ts /** * Props for the Carousel component */ interface CarouselProps { /** * The element or component this component should render as. */ as?: any; /** * Configure the prev button when arrows are enabled. */ prev?: ButtonProps | undefined; /** * The icon displayed in the prev button. */ prevIcon?: string | object | undefined; /** * Configure the next button when arrows are enabled. */ next?: ButtonProps | undefined; /** * The icon displayed in the next button. */ nextIcon?: string | object | undefined; /** * Display prev and next buttons to scroll the carousel. * @default "false" */ arrows?: boolean | undefined; /** * Display dots to scroll to a specific slide. * @default "false" */ dots?: boolean | undefined; /** * The orientation of the carousel. * @default "\"horizontal\"" */ orientation?: "vertical" | "horizontal" | undefined; items?: CarouselItem[] | undefined; /** * Enable Autoplay plugin * @default "false" */ autoplay?: boolean | Partial> | undefined; /** * Enable Auto Scroll plugin * @default "false" */ autoScroll?: boolean | Partial> | undefined; /** * Enable Auto Height plugin * @default "false" */ autoHeight?: boolean | Partial, "breakpoints">; }; }>> | undefined; /** * Enable Class Names plugin * @default "false" */ classNames?: boolean | Partial> | undefined; /** * Enable Fade plugin * @default "false" */ fade?: boolean | Partial, "breakpoints">; }; }>> | undefined; /** * Enable Wheel Gestures plugin * @default "false" */ wheelGestures?: boolean | WheelGesturesPluginOptions | undefined; ui?: { root?: ClassNameValue; viewport?: ClassNameValue; container?: ClassNameValue; item?: ClassNameValue; controls?: ClassNameValue; arrows?: ClassNameValue; prev?: ClassNameValue; next?: ClassNameValue; dots?: ClassNameValue; dot?: ClassNameValue; } | undefined; /** * @default "\"center\"" */ align?: AlignmentOptionType | undefined; /** * @default "\"trimSnaps\"" */ containScroll?: ScrollContainOptionType | undefined; /** * @default "1" */ slidesToScroll?: SlidesToScrollOptionType | undefined; /** * @default "false" */ dragFree?: boolean | undefined; /** * @default "10" */ dragThreshold?: number | undefined; /** * @default "0" */ inViewThreshold?: number | number[] | undefined; /** * @default "false" */ loop?: boolean | undefined; /** * @default "false" */ skipSnaps?: boolean | undefined; /** * @default "25" */ duration?: number | undefined; /** * @default "0" */ startIndex?: number | undefined; /** * @default "true" */ watchDrag?: DragHandlerOptionType | undefined; /** * @default "true" */ watchResize?: ResizeHandlerOptionType | undefined; /** * @default "true" */ watchSlides?: SlidesHandlerOptionType | undefined; /** * @default "true" */ watchFocus?: FocusHandlerOptionType | undefined; /** * @default "true" */ active?: boolean | undefined; /** * @default "{}" */ breakpoints?: { [key: string]: Omit | null; containScroll: ScrollContainOptionType; direction: AxisDirectionOptionType; slidesToScroll: SlidesToScrollOptionType; dragFree: boolean; dragThreshold: number; inViewThreshold: number | number[] | undefined; loop: boolean; skipSnaps: boolean; duration: number; startIndex: number; watchDrag: DragHandlerOptionType; watchResize: ResizeHandlerOptionType; watchSlides: SlidesHandlerOptionType; watchFocus: FocusHandlerOptionType; }>>, "breakpoints">; } | undefined; } ``` ### Slots ```ts /** * Slots for the Carousel component */ interface CarouselSlots { default(): any; } ``` ### Emits ```ts /** * Emitted events for the Carousel component */ interface CarouselEmits { select: (payload: [selectedIndex: number]) => void; } ``` ### Expose You can access the typed component instance using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref){rel="nofollow"}. ```vue ``` This will give you access to the following: | Name | Type | | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emblaRef`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | | `emblaApi`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | [`Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://www.embla-carousel.com/api/methods/#typescript){rel="nofollow"} | ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { carousel: { slots: { root: 'relative focus:outline-none', viewport: 'overflow-hidden', container: 'flex items-start', item: 'min-w-0 shrink-0 basis-full', controls: '', arrows: '', prev: 'absolute rounded-full', next: 'absolute rounded-full', dots: 'absolute inset-x-0 -bottom-7 flex flex-wrap items-center justify-center gap-3', dot: [ 'cursor-pointer size-3 bg-accented rounded-full', 'transition' ] }, variants: { orientation: { vertical: { container: 'flex-col -mt-4', item: 'pt-4', prev: 'top-4 sm:-top-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90', next: 'bottom-4 sm:-bottom-12 left-1/2 -translate-x-1/2 rotate-90 rtl:-rotate-90' }, horizontal: { container: 'flex-row -ms-4', item: 'ps-4', prev: 'start-4 sm:-start-12 top-1/2 -translate-y-1/2', next: 'end-4 sm:-end-12 top-1/2 -translate-y-1/2' } }, active: { true: { dot: 'data-[state=active]:bg-inverted' } } } } } }) ``` ## Changelog ::component-changelog :: # ChangelogVersion ## Usage The ChangelogVersion component provides a flexible way to display an `
` element with customizable content including title, description, image, etc. ::code-preview :::u-changelog-version --- authors: - name: Benjamin Canac description: "@benjamincanac" avatar: src: https://github.com/benjamincanac.png to: https://x.com/benjamincanac target: _blank - name: Sebastien Chopin description: "@atinux" avatar: src: https://github.com/atinux.png to: https://x.com/atinux target: _blank - name: Hugo Richard description: "@hugorcd__" avatar: src: https://github.com/hugorcd.png to: https://x.com/hugorcd__ target: _blank ui: container: max-w-lg class: w-full date: 2025-03-12 description: Nuxt UI v3 is out! After 1500+ commits, this major redesign brings improved accessibility, Tailwind CSS v4 support, and full Vue compatibility. image: https://nuxt.com/assets/blog/nuxt-ui-v3.png target: _blank title: Introducing Nuxt UI v3 to: https://nuxt.com/blog/nuxt-ui-v3 --- ::: :: ::tip{to="https://ui.nuxt.com/docs/components/changelog-versions"} Use the [`ChangelogVersions`](https://ui.nuxt.com/docs/components/changelog-versions) component to display multiple changelog versions in a timeline with an indicator bar on the left. :: ### Title Use the `title` prop to display the title of the ChangelogVersion. ```vue ``` ### Description Use the `description` prop to display the description of the ChangelogVersion. ```vue ``` ### Date Use the `date` prop to display the date of the ChangelogVersion. ::tip The date is automatically formatted to the [current locale](https://ui.nuxt.com/docs/getting-started/integrations/i18n/nuxt#locale). You can either pass a `Date` object or a string. :: ```vue ``` ### Badge Use the `badge` prop to display a [Badge](https://ui.nuxt.com/docs/components/badge) on the ChangelogVersion. ```vue ``` You can pass any property from the [Badge](https://ui.nuxt.com/docs/components/badge#props) component to customize it. ```vue ``` ### Image Use the `image` prop to display an image in the BlogPost. ::note If [`@nuxt/image`](https://image.nuxt.com/get-started/installation){rel="nofollow"} is installed, the `` component will be used instead of the native `img` tag. :: ```vue ``` ### Authors Use the `authors` prop to display a list of [User](https://ui.nuxt.com/docs/components/user) in the ChangelogVersion as an array of objects with the following properties: - `name?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `description?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `avatar?: Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `chip?: boolean | Omit`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `size?: UserProps['size']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `orientation?: UserProps['orientation']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` ### Link You can pass any property from the [``](https://nuxt.com/docs/api/components/nuxt-link){rel="nofollow"} component such as `to`, `target`, `rel`, etc. ```vue ``` ### Indicator Use the `indicator` prop to hide the indicator dot on the left. Defaults to `true`. ```vue ``` ::note When the `indicator` prop is `false`, the date will be displayed over the title. :: ## Examples ### With body slot You can use the `body` slot to display custom content between the image and the authors with: - the [MDC](https://github.com/nuxt-modules/mdc?tab=readme-ov-file#mdc){rel="nofollow"} component from `@nuxtjs/mdc` to display some markdown. - the [ContentRenderer](https://content.nuxt.com/docs/components/content-renderer){rel="nofollow"} component from `@nuxt/content` to render the content of the page or list. - or use the `:u-changelog-version` component directly in your content with markdown inside the `body` slot as Nuxt UI provides pre-styled prose components. ```vue [ChangelogVersionMarkdownExample.vue] ``` ## API ### Props ```ts /** * Props for the ChangelogVersion component */ interface ChangelogVersionProps { /** * The element or component this component should render as. * @default "\"article\"" */ as?: any; title?: string | undefined; description?: string | undefined; /** * The date of the changelog version. Can be a string or a Date object. */ date?: string | Date | undefined; /** * Display a badge on the changelog version. * Can be a string or an object. * `{ color: 'neutral', variant: 'solid' }`{lang="ts-type"} */ badge?: string | BadgeProps | undefined; /** * The authors of the changelog version. */ authors?: UserProps[] | undefined; /** * The image of the changelog version. Can be a string or an object. */ image?: string | (Partial & { [key: string]: any; }) | undefined; /** * Display an indicator dot on the left. * @default "true" */ indicator?: boolean | undefined; to?: string | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric | undefined; target?: "_blank" | "_parent" | "_self" | "_top" | (string & {}) | null | undefined; onClick?: ((event: MouseEvent) => void | Promise) | undefined; ui?: { root?: ClassNameValue; container?: ClassNameValue; header?: ClassNameValue; meta?: ClassNameValue; date?: ClassNameValue; badge?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; imageWrapper?: ClassNameValue; image?: ClassNameValue; authors?: ClassNameValue; footer?: ClassNameValue; indicator?: ClassNameValue; dot?: ClassNameValue; dotInner?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the ChangelogVersion component */ interface ChangelogVersionSlots { header(): any; badge(): any; date(): any; title(): any; description(): any; image(): any; body(): any; footer(): any; authors(): any; actions(): any; indicator(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { changelogVersion: { slots: { root: 'relative', container: 'flex flex-col mx-auto max-w-2xl', header: '', meta: 'flex items-center gap-3 mb-2', date: 'text-sm/6 text-toned truncate', badge: '', title: 'relative text-xl text-pretty font-semibold text-highlighted', description: 'text-base text-pretty text-muted mt-1', imageWrapper: 'relative overflow-hidden rounded-lg aspect-[16/9] mt-5 group/changelog-version-image', image: 'object-cover object-top w-full h-full', authors: 'flex flex-wrap gap-x-4 gap-y-1.5', footer: 'border-t border-default pt-5 flex items-center justify-between', indicator: 'absolute start-0 top-0 w-32 hidden lg:flex items-center justify-end gap-3 min-w-0', dot: 'size-4 rounded-full bg-default ring ring-default flex items-center justify-center my-1', dotInner: 'size-2 rounded-full bg-primary' }, variants: { body: { false: { footer: 'mt-5' } }, badge: { false: { meta: 'lg:hidden' } }, to: { true: { image: 'transform transition-transform duration-200 group-hover/changelog-version-image:scale-105' } }, hidden: { true: { date: 'lg:hidden' } } } } } }) ``` ## Changelog ::component-changelog :: # ChangelogVersions ## Usage The ChangelogVersions component provides a flexible layout to display a list of [ChangelogVersion](https://ui.nuxt.com/docs/components/changelog-version) components using either the default slot or the `versions` prop. ```vue {2,8} ``` ### Versions Use the `versions` prop as an array of objects with the properties of the [ChangelogVersion](https://ui.nuxt.com/docs/components/changelog-version#props) component. ```vue ``` ### Indicator Use the `indicator` prop to hide the indicator bar on the left. Defaults to `true`. ```vue ``` ### Indicator Motion Use the `indicator-motion` prop to customize or hide the motion effect on the indicator bar. Defaults to `true` with `{ damping: 30, restDelta: 0.001 }` [spring transition options](https://motion.dev/docs/vue-transitions#spring){rel="nofollow"}. ```vue ``` ## Examples ::note While these examples use [Nuxt Content](https://content.nuxt.com){rel="nofollow"}, the components can be integrated with any content management system. :: ### Within a page Use the ChangelogVersions component in a page to create a changelog page: ```vue [pages/changelog.vue] {10-17} ``` ::note In this example, the `versions` are fetched using `queryCollection` from the `@nuxt/content` module. :: ::tip The `to` prop is overridden here since `@nuxt/content` uses the `path` property. :: ### With sticky indicator You can use the `ui` prop and the different slots to make the indicators sticky: ```vue [ChangelogVersionsStickyExample.vue] ``` ## API ### Props ```ts /** * Props for the ChangelogVersions component */ interface ChangelogVersionsProps { /** * The element or component this component should render as. */ as?: any; versions?: ChangelogVersionProps[] | undefined; /** * Display an indicator bar on the left. * @default "true" */ indicator?: boolean | undefined; /** * Enable scrolling motion effect on the indicator bar. * `{ damping: 30, restDelta: 0.001 }`{lang="ts-type"} * @default "true" */ indicatorMotion?: boolean | SpringOptions | undefined; ui?: { root?: ClassNameValue; container?: ClassNameValue; indicator?: ClassNameValue; beam?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the ChangelogVersions component */ interface ChangelogVersionsSlots { header(): any; badge(): any; date(): any; title(): any; description(): any; image(): any; body(): any; footer(): any; authors(): any; actions(): any; indicator(): any; default(): any; } ``` ::tip You can use all the slots of the [`ChangelogVersion`](https://ui.nuxt.com/docs/components/changelog-version#slots) component inside ChangelogVersions, they are automatically forwarded allowing you to customize individual versions when using the `versions` prop. ```vue {3-5} ``` :: ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { changelogVersions: { slots: { root: 'relative', container: 'flex flex-col gap-y-8 sm:gap-y-12 lg:gap-y-16', indicator: 'absolute hidden lg:block overflow-hidden inset-y-3 start-32 h-full w-px bg-border -ms-[8.5px]', beam: 'absolute start-0 top-0 w-full bg-primary will-change-[height]' } } } }) ``` ## Changelog ::component-changelog :: # ChatMessage ## Usage The ChatMessage component renders an `
` element for a `user` or `assistant` chat message. ::code-preview :::u-chat-message --- avatar: src: https://github.com/benjamincanac.png parts: - type: text id: "1" text: Hello! Tell me more about building AI chatbots with Nuxt UI. id: "1" role: user side: right variant: soft --- ::: :: ::tip{to="https://ui.nuxt.com/docs/components/chat-messages"} Use the [`ChatMessages`](https://ui.nuxt.com/docs/components/chat-messages) component to display a list of chat messages. :: ### Parts Use the `parts` prop to display the message content using the AI SDK v5 format. ```vue ``` ::note The `parts` prop is the recommended format for AI SDK v5. Each part has a `type` (e.g., 'text') and corresponding content. The ChatMessage component also supports the deprecated `content` prop for backward compatibility. :: ### Side Use the `side` prop to display the message on the left or right. ```vue ``` ::note When using the [`ChatMessages`](https://ui.nuxt.com/docs/components/chat-messages) component, the `side` prop is set to `left` for `assistant` messages and `right` for `user` messages. :: ### Variant Use the `variant` prop to change style of the message. ```vue ``` ::note When using the [`ChatMessages`](https://ui.nuxt.com/docs/components/chat-messages) component, the `variant` prop is set to `naked` for `assistant` messages and `soft` for `user` messages. :: ### Icon Use the `icon` prop to display an [Icon](https://ui.nuxt.com/docs/components/icon) component next to the message. ```vue ``` ### Avatar Use the `avatar` prop to display an [Avatar](https://ui.nuxt.com/docs/components/avatar) component next to the message. ```vue ``` You can also use the `avatar.icon` prop to display an icon as the avatar. ```vue ``` ### Actions Use the `actions` prop to display actions below the message that will be displayed when hovering over the message. ```vue ``` ## API ### Props ```ts /** * Props for the ChatMessage component */ interface ChatMessageProps { /** * A unique identifier for the message. */ id: string; /** * The role of the message. */ role: "system" | "user" | "assistant"; /** * The parts of the message. Use this for rendering the message in the UI. * * System messages should be avoided (set the system prompt on the server instead). * They can have text parts. * * User messages can have text parts and file parts. * * Assistant messages can have text, reasoning, tool invocation, and file parts. */ parts: UIMessagePart[]; /** * The element or component this component should render as. * @default "\"article\"" */ as?: any; icon?: string | object | undefined; avatar?: (AvatarProps & { [key: string]: any; }) | undefined; variant?: "solid" | "outline" | "soft" | "subtle" | "naked" | undefined; side?: "left" | "right" | undefined; /** * Display a list of actions under the message. * The `label` will be used in a tooltip. * `{ size: 'xs', color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ actions?: (Omit & { onClick?: ((e: MouseEvent, message: UIMessage) => void) | undefined; })[] | undefined; /** * Render the message in a compact style. * This is done automatically when used inside a `UChatPalette`{lang="ts-type"}. */ compact?: boolean | undefined; content?: string | undefined; ui?: { root?: ClassNameValue; container?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; content?: ClassNameValue; actions?: ClassNameValue; } | undefined; /** * The metadata of the message. */ metadata?: unknown; } ``` ### Slots ```ts /** * Slots for the ChatMessage component */ interface ChatMessageSlots { leading(): any; content(): any; actions(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { chatMessage: { slots: { root: 'group/message relative w-full', container: 'relative flex items-start', leading: 'inline-flex items-center justify-center min-h-6', leadingIcon: 'shrink-0', leadingAvatar: 'shrink-0', leadingAvatarSize: '', content: 'relative text-pretty min-w-0 *:first:mt-0 *:last:mb-0', actions: [ 'opacity-0 group-hover/message:opacity-100 absolute bottom-0 flex items-center', 'transition-opacity' ] }, variants: { variant: { solid: { content: 'bg-inverted text-inverted' }, outline: { content: 'bg-default ring ring-default' }, soft: { content: 'bg-elevated/50' }, subtle: { content: 'bg-elevated/50 ring ring-default' }, naked: { content: '' } }, side: { left: { container: 'rtl:justify-end' }, right: { container: 'ltr:justify-end ms-auto max-w-[75%]' } }, leading: { true: '' }, actions: { true: '' }, compact: { true: { root: 'scroll-mt-3', container: 'gap-1.5 pb-3', leadingIcon: 'size-5', leadingAvatarSize: '2xs' }, false: { root: 'scroll-mt-4 sm:scroll-mt-6', container: 'gap-3 pb-8', leadingIcon: 'size-8', leadingAvatarSize: 'md' } } }, compoundVariants: [ { compact: true, actions: true, class: { container: 'pb-8' } }, { leading: true, compact: false, side: 'left', class: { actions: 'left-11' } }, { leading: true, compact: true, side: 'left', class: { actions: 'left-6.5' } }, { variant: [ 'solid', 'outline', 'soft', 'subtle' ], compact: false, class: { content: 'px-4 py-3 rounded-lg min-h-12', leading: 'mt-2' } }, { variant: [ 'solid', 'outline', 'soft', 'subtle' ], compact: true, class: { content: 'px-2 py-1 rounded-lg min-h-8', leading: 'mt-1' } }, { variant: 'naked', side: 'left', class: { content: 'w-full' } } ], defaultVariants: { variant: 'naked' } } } }) ``` ## Changelog ::component-changelog :: # ChatMessages ## Usage The ChatMessages component displays a list of [ChatMessage](https://ui.nuxt.com/docs/components/chat-message) components using either the default slot or the `messages` prop. ```vue {2,8} ``` ::callout{icon="i-lucide-rocket"} This component is purpose-built for AI chatbots with features like: - Initial scroll to the bottom upon loading ([`shouldScrollToBottom`](https://ui.nuxt.com/#should-scroll-to-bottom)). - Continuous scrolling down as new messages arrive ([`shouldAutoScroll`](https://ui.nuxt.com/#should-auto-scroll)). - An "Auto scroll" button appears when scrolled up, allowing users to jump back to the latest messages ([`autoScroll`](https://ui.nuxt.com/#auto-scroll)). - A loading indicator displays while the assistant is processing ([`status`](https://ui.nuxt.com/#status)). - Submitted messages are scrolled to the top of the viewport and the height of the last user message is dynamically adjusted. :: ### Messages Use the `messages` prop to display a list of chat messages. ```vue ``` ### Status Use the `status` prop to display a visual indicator when the assistant is processing. ```vue ``` ::note Here's the detail of the different statuses from the AI SDK v5 Chat class: - `submitted`: The message has been sent to the API and we're awaiting the start of the response stream. - `streaming`: The response is actively streaming in from the API, receiving chunks of data. - `ready`: The full response has been received and processed; a new user message can be submitted. - `error`: An error occurred during the API request, preventing successful completion. :: ### User Use the `user` prop to change the [ChatMessage](https://ui.nuxt.com/docs/components/chat-message) props for `user` messages. Defaults to: - `side: 'right'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `variant: 'soft'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue ``` ### Assistant Use the `assistant` prop to change the [ChatMessage](https://ui.nuxt.com/docs/components/chat-message) props for `assistant` messages. Defaults to: - `side: 'left'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `variant: 'naked'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue ``` ### Auto Scroll Use the `auto-scroll` prop to customize or hide the auto scroll button (with `false` value) displayed when scrolling to the top of the chat. Defaults to: - `color: 'neutral'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `variant: 'outline'`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue ``` ### Auto Scroll Icon Use the `auto-scroll-icon` prop to customize the auto scroll button [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-arrow-down`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.arrowDown` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.arrowDown` key. ::: :: ### Should Auto Scroll Use the `should-auto-scroll` prop to enable/disable continuous auto scroll while messages are streaming. Defaults to `false`. ```vue ``` ### Should Scroll To Bottom Use the `should-scroll-to-bottom` prop to enable/disable bottom auto scroll when the component is mounted. Defaults to `true`. ```vue ``` ## Examples ::note{target="_blank" to="https://ai-sdk.dev/docs/getting-started/nuxt"} These chat components are designed to be used with the **AI SDK v5** from **Vercel AI SDK**. :: ::callout --- icon: i-simple-icons-github target: _blank to: https://github.com/nuxt-ui-templates/chat --- Check out the source code of our **AI Chat template** on GitHub for a real-life example. :: ### Within a page Use the ChatMessages component with the `Chat` class from AI SDK v5 to display a list of chat messages within a page. Pass the `messages` prop alongside the `status` prop that will be used for the auto scroll and the indicator display. ```vue [pages/[id\\].vue] {2,7-11,24,28} ``` ::note In this example, we use the `MDC` component from [`@nuxtjs/mdc`](https://github.com/nuxt-modules/mdc){rel="nofollow"} to render the content of the message. The `getTextFromMessage` utility extracts the text content from the AI SDK V5 message parts. As Nuxt UI provides pre-styled prose components, your content will be automatically styled. :: ### With indicator slot You can customize the loading indicator that appears when the status is `submitted`. ```vue [ChatMessagesIndicatorSlotExample.vue] ``` ## API ### Props ```ts /** * Props for the ChatMessages component */ interface ChatMessagesProps { messages?: UIMessage[] | undefined; status?: ChatStatus | undefined; /** * Whether to automatically scroll to the bottom when a message is streaming. * @default "false" */ shouldAutoScroll?: boolean | undefined; /** * Whether to scroll to the bottom on mounted. * @default "true" */ shouldScrollToBottom?: boolean | undefined; /** * Display an auto scroll button. * `{ size: 'md', color: 'neutral', variant: 'outline' }`{lang="ts-type"} * @default "true" */ autoScroll?: boolean | Partial | undefined; /** * The icon displayed in the auto scroll button. */ autoScrollIcon?: string | object | undefined; /** * The `user` messages props. * `{ side: 'right', variant: 'soft' }`{lang="ts-type"} */ user?: Pick | undefined; /** * The `assistant` messages props. * `{ side: 'left', variant: 'naked' }`{lang="ts-type"} */ assistant?: Pick | undefined; /** * Render the messages in a compact style. * This is done automatically when used inside a `UChatPalette`{lang="ts-type"}. */ compact?: boolean | undefined; /** * The spacing offset for the last message in px. Can be useful when the prompt is sticky for example. * @default "0" */ spacingOffset?: number | undefined; ui?: { root?: ClassNameValue; indicator?: ClassNameValue; viewport?: ClassNameValue; autoScroll?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the ChatMessages component */ interface ChatMessagesSlots { leading(): any; content(): any; actions(): any; default(): any; indicator(): any; viewport(): any; } ``` ::tip You can use all the slots of the [`ChatMessage`](https://ui.nuxt.com/docs/components/chat-message#slots) component inside ChatMessages, they are automatically forwarded allowing you to customize individual messages when using the `messages` prop. ```vue {7-9} ``` :: ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { chatMessages: { slots: { root: 'w-full flex flex-col gap-1 flex-1 px-2.5 [&>article]:last-of-type:min-h-(--last-message-height)', indicator: 'h-6 flex items-center gap-1 py-3 *:size-2 *:rounded-full *:bg-elevated [&>*:nth-child(1)]:animate-[bounce_1s_infinite] [&>*:nth-child(2)]:animate-[bounce_1s_0.15s_infinite] [&>*:nth-child(3)]:animate-[bounce_1s_0.3s_infinite]', viewport: 'absolute inset-x-0 top-[86%] data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]', autoScroll: 'rounded-full absolute right-1/2 translate-x-1/2 bottom-0' }, variants: { compact: { true: '', false: '' } } } } }) ``` ## Changelog ::component-changelog :: # ChatPalette ## Usage The ChatPalette component is a structured layout wrapper that organizes [ChatMessages](https://ui.nuxt.com/docs/components/chat-messages) in a scrollable content area and [ChatPrompt](https://ui.nuxt.com/docs/components/chat-prompt) in a fixed bottom section, creating cohesive chatbot interfaces for modals, slideovers, or drawers. ```vue {2,8} ``` ## Examples ::note{target="_blank" to="https://ai-sdk.dev/docs/getting-started/nuxt"} These chat components are designed to be used with the **AI SDK v5** from **Vercel AI SDK**. :: ### Within a Modal You can use the ChatPalette component inside a [Modal](https://ui.nuxt.com/docs/components/modal)'s content. ```vue [ChatPaletteModalExample.vue] ``` ### Within ContentSearch You can use the ChatPalette component conditionally inside [ContentSearch](https://ui.nuxt.com/docs/components/content-search)'s content to display a chatbot interface when a user selects an item. ```vue [ChatPaletteContentSearchExample.vue] ``` ## API ### Props ```ts /** * Props for the ChatPalette component */ interface ChatPaletteProps { /** * The element or component this component should render as. */ as?: any; ui?: { root?: ClassNameValue; prompt?: ClassNameValue; close?: ClassNameValue; content?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the ChatPalette component */ interface ChatPaletteSlots { default(): any; prompt(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { chatPalette: { slots: { root: 'relative flex-1 flex flex-col min-h-0 min-w-0', prompt: 'px-0 rounded-t-none border-t border-default', close: '', content: 'overflow-y-auto flex-1 flex flex-col py-3' } } } }) ``` ## Changelog ::component-changelog :: # ChatPrompt ## Usage The ChatPrompt component renders a `
` element and extends the [Textarea](https://ui.nuxt.com/docs/components/textarea) component so you can pass any property such as `icon`, `placeholder`, `autofocus`, etc. ::code-preview :::u-chat-prompt --- variant: subtle --- ::::u-chat-prompt-submit{.rounded-full color="neutral"} :::: #footer ::::u-select --- items: - label: Gemini 2.5 Pro value: gemini-2.5-pro icon: i-simple-icons-googlegemini - label: GPT-4o value: gpt-4o icon: i-simple-icons-openai - label: Claude 3.5 Sonnet value: claude-3.5-sonnet icon: i-simple-icons-anthropic - label: Llama 4 value: llama-4 icon: i-simple-icons-ollama icon: i-simple-icons-openai modelValue: gpt-4o placeholder: Select a model variant: ghost --- :::: ::: :: ::note The ChatPrompt handles the following events: - The form is submitted when the user presses `` or when the user clicks on the submit button. - The textarea is blurred when `` is pressed and emits a `close` event. :: ### Variant Use the `variant` prop to change the style of the prompt. Defaults to `outline`. ```vue ``` ## Examples ::note{target="_blank" to="https://ai-sdk.dev/docs/getting-started/nuxt"} These chat components are designed to be used with the **AI SDK v5** from **Vercel AI SDK**. :: ::callout --- icon: i-simple-icons-github target: _blank to: https://github.com/nuxt-ui-templates/chat --- Check out the source code of our **AI Chat template** on GitHub for a real-life example. :: ### Within a page Use the ChatPrompt component with the `Chat` class from AI SDK v5 to display a chat prompt within a page. Pass the `input` prop alongside the `error` prop to disable the textarea when an error occurs. ```vue [pages/[id\\].vue] {2,5,13-17,34,36} ``` You can also use it as a starting point for a chat interface. ```vue [pages/index.vue] {2,4,8-15,24,26} ``` ## API ### Props ```ts /** * Props for the ChatPrompt component */ interface ChatPromptProps { /** * The element or component this component should render as. * @default "\"form\"" */ as?: any; /** * The placeholder text for the textarea. */ placeholder?: string | undefined; variant?: "outline" | "soft" | "subtle" | "naked" | undefined; error?: Error | undefined; ui?: ({ root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; base?: ClassNameValue; } & { root?: ClassNameValue; base?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailing?: ClassNameValue; trailingIcon?: ClassNameValue; }) | undefined; disabled?: boolean | undefined; /** * Display an icon based on the `leading` and `trailing` props. */ icon?: string | object | undefined; /** * Display an avatar on the left side. */ avatar?: AvatarProps | undefined; /** * When `true`, the loading icon will be displayed. */ loading?: boolean | undefined; /** * The icon when the `loading` prop is `true`. */ loadingIcon?: string | object | undefined; /** * @default "1" */ rows?: number | undefined; /** * @default "true" */ autofocus?: boolean | undefined; autofocusDelay?: number | undefined; /** * @default "true" */ autoresize?: boolean | undefined; autoresizeDelay?: number | undefined; maxrows?: number | undefined; /** * @default "\"\"" */ modelValue?: string | undefined; } ``` ### Slots ```ts /** * Slots for the ChatPrompt component */ interface ChatPromptSlots { header(): any; footer(): any; leading(): any; default(): any; trailing(): any; } ``` ### Emits ```ts /** * Emitted events for the ChatPrompt component */ interface ChatPromptEmits { close: (payload: [event: Event]) => void; submit: (payload: [event: Event]) => void; update:modelValue: (payload: [value: string]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { chatPrompt: { slots: { root: 'relative flex flex-col items-stretch gap-2 px-2.5 py-2 w-full rounded-lg backdrop-blur', header: 'flex items-center gap-1.5', body: 'items-start', footer: 'flex items-center justify-between gap-1.5', base: 'text-base/5' }, variants: { variant: { outline: { root: 'bg-default/75 ring ring-default' }, soft: { root: 'bg-elevated/50' }, subtle: { root: 'bg-elevated/50 ring ring-default' }, naked: { root: '' } } }, defaultVariants: { variant: 'outline' } } } }) ``` ## Changelog ::component-changelog :: # ChatPromptSubmit ## Usage The ChatPromptSubmit component is used inside the [ChatPrompt](https://ui.nuxt.com/docs/components/chat-prompt) component to submit the prompt. It automatically handles the different `status` values to control the chat. It extends the [Button](https://ui.nuxt.com/docs/components/button) component, so you can pass any property such as `color`, `variant`, `size`, etc. ::code-preview :::u-chat-prompt-submit ::: #code ```vue ``` :: ::note You can also use it inside the `footer` slot of the [`ChatPrompt`](https://ui.nuxt.com/docs/components/chat-prompt) component. :: ### Ready When its status is `ready`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}, use the `color`, `variant` and `icon` props to customize the Button. Defaults to: - `color="primary"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `variant="solid"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `icon="i-lucide-arrow-up"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.arrowUp` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.arrowUp` key. ::: :: ### Submitted When its status is `submitted`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}, use the `submitted-color`, `submitted-variant` and `submitted-icon` props to customize the Button. Defaults to: - `submittedColor="neutral"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `submittedVariant="subtle"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `submittedIcon="i-lucide-square"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ::note The `stop` event is emitted when the user clicks on the Button. :: ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.stop` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.stop` key. ::: :: ### Streaming When its status is `streaming`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}, use the `streaming-color`, `streaming-variant` and `streaming-icon` props to customize the Button. Defaults to: - `streamingColor="neutral"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `streamingVariant="subtle"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `streamingIcon="i-lucide-square"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ::note The `stop` event is emitted when the user clicks on the Button. :: ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.stop` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.stop` key. ::: :: ### Error When its status is `error`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}, use the `error-color`, `error-variant` and `error-icon` props to customize the Button. Defaults to: - `errorColor="error"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `errorVariant="soft"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `errorIcon="i-lucide-rotate-ccw"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ::note The `reload` event is emitted when the user clicks on the Button. :: ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.reload` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.reload` key. ::: :: ## Examples ::note{target="_blank" to="https://ai-sdk.dev/docs/getting-started/nuxt"} These chat components are designed to be used with the **AI SDK v5** from **Vercel AI SDK**. :: ::callout --- icon: i-simple-icons-github target: _blank to: https://github.com/nuxt-ui-templates/chat --- Check out the source code of our **AI Chat template** on GitHub for a real-life example. :: ### Within a page Use the ChatPromptSubmit component with the `Chat` class from AI SDK v5 to display a chat prompt within a page. Pass the `status` prop and listen to the `stop` and `reload` events to control the chat. ```vue [pages/[id\\].vue] {2,7-11,35} ``` ## API ### Props ```ts /** * Props for the ChatPromptSubmit component */ interface ChatPromptSubmitProps { /** * @default "\"ready\"" */ status?: ChatStatus | undefined; /** * The icon displayed in the button when the status is `ready`. */ icon?: string | object | undefined; /** * The color of the button when the status is `ready`. */ color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; /** * The variant of the button when the status is `ready`. */ variant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; /** * The icon displayed in the button when the status is `streaming`. */ streamingIcon?: string | object | undefined; /** * The color of the button when the status is `streaming`. * @default "\"neutral\"" */ streamingColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; /** * The variant of the button when the status is `streaming`. * @default "\"subtle\"" */ streamingVariant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; /** * The icon displayed in the button when the status is `submitted`. */ submittedIcon?: string | object | undefined; /** * The color of the button when the status is `submitted`. * @default "\"neutral\"" */ submittedColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; /** * The variant of the button when the status is `submitted`. * @default "\"subtle\"" */ submittedVariant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; /** * The icon displayed in the button when the status is `error`. */ errorIcon?: string | object | undefined; /** * The color of the button when the status is `error`. * @default "\"error\"" */ errorColor?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; /** * The variant of the button when the status is `error`. * @default "\"soft\"" */ errorVariant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; ui?: ({ base?: ClassNameValue; } & { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; }) | undefined; label?: string | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; } ``` ### Slots ```ts /** * Slots for the ChatPromptSubmit component */ interface ChatPromptSubmitSlots { leading(): any; default(): any; trailing(): any; } ``` ### Emits ```ts /** * Emitted events for the ChatPromptSubmit component */ interface ChatPromptSubmitEmits { stop: (payload: []) => void; reload: (payload: []) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { chatPromptSubmit: { slots: { base: '' } } } }) ``` ## Changelog ::component-changelog :: # Checkbox ## Usage Use the `v-model` directive to control the checked state of the Checkbox. ```vue ``` Use the `default-value` prop to set the initial value when you do not need to control its state. ```vue ``` ### Indeterminate Use the `indeterminate` value in the `v-model` directive or `default-value` prop to set the Checkbox to an [indeterminate state](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes){rel="nofollow"}. ```vue ``` ### Indeterminate Icon Use the `indeterminate-icon` prop to customize the indeterminate icon. Defaults to `i-lucide-minus`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.minus` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.minus` key. ::: :: ### Label Use the `label` prop to set the label of the Checkbox. ```vue ``` When using the `required` prop, an asterisk is added next to the label. ```vue ``` ### Description Use the `description` prop to set the description of the Checkbox. ```vue ``` ### Icon Use the `icon` prop to set the icon of the Checkbox when it is checked. Defaults to `i-lucide-check`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key. ::: :: ### Color Use the `color` prop to change the color of the Checkbox. ```vue ``` ### Variant Use the `variant` prop to change the variant of the Checkbox. ```vue ``` ### Size Use the `size` prop to change the size of the Checkbox. ```vue ``` ### Indicator Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`. ```vue ``` ### Disabled Use the `disabled` prop to disable the Checkbox. ```vue ``` ## API ### Props ```ts /** * Props for the Checkbox component */ interface CheckboxProps { /** * The element or component this component should render as. */ as?: any; label?: string | undefined; description?: string | undefined; color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined; variant?: "card" | "list" | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; /** * Position of the indicator. */ indicator?: "start" | "end" | "hidden" | undefined; /** * The icon displayed when checked. */ icon?: string | object | undefined; /** * The icon displayed when the checkbox is indeterminate. */ indeterminateIcon?: string | object | undefined; ui?: { root?: ClassNameValue; container?: ClassNameValue; base?: ClassNameValue; indicator?: ClassNameValue; icon?: ClassNameValue; wrapper?: ClassNameValue; label?: ClassNameValue; description?: ClassNameValue; } | undefined; /** * When `true`, prevents the user from interacting with the checkbox */ disabled?: boolean | undefined; /** * The name of the field. Submitted with its owning form as part of a name/value pair. */ name?: string | undefined; /** * Id of the element */ id?: string | undefined; /** * When `true`, indicates that the user must set the value before the owning form can be submitted. */ required?: boolean | undefined; /** * The value of the checkbox when it is initially rendered. Use when you do not need to control its value. */ defaultValue?: boolean | "indeterminate" | undefined; /** * The value given as data when submitted with a `name`. */ value?: AcceptableValue | undefined; /** * @default "undefined" */ modelValue?: boolean | "indeterminate" | undefined; } ``` ### Slots ```ts /** * Slots for the Checkbox component */ interface CheckboxSlots { label(): any; description(): any; } ``` ### Emits ```ts /** * Emitted events for the Checkbox component */ interface CheckboxEmits { change: (payload: [event: Event]) => void; update:modelValue: (payload: [value: boolean | "indeterminate"]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { checkbox: { slots: { root: 'relative flex items-start', container: 'flex items-center', base: 'rounded-sm ring ring-inset ring-accented overflow-hidden focus-visible:outline-2 focus-visible:outline-offset-2', indicator: 'flex items-center justify-center size-full text-inverted', icon: 'shrink-0 size-full', wrapper: 'w-full', label: 'block font-medium text-default', description: 'text-muted' }, variants: { color: { primary: { base: 'focus-visible:outline-primary', indicator: 'bg-primary' }, secondary: { base: 'focus-visible:outline-secondary', indicator: 'bg-secondary' }, success: { base: 'focus-visible:outline-success', indicator: 'bg-success' }, info: { base: 'focus-visible:outline-info', indicator: 'bg-info' }, warning: { base: 'focus-visible:outline-warning', indicator: 'bg-warning' }, error: { base: 'focus-visible:outline-error', indicator: 'bg-error' }, neutral: { base: 'focus-visible:outline-inverted', indicator: 'bg-inverted' } }, variant: { list: { root: '' }, card: { root: 'border border-muted rounded-lg' } }, indicator: { start: { root: 'flex-row', wrapper: 'ms-2' }, end: { root: 'flex-row-reverse', wrapper: 'me-2' }, hidden: { base: 'sr-only', wrapper: 'text-center' } }, size: { xs: { base: 'size-3', container: 'h-4', wrapper: 'text-xs' }, sm: { base: 'size-3.5', container: 'h-4', wrapper: 'text-xs' }, md: { base: 'size-4', container: 'h-5', wrapper: 'text-sm' }, lg: { base: 'size-4.5', container: 'h-5', wrapper: 'text-sm' }, xl: { base: 'size-5', container: 'h-6', wrapper: 'text-base' } }, required: { true: { label: "after:content-['*'] after:ms-0.5 after:text-error" } }, disabled: { true: { base: 'cursor-not-allowed opacity-75', label: 'cursor-not-allowed opacity-75', description: 'cursor-not-allowed opacity-75' } }, checked: { true: '' } }, compoundVariants: [ { size: 'xs', variant: 'card', class: { root: 'p-2.5' } }, { size: 'sm', variant: 'card', class: { root: 'p-3' } }, { size: 'md', variant: 'card', class: { root: 'p-3.5' } }, { size: 'lg', variant: 'card', class: { root: 'p-4' } }, { size: 'xl', variant: 'card', class: { root: 'p-4.5' } }, { color: 'primary', variant: 'card', class: { root: 'has-data-[state=checked]:border-primary' } }, { color: 'secondary', variant: 'card', class: { root: 'has-data-[state=checked]:border-secondary' } }, { color: 'success', variant: 'card', class: { root: 'has-data-[state=checked]:border-success' } }, { color: 'info', variant: 'card', class: { root: 'has-data-[state=checked]:border-info' } }, { color: 'warning', variant: 'card', class: { root: 'has-data-[state=checked]:border-warning' } }, { color: 'error', variant: 'card', class: { root: 'has-data-[state=checked]:border-error' } }, { color: 'neutral', variant: 'card', class: { root: 'has-data-[state=checked]:border-inverted' } }, { variant: 'card', disabled: true, class: { root: 'cursor-not-allowed opacity-75' } } ], defaultVariants: { size: 'md', color: 'primary', variant: 'list', indicator: 'start' } } } }) ``` ## Changelog ::component-changelog :: # CheckboxGroup ## Usage Use the `v-model` directive to control the value of the CheckboxGroup or the `default-value` prop to set the initial value when you do not need to control its state. ```vue ``` ### Items Use the `items` prop as an array of strings or numbers: ```vue ``` You can also pass an array of objects with the following properties: - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `description?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`value?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#value-key) - `disabled?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue, container?: ClassNameValue, base?: ClassNameValue, 'indicator'?: ClassNameValue, icon?: ClassNameValue, wrapper?: ClassNameValue, label?: ClassNameValue, description?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue ``` ::caution When using objects, you need to reference the `value` property of the object in the `v-model` directive or the `default-value` prop. :: ### Value Key You can change the property that is used to set the value by using the `value-key` prop. Defaults to `value`. ```vue ``` ### Legend Use the `legend` prop to set the legend of the CheckboxGroup. ```vue ``` ### Color Use the `color` prop to change the color of the CheckboxGroup. ```vue ``` ### Variant Use the `variant` prop to change the variant of the CheckboxGroup. ```vue ``` ### Size Use the `size` prop to change the size of the CheckboxGroup. ```vue ``` ### Orientation Use the `orientation` prop to change the orientation of the CheckboxGroup. Defaults to `vertical`. ```vue ``` ### Indicator Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`. ```vue ``` ### Disabled Use the `disabled` prop to disable the CheckboxGroup. ```vue ``` ## API ### Props ```ts /** * Props for the CheckboxGroup component */ interface CheckboxGroupProps { /** * The element or component this component should render as. */ as?: any; legend?: string | undefined; /** * When `items` is an array of objects, select the field to use as the value. * @default "\"value\" as never" */ valueKey?: GetItemKeys | undefined; /** * When `items` is an array of objects, select the field to use as the label. * @default "\"label\"" */ labelKey?: GetItemKeys | undefined; /** * When `items` is an array of objects, select the field to use as the description. * @default "\"description\"" */ descriptionKey?: GetItemKeys | undefined; items?: CheckboxGroupItem[] | undefined; /** * The controlled value of the CheckboxGroup. Can be bind as `v-model`. */ modelValue?: any[] | undefined; /** * The value of the CheckboxGroup when initially rendered. Use when you do not need to control the state of the CheckboxGroup. */ defaultValue?: any[] | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; variant?: "table" | "list" | "card" | undefined; /** * The orientation the checkbox buttons are laid out. * @default "\"vertical\"" */ orientation?: Orientation$1 | undefined; ui?: ({ root?: ClassNameValue; fieldset?: ClassNameValue; legend?: ClassNameValue; item?: ClassNameValue; } & { root?: ClassNameValue; container?: ClassNameValue; base?: ClassNameValue; indicator?: ClassNameValue; icon?: ClassNameValue; wrapper?: ClassNameValue; label?: ClassNameValue; description?: ClassNameValue; }) | undefined; /** * When `true`, prevents the user from interacting with the checkboxes */ disabled?: boolean | undefined; /** * Whether keyboard navigation should loop around */ loop?: boolean | undefined; /** * The name of the field. Submitted with its owning form as part of a name/value pair. */ name?: string | undefined; /** * When `true`, indicates that the user must set the value before the owning form can be submitted. */ required?: boolean | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; /** * Position of the indicator. */ indicator?: "start" | "end" | "hidden" | undefined; /** * The icon displayed when checked. */ icon?: string | object | undefined; } ``` ### Slots ```ts /** * Slots for the CheckboxGroup component */ interface CheckboxGroupSlots { legend(): any; label(): any; description(): any; } ``` ### Emits ```ts /** * Emitted events for the CheckboxGroup component */ interface CheckboxGroupEmits { update:modelValue: (payload: [value: CheckboxGroupItem[]]) => void; change: (payload: [event: Event]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { checkboxGroup: { slots: { root: 'relative', fieldset: 'flex gap-x-2', legend: 'mb-1 block font-medium text-default', item: '' }, variants: { orientation: { horizontal: { fieldset: 'flex-row' }, vertical: { fieldset: 'flex-col' } }, color: { primary: {}, secondary: {}, success: {}, info: {}, warning: {}, error: {}, neutral: {} }, variant: { list: {}, card: {}, table: { item: 'border border-muted' } }, size: { xs: { fieldset: 'gap-y-0.5', legend: 'text-xs' }, sm: { fieldset: 'gap-y-0.5', legend: 'text-xs' }, md: { fieldset: 'gap-y-1', legend: 'text-sm' }, lg: { fieldset: 'gap-y-1', legend: 'text-sm' }, xl: { fieldset: 'gap-y-1.5', legend: 'text-base' } }, required: { true: { legend: "after:content-['*'] after:ms-0.5 after:text-error" } } }, compoundVariants: [ { size: 'xs', variant: 'table', class: { item: 'p-2.5' } }, { size: 'sm', variant: 'table', class: { item: 'p-3' } }, { size: 'md', variant: 'table', class: { item: 'p-3.5' } }, { size: 'lg', variant: 'table', class: { item: 'p-4' } }, { size: 'xl', variant: 'table', class: { item: 'p-4.5' } }, { orientation: 'horizontal', variant: 'table', class: { item: 'first-of-type:rounded-s-lg last-of-type:rounded-e-lg', fieldset: 'gap-0 -space-x-px' } }, { orientation: 'vertical', variant: 'table', class: { item: 'first-of-type:rounded-t-lg last-of-type:rounded-b-lg', fieldset: 'gap-0 -space-y-px' } }, { color: 'primary', variant: 'table', class: { item: 'has-data-[state=checked]:bg-primary/10 has-data-[state=checked]:border-primary/50 has-data-[state=checked]:z-[1]' } }, { color: 'secondary', variant: 'table', class: { item: 'has-data-[state=checked]:bg-secondary/10 has-data-[state=checked]:border-secondary/50 has-data-[state=checked]:z-[1]' } }, { color: 'success', variant: 'table', class: { item: 'has-data-[state=checked]:bg-success/10 has-data-[state=checked]:border-success/50 has-data-[state=checked]:z-[1]' } }, { color: 'info', variant: 'table', class: { item: 'has-data-[state=checked]:bg-info/10 has-data-[state=checked]:border-info/50 has-data-[state=checked]:z-[1]' } }, { color: 'warning', variant: 'table', class: { item: 'has-data-[state=checked]:bg-warning/10 has-data-[state=checked]:border-warning/50 has-data-[state=checked]:z-[1]' } }, { color: 'error', variant: 'table', class: { item: 'has-data-[state=checked]:bg-error/10 has-data-[state=checked]:border-error/50 has-data-[state=checked]:z-[1]' } }, { color: 'neutral', variant: 'table', class: { item: 'has-data-[state=checked]:bg-elevated has-data-[state=checked]:border-inverted/50 has-data-[state=checked]:z-[1]' } }, { variant: 'table', disabled: true, class: { item: 'cursor-not-allowed opacity-75' } } ], defaultVariants: { size: 'md', variant: 'list', color: 'primary' } } } }) ``` ## Changelog ::component-changelog :: # Chip ## Usage Wrap any component with a Chip to display an indicator. ```vue ``` ### Color Use the `color` prop to change the color of the Chip. ```vue ``` ### Size Use the `size` prop to change the size of the Chip. ```vue ``` ### Text Use the `text` prop to set the text of the Chip. ```vue ``` ### Position Use the `position` prop to change the position of the Chip. ```vue ``` ### Inset Use the `inset` prop to display the Chip inside the component. This is useful when dealing with rounded components. ```vue ``` ### Standalone Use the `standalone` prop alongside the `inset` prop to display the Chip inline. ```vue ``` ::note It's used this way in the [`CommandPalette`](https://ui.nuxt.com/docs/components/command-palette), [`InputMenu`](https://ui.nuxt.com/docs/components/input-menu), [`Select`](https://ui.nuxt.com/docs/components/select) or [`SelectMenu`](https://ui.nuxt.com/docs/components/select-menu) components for example. :: ## Examples ### Control visibility You can control the visibility of the Chip using the `show` prop. ```vue [ChipShowExample.vue] ``` ::note In this example, the Chip has a color per status and is displayed when the status is not `offline`. :: ## API ### Props ```ts /** * Props for the Chip component */ interface ChipProps { /** * The element or component this component should render as. */ as?: any; /** * Display some text inside the chip. */ text?: string | number | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | "3xs" | "2xs" | "2xl" | "3xl" | undefined; /** * The position of the chip. */ position?: "top-right" | "bottom-right" | "top-left" | "bottom-left" | undefined; /** * When `true`, keep the chip inside the component for rounded elements. * @default "false" */ inset?: boolean | undefined; /** * When `true`, render the chip relatively to the parent. * @default "false" */ standalone?: boolean | undefined; ui?: { root?: ClassNameValue; base?: ClassNameValue; } | undefined; /** * @default "true" */ show?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the Chip component */ interface ChipSlots { default(): any; content(): any; } ``` ### Emits ```ts /** * Emitted events for the Chip component */ interface ChipEmits { update:show: (payload: [value: boolean]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { chip: { slots: { root: 'relative inline-flex items-center justify-center shrink-0', base: 'rounded-full ring ring-bg flex items-center justify-center text-inverted font-medium whitespace-nowrap' }, variants: { color: { primary: 'bg-primary', secondary: 'bg-secondary', success: 'bg-success', info: 'bg-info', warning: 'bg-warning', error: 'bg-error', neutral: 'bg-inverted' }, size: { '3xs': 'h-[4px] min-w-[4px] text-[4px]', '2xs': 'h-[5px] min-w-[5px] text-[5px]', xs: 'h-[6px] min-w-[6px] text-[6px]', sm: 'h-[7px] min-w-[7px] text-[7px]', md: 'h-[8px] min-w-[8px] text-[8px]', lg: 'h-[9px] min-w-[9px] text-[9px]', xl: 'h-[10px] min-w-[10px] text-[10px]', '2xl': 'h-[11px] min-w-[11px] text-[11px]', '3xl': 'h-[12px] min-w-[12px] text-[12px]' }, position: { 'top-right': 'top-0 right-0', 'bottom-right': 'bottom-0 right-0', 'top-left': 'top-0 left-0', 'bottom-left': 'bottom-0 left-0' }, inset: { false: '' }, standalone: { false: 'absolute' } }, compoundVariants: [ { position: 'top-right', inset: false, class: '-translate-y-1/2 translate-x-1/2 transform' }, { position: 'bottom-right', inset: false, class: 'translate-y-1/2 translate-x-1/2 transform' }, { position: 'top-left', inset: false, class: '-translate-y-1/2 -translate-x-1/2 transform' }, { position: 'bottom-left', inset: false, class: 'translate-y-1/2 -translate-x-1/2 transform' } ], defaultVariants: { size: 'md', color: 'primary', position: 'top-right' } } } }) ``` ## Changelog ::component-changelog :: # Collapsible ## Usage Use a [Button](https://ui.nuxt.com/docs/components/button) or any other component in the default slot of the Collapsible. Then, use the `#content` slot to add the content displayed when the Collapsible is open. ```vue ``` ### Unmount Use the `unmount-on-hide` prop to prevent the content from being unmounted when the Collapsible is collapsed. Defaults to `true`. ```vue ``` ::note You can inspect the DOM to see the content being rendered. :: ### Disabled Use the `disabled` prop to disable the Collapsible. ```vue ``` ## Examples ### Control open state You can control the open state by using the `default-open` prop or the `v-model:open` directive. ```vue [CollapsibleOpenExample.vue] ``` ::note In this example, leveraging [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts), you can toggle the Collapsible by pressing ``. :: ::tip This allows you to move the trigger outside of the Collapsible or remove it entirely. :: ### With rotating icon Here is an example with a rotating icon in the Button that indicates the open state of the Collapsible. ```vue [CollapsibleIconExample.vue] ``` ## API ### Props ```ts /** * Props for the Collapsible component */ interface CollapsibleProps { /** * The element or component this component should render as. */ as?: any; ui?: { root?: ClassNameValue; content?: ClassNameValue; } | undefined; /** * When `true`, prevents the user from interacting with the collapsible. */ disabled?: boolean | undefined; /** * The open state of the collapsible when it is initially rendered.
Use when you do not need to control its open state. */ defaultOpen?: boolean | undefined; /** * The controlled open state of the collapsible. Can be binded with `v-model`. */ open?: boolean | undefined; /** * When `true`, the element will be unmounted on closed state. * @default "true" */ unmountOnHide?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the Collapsible component */ interface CollapsibleSlots { default(): any; content(): any; } ``` ### Emits ```ts /** * Emitted events for the Collapsible component */ interface CollapsibleEmits { update:open: (payload: [value: boolean]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { collapsible: { slots: { root: '', content: 'data-[state=open]:animate-[collapsible-down_200ms_ease-out] data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden' } } } }) ``` ## Changelog ::component-changelog :: # ColorModeAvatar ## Usage The ColorModeAvatar component extends the [Avatar](https://ui.nuxt.com/docs/components/avatar) component, so you can pass any property such as `size`, `icon`, etc. Use the `light` and `dark` props to define the source for light and dark mode. ```vue ``` ::note Switch between light and dark mode to see the different images: :u-color-mode-select{size="sm"} :: ## API ### Props ```ts /** * Props for the ColorModeAvatar component */ interface ColorModeAvatarProps { light: string; dark: string; /** * The element or component this component should render as. */ as?: any; ui?: { root?: ClassNameValue; image?: ClassNameValue; fallback?: ClassNameValue; icon?: ClassNameValue; } | undefined; alt?: string | undefined; icon?: string | object | undefined; text?: string | undefined; size?: "md" | "3xs" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | undefined; chip?: boolean | ChipProps | undefined; } ``` ## Changelog ::component-changelog{prefix="color-mode"} :: # ColorModeButton ## Usage The ColorModeButton component extends the [Button](https://ui.nuxt.com/docs/components/button) component, so you can pass any property such as `color`, `variant`, `size`, etc. ```vue ``` ::note The button defaults to `color="neutral"` and `variant="ghost"`. :: ## Examples ### With custom icons ::framework-only #nuxt :::div Use the `app.config.ts` to customize the icon with the `ui.icons` property: ```ts [app.config.ts] export default defineAppConfig({ ui: { icons: { light: 'i-ph-sun', dark: 'i-ph-moon' } } }) ``` ::: #vue :::div Use the `vite.config.ts` to customize the icon with the `ui.icons` property: ```ts [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: { icons: { light: 'i-ph-sun', dark: 'i-ph-moon' } } }) ] }) ``` ::: :: ### With fallback slot As the button is wrapped in a [ClientOnly](https://nuxt.com/docs/api/components/client-only){rel="nofollow"} component, you can pass a `fallback` slot to display a placeholder while the component is loading. ```vue ``` ## API ### Props ```ts /** * Props for the ColorModeButton component */ interface ColorModeButtonProps { /** * @default "\"neutral\"" */ color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; /** * @default "\"ghost\"" */ variant?: "link" | "ghost" | "solid" | "outline" | "soft" | "subtle" | undefined; /** * The element or component this component should render as when not a link. */ as?: any; ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; disabled?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the ColorModeButton component */ interface ColorModeButtonSlots { fallback(): any; } ``` ## Changelog ::component-changelog{prefix="color-mode"} :: # ColorModeImage ## Usage The ColorModeImage component uses the `` component when [`@nuxt/image`](https://github.com/nuxt/image){rel="nofollow"} is installed, falling back to `img` otherwise. ```vue ``` ::note Switch between light and dark mode to see the different images: :u-color-mode-select{size="sm"} :: ## API ### Props ```ts /** * Props for the ColorModeImage component */ interface ColorModeImageProps { dark: string; light: string; } ``` ## Changelog ::component-changelog{prefix="color-mode"} :: # ColorModeSelect ## Usage The ColorModeSelect component extends the [SelectMenu](https://ui.nuxt.com/docs/components/select-menu) component, so you can pass any property such as `color`, `variant`, `size`, etc. ```vue ``` ## Examples ### With custom icons ::framework-only #nuxt :::div Use the `app.config.ts` to customize the icon with the `ui.icons` property: ```ts [app.config.ts] export default defineAppConfig({ ui: { icons: { system: 'i-ph-desktop', light: 'i-ph-sun', dark: 'i-ph-moon' } } }) ``` ::: #vue :::div Use the `vite.config.ts` to customize the icon with the `ui.icons` property: ```ts [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: { icons: { light: 'i-ph-sun', dark: 'i-ph-moon' } } }) ] }) ``` ::: :: ## API ### Props ```ts /** * Props for the ColorModeSelect component */ interface ColorModeSelectProps { /** * The icon displayed to open the menu. */ trailingIcon?: string | object | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; ui?: { base?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailing?: ClassNameValue; trailingIcon?: ClassNameValue; value?: ClassNameValue; placeholder?: ClassNameValue; arrow?: ClassNameValue; content?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; empty?: ClassNameValue; label?: ClassNameValue; separator?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemLeadingChip?: ClassNameValue; itemLeadingChipSize?: ClassNameValue; itemTrailing?: ClassNameValue; itemTrailingIcon?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; input?: ClassNameValue; focusScope?: ClassNameValue; } | undefined; /** * The content of the menu. */ content?: (Omit & Partial>) | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; /** * When `true`, prevents the user from interacting with listbox */ disabled?: boolean | undefined; variant?: "ghost" | "outline" | "soft" | "subtle" | "none" | undefined; /** * The icon displayed when an item is selected. */ selectedIcon?: string | object | undefined; /** * Display an arrow alongside the menu. */ arrow?: boolean | Omit | undefined; /** * Render the menu in a portal. */ portal?: string | boolean | HTMLElement | undefined; } ``` ## Changelog ::component-changelog{prefix="color-mode"} :: # ColorModeSwitch ## Usage The ColorModeSwitch component extends the [Switch](https://ui.nuxt.com/docs/components/switch) component, so you can pass any property such as `color`, `size`, etc. ```vue ``` ## Examples ### With custom icons ::framework-only #nuxt :::div Use the `app.config.ts` to customize the icon with the `ui.icons` property: ```ts [app.config.ts] export default defineAppConfig({ ui: { icons: { light: 'i-ph-sun', dark: 'i-ph-moon' } } }) ``` ::: #vue :::div Use the `vite.config.ts` to customize the icon with the `ui.icons` property: ```ts [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: { icons: { light: 'i-ph-sun', dark: 'i-ph-moon' } } }) ] }) ``` ::: :: ## API ### Props ```ts /** * Props for the ColorModeSwitch component */ interface ColorModeSwitchProps { /** * The element or component this component should render as. */ as?: any; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; ui?: { root?: ClassNameValue; base?: ClassNameValue; container?: ClassNameValue; thumb?: ClassNameValue; icon?: ClassNameValue; wrapper?: ClassNameValue; label?: ClassNameValue; description?: ClassNameValue; } | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; /** * When `true`, prevents the user from interacting with the switch. */ disabled?: boolean | undefined; } ``` ## Changelog ::component-changelog{prefix="color-mode"} :: # ColorPicker ## Usage Use the `v-model` directive to control the value of the ColorPicker. ```vue ``` Use the `default-value` prop to set the initial value when you do not need to control its state. ```vue ``` ### RGB Format Use the `format` prop to set `rgb` value of the ColorPicker. ```vue ``` ### HSL Format Use the `format` prop to set `hsl` value of the ColorPicker. ```vue ``` ### CMYK Format Use the `format` prop to set `cmyk` value of the ColorPicker. ```vue ``` ### CIELab Format Use the `format` prop to set `lab` value of the ColorPicker. ```vue ``` ### Throttle Use the `throttle` prop to set the throttle value of the ColorPicker. ```vue ``` ### Size Use the `size` prop to set the size of the ColorPicker. ```vue ``` ### Disabled Use the `disabled` prop to disable the ColorPicker. ```vue ``` ## Examples ### As a Color chooser Use a [Button](https://ui.nuxt.com/docs/components/button) and a [Popover](https://ui.nuxt.com/docs/components/popover) component to create a color chooser. ```vue [ColorPickerChooserExample.vue] ``` ## API ### Props ```ts /** * Props for the ColorPicker component */ interface ColorPickerProps { /** * The element or component this component should render as. */ as?: any; /** * Throttle time in ms for the color picker * @default "50" */ throttle?: number | undefined; /** * Disable the color picker */ disabled?: boolean | undefined; /** * The default value of the color picker * @default "\"#FFFFFF\"" */ defaultValue?: string | undefined; /** * Format of the color * @default "\"hex\"" */ format?: "hex" | "rgb" | "hsl" | "cmyk" | "lab" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; ui?: { root?: ClassNameValue; picker?: ClassNameValue; selector?: ClassNameValue; selectorBackground?: ClassNameValue; selectorThumb?: ClassNameValue; track?: ClassNameValue; trackThumb?: ClassNameValue; } | undefined; modelValue?: string | undefined; } ``` ### Emits ```ts /** * Emitted events for the ColorPicker component */ interface ColorPickerEmits { update:modelValue: (payload: [value: string | undefined]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { colorPicker: { slots: { root: 'data-[disabled]:opacity-75', picker: 'flex gap-4', selector: 'rounded-md touch-none', selectorBackground: 'w-full h-full relative rounded-md', selectorThumb: '-translate-y-1/2 -translate-x-1/2 absolute size-4 ring-2 ring-(--color-white) rounded-full cursor-pointer data-[disabled]:cursor-not-allowed', track: 'w-[8px] relative rounded-md touch-none', trackThumb: 'absolute transform -translate-y-1/2 -translate-x-[4px] rtl:translate-x-[4px] size-4 rounded-full ring-2 ring-(--color-white) cursor-pointer data-[disabled]:cursor-not-allowed' }, variants: { size: { xs: { selector: 'w-38 h-38', track: 'h-38' }, sm: { selector: 'w-40 h-40', track: 'h-40' }, md: { selector: 'w-42 h-42', track: 'h-42' }, lg: { selector: 'w-44 h-44', track: 'h-44' }, xl: { selector: 'w-46 h-46', track: 'h-46' } } }, compoundVariants: [], defaultVariants: { size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # CommandPalette ## Usage Use the `v-model` directive to control the value of the CommandPalette or the `default-value` prop to set the initial value when you do not need to control its state. ```vue ``` ::tip{to="https://ui.nuxt.com/#control-selected-items"} You can also use the `@update:model-value` event to listen to the selected item(s). :: ### Groups The CommandPalette component filters groups and ranks matching commands by relevance as users type. It provides dynamic, instant search results for efficient command discovery. Use the `groups` prop as an array of objects with the following properties: - `id: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `items?: CommandPaletteItem[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`ignoreFilter?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-ignore-filter) - [`postFilter?: (searchTerm: string, items: T[]) => T[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-post-filtered-items) - `highlightedIcon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ::caution You must provide an `id` for each group otherwise the group will be ignored. :: Each group contains an `items` array of objects that define the commands. Each item can have the following properties: - `prefix?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `suffix?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `avatar?: AvatarProps`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `chip?: ChipProps`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `kbds?: string[] | KbdProps[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `active?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `loading?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `disabled?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot) - `placeholder?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `children?: CommandPaletteItem[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `onSelect?: (e: Event) => void`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelPrefix?: ClassNameValue, itemLabelBase?: ClassNameValue, itemLabelSuffix?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue, itemTrailingHighlightedIcon?: ClassNameValue, itemTrailingIcon?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` ::tip{to="https://ui.nuxt.com/#with-children-in-items"} Each item can take a `children` array of objects with the following properties to create submenus: :: ### Multiple Use the `multiple` prop to allow multiple selections. ```vue ``` ::caution Ensure to pass an array to the `default-value` prop or the `v-model` directive. :: ### Placeholder Use the `placeholder` prop to change the placeholder text. ```vue ``` ### Icon Use the `icon` prop to customize the input [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-search`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.search` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.search` key. ::: :: ### Selected Icon Use the `selected-icon` prop to customize the selected item [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-check`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.check` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.check` key. ::: :: ### Trailing Icon Use the `trailing-icon` prop to customize the trailing [Icon](https://ui.nuxt.com/docs/components/icon) when an item has children. Defaults to `i-lucide-chevron-right`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.chevronRight` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.chevronRight` key. ::: :: ### Loading Use the `loading` prop to show a loading icon on the CommandPalette. ```vue ``` ### Loading Icon Use the `loading-icon` prop to customize the loading icon. Defaults to `i-lucide-loader-circle`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.loading` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.loading` key. ::: :: ### Close Use the `close` prop to display a [Button](https://ui.nuxt.com/docs/components/button) to dismiss the CommandPalette. ::tip An `update:open` event will be emitted when the close button is clicked. :: ```vue ``` You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue ``` ### Close Icon Use the `close-icon` prop to customize the close button [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-x`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.close` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.close` key. ::: :: ### Back Use the `back` prop to customize or hide the back button (with `false` value) displayed when navigating into a submenu. You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue ``` ### Back Icon Use the `back-icon` prop to customize the back button [Icon](https://ui.nuxt.com/docs/components/icon). Defaults to `i-lucide-arrow-left`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.arrowLeft` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.arrowLeft` key. ::: :: ### Disabled Use the `disabled` prop to disable the CommandPalette. ```vue ``` ## Examples ### Control selected item(s) You can control the selected item(s) by using the `default-value` prop or the `v-model` directive, by using the `onSelect` field on each item or by using the `@update:model-value` event. ```vue [CommandPaletteSelectExample.vue] ``` ### Control search term Use the `v-model:search-term` directive to control the search term. ```vue [CommandPaletteSearchTermExample.vue] ``` ::note This example uses the `@update:model-value` event to reset the search term when an item is selected. :: ### With children in items You can create hierarchical menus by using the `children` property in items. When an item has children, it will automatically display a chevron icon and enable navigation into a submenu. ```vue [CommandPaletteItemsChildrenExample.vue] ``` ::note When navigating into a submenu: - The search term is reset - A back button appears in the input - You can go back to the previous group by pressing the `` key :: ### With fetched items You can fetch items from an API and use them in the CommandPalette. ```vue [CommandPaletteFetchExample.vue] ``` ### With ignore filter You can set the `ignoreFilter` field to `true` on a group to disable the internal search and use your own search logic. ```vue [CommandPaletteIgnoreFilterExample.vue] ``` ::note This example uses [`refDebounced`](https://vueuse.org/shared/refDebounced/#refdebounced){rel="nofollow"} to debounce the API calls. :: ### With post-filtered items You can use the `postFilter` field on a group to filter items after the search happened. ```vue [CommandPalettePostFilterExample.vue] ``` ::note Start typing to see items with higher level appear. :: ### With custom fuse search You can use the `fuse` prop to override the options of [useFuse](https://vueuse.org/integrations/useFuse){rel="nofollow"} which defaults to: ```ts { fuseOptions: { ignoreLocation: true, threshold: 0.1, keys: ['label', 'suffix'] }, resultLimit: 12, matchAllWhenSearchEmpty: true } ``` ::tip The `fuseOptions` are the options of [Fuse.js](https://www.fusejs.io/api/options.html){rel="nofollow"}, the `resultLimit` is the maximum number of results to return and the `matchAllWhenSearchEmpty` is a boolean to match all items when the search term is empty. :: You can for example set `{ fuseOptions: { includeMatches: true } }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} to highlight the search term in the items. ```vue [CommandPaletteFuseExample.vue] ``` ### With virtualization :badge{label="4.1+"} Use the `virtualize` prop to enable virtualization for large lists as a boolean or an object with options like `{ estimateSize: 32, overscan: 12 }`. ::warning{target="_blank" to="https://github.com/unovue/reka-ui/issues/1885"} When enabled, all groups are flattened into a single list due to a limitation of Reka UI. :: ```vue [CommandPaletteVirtualizeExample.vue] ``` ### Within a Popover You can use the CommandPalette component inside a [Popover](https://ui.nuxt.com/docs/components/popover)'s content. ```vue [PopoverCommandPaletteExample.vue] ``` ### Within a Modal You can use the CommandPalette component inside a [Modal](https://ui.nuxt.com/docs/components/modal)'s content. ```vue [ModalCommandPaletteExample.vue] ``` ### Within a Drawer You can use the CommandPalette component inside a [Drawer](https://ui.nuxt.com/docs/components/drawer)'s content. ```vue [DrawerCommandPaletteExample.vue] ``` ### Listen open state When using the `close` prop, you can listen to the `update:open` event when the button is clicked. ```vue [CommandPaletteOpenExample.vue] ``` ::note This can be useful when using the CommandPalette inside a [`Modal`](https://ui.nuxt.com/docs/components/modal) for example. :: ### With footer slot Use the `#footer` slot to add custom content at the bottom of the CommandPalette, such as keyboard shortcuts help or additional actions. ```vue [CommandPaletteFooterSlotExample.vue] ``` ### With custom slot Use the `slot` property to customize a specific item or group. You will have access to the following slots: - `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-leading`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-label`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-trailing`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ group.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ group.slot }}-leading`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ group.slot }}-label`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ group.slot }}-trailing`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue [CommandPaletteCustomSlotExample.vue] ``` ::tip{to="https://ui.nuxt.com/#slots"} You can also use the `#item`, `#item-leading`, `#item-label` and `#item-trailing` slots to customize all items. :: ## API ### Props ```ts /** * Props for the CommandPalette component */ interface CommandPaletteProps { /** * The element or component this component should render as. */ as?: any; /** * The icon displayed in the input. */ icon?: string | object | undefined; /** * The icon displayed on the right side of the input. */ trailingIcon?: string | object | undefined; /** * The icon displayed when an item is selected. */ selectedIcon?: string | object | undefined; /** * The icon displayed when an item has children. */ childrenIcon?: string | object | undefined; /** * The placeholder text for the input. */ placeholder?: string | undefined; /** * Automatically focus the input when component is mounted. * @default "true" */ autofocus?: boolean | undefined; /** * Display a close button in the input (useful when inside a Modal for example). * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"} */ close?: boolean | Partial | undefined; /** * The icon displayed in the close button. */ closeIcon?: string | object | undefined; /** * Display a button to navigate back in history. * `{ size: 'md', color: 'neutral', variant: 'link' }`{lang="ts-type"} * @default "true" */ back?: boolean | ButtonProps | undefined; /** * The icon displayed in the back button. */ backIcon?: string | object | undefined; groups?: CommandPaletteGroup[] | undefined; /** * Options for [useFuse](https://vueuse.org/integrations/useFuse). */ fuse?: UseFuseOptions | undefined; /** * Enable virtualization for large lists. * Note: when enabled, all groups are flattened into a single list due to a limitation of Reka UI (https://github.com/unovue/reka-ui/issues/1885). * @default "false" */ virtualize?: boolean | { overscan?: number | undefined; estimateSize?: number | undefined; } | undefined; /** * The key used to get the label from the item. * @default "\"label\"" */ labelKey?: GetItemKeys | undefined; /** * The key used to get the description from the item. * @default "\"description\"" */ descriptionKey?: GetItemKeys | undefined; /** * Whether to preserve the order of groups as defined in the `groups` prop when filtering. * When `false`, groups will appear based on item matches. * @default "false" */ preserveGroupOrder?: boolean | undefined; ui?: { root?: ClassNameValue; input?: ClassNameValue; close?: ClassNameValue; back?: ClassNameValue; content?: ClassNameValue; footer?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; empty?: ClassNameValue; label?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemLeadingChip?: ClassNameValue; itemLeadingChipSize?: ClassNameValue; itemTrailing?: ClassNameValue; itemTrailingIcon?: ClassNameValue; itemTrailingHighlightedIcon?: ClassNameValue; itemTrailingKbds?: ClassNameValue; itemTrailingKbdsSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelBase?: ClassNameValue; itemLabelPrefix?: ClassNameValue; itemLabelSuffix?: ClassNameValue; } | undefined; /** * Whether multiple options can be selected or not. */ multiple?: boolean | undefined; /** * When `true`, prevents the user from interacting with listbox */ disabled?: boolean | undefined; /** * The controlled value of the listbox. Can be binded with with `v-model`. * @default "\"\"" */ modelValue?: AcceptableValue | AcceptableValue[] | undefined; /** * The value of the listbox when initially rendered. Use when you do not need to control the state of the Listbox */ defaultValue?: AcceptableValue | AcceptableValue[] | undefined; /** * When `true`, hover over item will trigger highlight * @default "true" */ highlightOnHover?: boolean | undefined; /** * How multiple selection should behave in the collection. */ selectionBehavior?: "replace" | "toggle" | undefined; /** * When `true`, the loading icon will be displayed. */ loading?: boolean | undefined; /** * The icon when the `loading` prop is `true`. */ loadingIcon?: string | object | undefined; /** * @default "\"\"" */ searchTerm?: string | undefined; } ``` ### Slots ```ts /** * Slots for the CommandPalette component */ interface CommandPaletteSlots { empty(): any; footer(): any; back(): any; close(): any; item(): any; item-leading(): any; item-label(): any; item-description(): any; item-trailing(): any; } ``` ### Emits ```ts /** * Emitted events for the CommandPalette component */ interface CommandPaletteEmits { update:modelValue: (payload: [value: CommandPaletteItem]) => void; highlight: (payload: [payload: { ref: HTMLElement; value: CommandPaletteItem; } | undefined]) => void; entryFocus: (payload: [event: CustomEvent]) => void; leave: (payload: [event: Event]) => void; update:open: (payload: [value: boolean]) => void; update:searchTerm: (payload: [value: string]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { commandPalette: { slots: { root: 'flex flex-col min-h-0 min-w-0 divide-y divide-default', input: '[&>input]:h-12', close: '', back: 'p-0', content: 'relative overflow-hidden flex flex-col', footer: 'p-1', viewport: 'relative scroll-py-1 overflow-y-auto flex-1 focus:outline-none', group: 'p-1 isolate', empty: 'py-6 text-center text-sm text-muted', label: 'p-1.5 text-xs font-semibold text-highlighted', item: 'group relative w-full flex items-start gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', itemLeadingIcon: 'shrink-0 size-5', itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '2xs', itemLeadingChip: 'shrink-0 size-5', itemLeadingChipSize: 'md', itemTrailing: 'ms-auto inline-flex gap-1.5 items-center', itemTrailingIcon: 'shrink-0 size-5', itemTrailingHighlightedIcon: 'shrink-0 size-5 text-dimmed hidden group-data-highlighted:inline-flex', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5', itemTrailingKbdsSize: 'md', itemWrapper: 'flex-1 flex flex-col text-start min-w-0', itemLabel: 'truncate space-x-1 text-dimmed', itemDescription: 'truncate text-muted', itemLabelBase: 'text-highlighted [&>mark]:text-inverted [&>mark]:bg-primary', itemLabelPrefix: 'text-default', itemLabelSuffix: 'text-dimmed [&>mark]:text-inverted [&>mark]:bg-primary' }, variants: { virtualize: { true: { viewport: 'p-1 isolate' }, false: { viewport: 'divide-y divide-default' } }, active: { true: { item: 'text-highlighted before:bg-elevated', itemLeadingIcon: 'text-default' }, false: { item: [ 'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50', 'transition-colors before:transition-colors' ], itemLeadingIcon: [ 'text-dimmed group-data-highlighted:not-group-data-disabled:text-default', 'transition-colors' ] } }, loading: { true: { itemLeadingIcon: 'animate-spin' } } } } } }) ``` ## Changelog ::component-changelog :: # Container ## Usage Use the default slot to center and constrain the width of your content. ::tip --- to: https://ui.nuxt.com/docs/getting-started/theme/css-variables#container --- Its max width is controlled by the `--ui-container` CSS variable. :: ```vue [ContainerExample.vue] ``` ## API ### Props ```ts /** * Props for the Container component */ interface ContainerProps { /** * The element or component this component should render as. */ as?: any; } ``` ### Slots ```ts /** * Slots for the Container component */ interface ContainerSlots { default(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { container: { base: 'w-full max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8' } } }) ``` ## Changelog ::component-changelog :: # ContentNavigation ::warning{to="https://ui.nuxt.com/docs/getting-started/integrations/content"} This component is only available when the `@nuxt/content` module is installed. :: ## Usage Use the `navigation` prop with the `navigation`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} value you get when fetching the navigation of your app. ```vue [ContentNavigationExample.vue] ``` ### Type Set the `type` prop to `single` to only allow one item to be open at a time. Defaults to `multiple`. ```vue ``` ### Color Use the `color` prop to change the color of the navigation links. ```vue ``` ### Variant Use the `variant` prop to change the variant of the navigation links. ```vue ``` ### Highlight Use the `highlight` prop to display a highlighted border for the active link. Use the `highlight-color` prop to change the color of the border. It defaults to the `color` prop. ```vue ``` ### Trailing Icon ```vue ``` ## Examples ### Within a layout Use the ContentNavigation component inside a [PageAside](https://ui.nuxt.com/docs/components/page-aside) component within a layout to display the navigation of the page: ```vue [layouts/docs.vue] {11} ``` ### Within a header Use the ContentNavigation component inside the `content` slot of a [Header](https://ui.nuxt.com/docs/components/header) component to display the navigation of the page on mobile: ```vue [components/Header.vue] {9-11} ``` ## API ### Props ```ts /** * Props for the ContentNavigation component */ interface ContentNavigationProps { /** * The element or component this component should render as. * @default "\"nav\"" */ as?: any; /** * When `true`, the tree will be opened based on the current route. * When `false`, the tree will be closed. * When `undefined` (default), the first item will be opened with `type="single"` and the first level will be opened with `type="multiple"`. * @default "undefined" */ defaultOpen?: boolean | undefined; /** * The icon displayed to toggle the accordion. */ trailingIcon?: string | object | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; variant?: "pill" | "link" | undefined; /** * Display a line next to the active link. * @default "false" */ highlight?: boolean | undefined; highlightColor?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; /** * When type is "single", prevents closing the open item when clicking its trigger. * When type is "multiple", disables the collapsible behavior. * @default "true" */ collapsible?: boolean | undefined; /** * @default "0" */ level?: number | undefined; navigation?: ContentNavigationLink[] | undefined; ui?: { root?: ClassNameValue; content?: ClassNameValue; list?: ClassNameValue; item?: ClassNameValue; listWithChildren?: ClassNameValue; itemWithChildren?: ClassNameValue; trigger?: ClassNameValue; link?: ClassNameValue; linkLeadingIcon?: ClassNameValue; linkTrailing?: ClassNameValue; linkTrailingBadge?: ClassNameValue; linkTrailingBadgeSize?: ClassNameValue; linkTrailingIcon?: ClassNameValue; linkTitle?: ClassNameValue; linkTitleExternalIcon?: ClassNameValue; } | undefined; /** * When `true`, prevents the user from interacting with the accordion and all its items */ disabled?: boolean | undefined; /** * Determines whether a "single" or "multiple" items can be selected at a time. * * This prop will overwrite the inferred type from `modelValue` and `defaultValue`. * @default "\"multiple\"" */ type?: SingleOrMultipleType | undefined; /** * When `true`, the element will be unmounted on closed state. * @default "true" */ unmountOnHide?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the ContentNavigation component */ interface ContentNavigationSlots { link(): any; link-leading(): any; link-title(): any; link-trailing(): any; } ``` ### Emits ```ts /** * Emitted events for the ContentNavigation component */ interface ContentNavigationEmits { update:modelValue: (payload: [value: string | string[] | undefined]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { contentNavigation: { slots: { root: '', content: 'data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out] overflow-hidden focus:outline-none', list: 'isolate -mx-2.5 -mt-1.5', item: '', listWithChildren: 'ms-5 border-s border-default', itemWithChildren: 'flex flex-col data-[state=open]:mb-1.5', trigger: 'font-semibold', link: 'group relative w-full px-2.5 py-1.5 before:inset-y-px before:inset-x-0 flex items-center gap-1.5 text-sm before:absolute before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0 size-5', linkTrailing: 'ms-auto inline-flex gap-1.5 items-center', linkTrailingBadge: 'shrink-0', linkTrailingBadgeSize: 'sm', linkTrailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180', linkTitle: 'truncate', linkTitleExternalIcon: 'size-3 align-top text-dimmed' }, variants: { color: { primary: { trigger: 'focus-visible:ring-primary', link: 'focus-visible:before:ring-primary' }, secondary: { trigger: 'focus-visible:ring-secondary', link: 'focus-visible:before:ring-secondary' }, success: { trigger: 'focus-visible:ring-success', link: 'focus-visible:before:ring-success' }, info: { trigger: 'focus-visible:ring-info', link: 'focus-visible:before:ring-info' }, warning: { trigger: 'focus-visible:ring-warning', link: 'focus-visible:before:ring-warning' }, error: { trigger: 'focus-visible:ring-error', link: 'focus-visible:before:ring-error' }, neutral: { trigger: 'focus-visible:ring-inverted', link: 'focus-visible:before:ring-inverted' } }, highlightColor: { primary: '', secondary: '', success: '', info: '', warning: '', error: '', neutral: '' }, variant: { pill: '', link: '' }, active: { true: { link: 'font-medium' }, false: { link: 'text-muted', linkLeadingIcon: 'text-dimmed' } }, disabled: { true: { trigger: 'data-[state=open]:text-highlighted' } }, highlight: { true: {} }, level: { true: { item: 'ps-1.5 -ms-px', itemWithChildren: 'ps-1.5 -ms-px' } } }, compoundVariants: [ { highlight: true, level: true, class: { link: [ 'after:absolute after:-left-1.5 after:inset-y-0.5 after:block after:w-px after:rounded-full', 'after:transition-colors' ] } }, { disabled: false, active: false, variant: 'pill', class: { link: [ 'hover:text-highlighted hover:before:bg-elevated/50 data-[state=open]:text-highlighted', 'transition-colors before:transition-colors' ], linkLeadingIcon: [ 'group-hover:text-default group-data-[state=open]:text-default', 'transition-colors' ] } }, { color: 'primary', variant: 'pill', active: true, class: { link: 'text-primary', linkLeadingIcon: 'text-primary group-data-[state=open]:text-primary' } }, { color: 'secondary', variant: 'pill', active: true, class: { link: 'text-secondary', linkLeadingIcon: 'text-secondary group-data-[state=open]:text-secondary' } }, { color: 'success', variant: 'pill', active: true, class: { link: 'text-success', linkLeadingIcon: 'text-success group-data-[state=open]:text-success' } }, { color: 'info', variant: 'pill', active: true, class: { link: 'text-info', linkLeadingIcon: 'text-info group-data-[state=open]:text-info' } }, { color: 'warning', variant: 'pill', active: true, class: { link: 'text-warning', linkLeadingIcon: 'text-warning group-data-[state=open]:text-warning' } }, { color: 'error', variant: 'pill', active: true, class: { link: 'text-error', linkLeadingIcon: 'text-error group-data-[state=open]:text-error' } }, { color: 'neutral', variant: 'pill', active: true, class: { link: 'text-highlighted', linkLeadingIcon: 'text-highlighted group-data-[state=open]:text-highlighted' } }, { variant: 'pill', active: true, highlight: false, class: { link: 'before:bg-elevated' } }, { variant: 'pill', active: true, highlight: true, disabled: false, class: { link: [ 'hover:before:bg-elevated/50', 'before:transition-colors' ] } }, { disabled: false, active: false, variant: 'link', class: { link: [ 'hover:text-highlighted data-[state=open]:text-highlighted', 'transition-colors' ], linkLeadingIcon: [ 'group-hover:text-default group-data-[state=open]:text-default', 'transition-colors' ] } }, { color: 'primary', variant: 'link', active: true, class: { link: 'text-primary', linkLeadingIcon: 'text-primary group-data-[state=open]:text-primary' } }, { color: 'secondary', variant: 'link', active: true, class: { link: 'text-secondary', linkLeadingIcon: 'text-secondary group-data-[state=open]:text-secondary' } }, { color: 'success', variant: 'link', active: true, class: { link: 'text-success', linkLeadingIcon: 'text-success group-data-[state=open]:text-success' } }, { color: 'info', variant: 'link', active: true, class: { link: 'text-info', linkLeadingIcon: 'text-info group-data-[state=open]:text-info' } }, { color: 'warning', variant: 'link', active: true, class: { link: 'text-warning', linkLeadingIcon: 'text-warning group-data-[state=open]:text-warning' } }, { color: 'error', variant: 'link', active: true, class: { link: 'text-error', linkLeadingIcon: 'text-error group-data-[state=open]:text-error' } }, { color: 'neutral', variant: 'link', active: true, class: { link: 'text-highlighted', linkLeadingIcon: 'text-highlighted group-data-[state=open]:text-highlighted' } }, { highlightColor: 'primary', highlight: true, level: true, active: true, class: { link: 'after:bg-primary' } }, { highlightColor: 'secondary', highlight: true, level: true, active: true, class: { link: 'after:bg-secondary' } }, { highlightColor: 'success', highlight: true, level: true, active: true, class: { link: 'after:bg-success' } }, { highlightColor: 'info', highlight: true, level: true, active: true, class: { link: 'after:bg-info' } }, { highlightColor: 'warning', highlight: true, level: true, active: true, class: { link: 'after:bg-warning' } }, { highlightColor: 'error', highlight: true, level: true, active: true, class: { link: 'after:bg-error' } }, { highlightColor: 'neutral', highlight: true, level: true, active: true, class: { link: 'after:bg-inverted' } } ], defaultVariants: { color: 'primary', highlightColor: 'primary', variant: 'pill' } } } }) ``` ## Changelog ::component-changelog{prefix="content"} :: # ContentSearch ::warning{to="https://ui.nuxt.com/docs/getting-started/integrations/content"} This component is only available when the `@nuxt/content` module is installed. :: ## Usage The ContentSearch component extends the [CommandPalette](https://ui.nuxt.com/docs/components/command-palette) component, so you can pass any property such as `icon`, `placeholder`, etc. Use the `files` and `navigation` props with the `files`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} and `navigation`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} values you fetched using the `queryCollectionSearchSections` and `queryCollectionNavigation` composables from `@nuxt/content`. ```vue [ContentSearchExample.vue] ``` ::tip You can open the CommandPalette by pressing `` ``, by using the [ContentSearchButton](https://ui.nuxt.com/docs/components/content-search-button) component or by using the `useContentSearch` composable: `const { open } = useContentSearch()`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts"}. :: ### Shortcut Use the `shortcut` prop to change the shortcut used in [defineShortcuts](https://ui.nuxt.com/docs/composables/define-shortcuts) to open the ContentSearch component. Defaults to `meta_k` (`` ``). ```vue [app.vue] {6} ``` ### Color Mode By default, a group of commands will be added to the command palette so you can switch between light and dark mode. This will only take effect if the `colorMode` is not forced in a specific page which can be achieved through `definePageMeta`: ```vue [pages/index.vue] ``` You can disable this behavior by setting the `color-mode` prop to `false`: ```vue [app.vue] {6} ``` ## Examples ### Within `app.vue` Use the ContentSearch component in your `app.vue` or in a layout: ```vue [app.vue] ``` ::tip It is recommended to wrap the `ContentSearch` component in a [ClientOnly](https://nuxt.com/docs/api/components/client-only){rel="nofollow"} component so it's not rendered on the server. :: ## API ### Props ```ts /** * Props for the ContentSearch component */ interface ContentSearchProps { /** * The icon displayed in the input. */ icon?: string | object | undefined; /** * The placeholder text for the input. */ placeholder?: string | undefined; /** * Automatically focus the input when component is mounted. */ autofocus?: boolean | undefined; /** * When `true`, the loading icon will be displayed. */ loading?: boolean | undefined; /** * The icon when the `loading` prop is `true`. */ loadingIcon?: string | object | undefined; /** * Display a close button in the input (useful when inside a Modal for example). * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"} * @default "true" */ close?: boolean | Partial | undefined; /** * The icon displayed in the close button. */ closeIcon?: string | object | undefined; /** * Keyboard shortcut to open the search (used by [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts)) * @default "\"meta_k\"" */ shortcut?: string | undefined; /** * Links group displayed as the first group in the command palette. */ links?: ContentSearchLink[] | undefined; navigation?: ContentNavigationItem[] | undefined; /** * Custom groups displayed between navigation and color mode group. */ groups?: CommandPaletteGroup[] | undefined; files?: ContentSearchFile[] | undefined; /** * Options for [useFuse](https://vueuse.org/integrations/useFuse) passed to the [CommandPalette](https://ui.nuxt.com/docs/components/command-palette). */ fuse?: UseFuseOptions | undefined; /** * When `true`, the theme command will be added to the groups. * @default "true" */ colorMode?: boolean | undefined; ui?: ({ modal?: ClassNameValue; input?: ClassNameValue; } & { root?: ClassNameValue; input?: ClassNameValue; close?: ClassNameValue; back?: ClassNameValue; content?: ClassNameValue; footer?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; empty?: ClassNameValue; label?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemLeadingChip?: ClassNameValue; itemLeadingChipSize?: ClassNameValue; itemTrailing?: ClassNameValue; itemTrailingIcon?: ClassNameValue; itemTrailingHighlightedIcon?: ClassNameValue; itemTrailingKbds?: ClassNameValue; itemTrailingKbdsSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelBase?: ClassNameValue; itemLabelPrefix?: ClassNameValue; itemLabelSuffix?: ClassNameValue; }) | undefined; title?: string | undefined; description?: string | undefined; /** * Render an overlay behind the modal. */ overlay?: boolean | undefined; /** * Animate the modal when opening or closing. */ transition?: boolean | undefined; /** * The content of the modal. */ content?: (Omit & Partial>) | undefined; /** * When `false`, the modal will not close when clicking outside or pressing escape. */ dismissible?: boolean | undefined; /** * When `true`, the modal will take up the full screen. * @default "false" */ fullscreen?: boolean | undefined; /** * The modality of the dialog When set to `true`,
* interaction with outside elements will be disabled and only dialog content will be visible to screen readers. */ modal?: boolean | undefined; /** * Render the modal in a portal. */ portal?: string | boolean | HTMLElement | undefined; /** * @default "\"\"" */ searchTerm?: string | undefined; } ``` ### Slots ```ts /** * Slots for the ContentSearch component */ interface ContentSearchSlots { empty(): any; footer(): any; back(): any; close(): any; item(): any; item-leading(): any; item-label(): any; item-description(): any; item-trailing(): any; content(): any; } ``` ### Emits ```ts /** * Emitted events for the ContentSearch component */ interface ContentSearchEmits { update:searchTerm: (payload: [value: string]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { contentSearch: { slots: { modal: '', input: '[&>input]:text-base/5' }, variants: { fullscreen: { false: { modal: 'sm:max-w-3xl sm:h-[28rem]' } } } } } }) ``` ## Changelog ::component-changelog{prefix="content"} :: # ContentSearchButton ::warning{to="https://ui.nuxt.com/docs/getting-started/integrations/content"} This component is only available when the `@nuxt/content` module is installed. :: ## Usage The ContentSearchButton component is used to open the [ContentSearch](https://ui.nuxt.com/docs/components/content-search) modal. ```vue ``` It extends the [Button](https://ui.nuxt.com/docs/components/button) component, so you can pass any property such as `color`, `variant`, `size`, etc. ```vue ``` ::note{to="https://ui.nuxt.com/#collapsed"} The button defaults to `color="neutral"` and `variant="outline"` when not collapsed, `variant="ghost"` when collapsed. :: ### Collapsed Use the `collapsed` prop to show the button's label and [kbds](https://ui.nuxt.com/#kbds). Defaults to `true`. ```vue ``` ### Kbds Use the `kbds` prop to display keyboard keys in the button. Defaults to `['meta', 'K']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} to match the default shortcut of the [ContentSearch](https://ui.nuxt.com/docs/components/content-search#shortcut) component. ```vue ``` ## API ### Props ```ts /** * Props for the ContentSearchButton component */ interface ContentSearchButtonProps { /** * The icon displayed in the button. */ icon?: string | object | undefined; /** * The label displayed in the button. */ label?: string | undefined; /** * The color of the button. * @default "\"neutral\"" */ color?: "error" | "neutral" | "primary" | "secondary" | "success" | "info" | "warning" | undefined; /** * The variant of the button. * Defaults to 'outline' when not collapsed, 'ghost' when collapsed. */ variant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; /** * Whether the button is collapsed. * @default "true" */ collapsed?: boolean | undefined; /** * Display a tooltip on the button when is collapsed with the button label. * This has priority over the global `tooltip` prop. * @default "false" */ tooltip?: boolean | TooltipProps | undefined; /** * The keyboard keys to display in the button. * `{ variant: 'subtle' }`{lang="ts-type"} * @default "[\"meta\", \"k\"]" */ kbds?: (string | undefined)[] | KbdProps[] | undefined; ui?: ({ base?: ClassNameValue; trailing?: ClassNameValue; } & { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; }) | undefined; } ``` ### Slots ```ts /** * Slots for the ContentSearchButton component */ interface ContentSearchButtonSlots { leading(): any; default(): any; trailing(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { contentSearchButton: { slots: { base: '', trailing: 'hidden lg:flex items-center gap-0.5 ms-auto' } } } }) ``` ## Changelog ::component-changelog{prefix="content"} :: # ContentSurround ::warning{to="https://ui.nuxt.com/docs/getting-started/integrations/content"} This component is only available when the `@nuxt/content` module is installed. :: ## Usage Use the `surround` prop with the `surround`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} value you get when fetching a page surround. ```vue [ContentSurroundExample.vue] ``` ### Prev / Next Use the `prev-icon` and `next-icon` props to customize the buttons [Icon](https://ui.nuxt.com/docs/components/icon). ```vue ``` ## Examples ### Within a page Use the ContentSurround component in a page to display the prev and next links: ```vue [pages/[...slug\\].vue] {19} ``` ## API ### Props ```ts /** * Props for the ContentSurround component */ interface ContentSurroundProps { /** * The element or component this component should render as. */ as?: any; /** * The icon displayed in the prev link. */ prevIcon?: string | object | undefined; /** * The icon displayed in the next link. */ nextIcon?: string | object | undefined; surround?: ContentSurroundLink[] | undefined; ui?: { root?: ClassNameValue; link?: ClassNameValue; linkLeading?: ClassNameValue; linkLeadingIcon?: ClassNameValue; linkTitle?: ClassNameValue; linkDescription?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the ContentSurround component */ interface ContentSurroundSlots { link(): any; link-leading(): any; link-title(): any; link-description(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { contentSurround: { slots: { root: 'grid grid-cols-1 sm:grid-cols-2 gap-8', link: [ 'group block px-6 py-8 rounded-lg border border-default hover:bg-elevated/50 focus-visible:outline-primary', 'transition-colors' ], linkLeading: [ 'inline-flex items-center rounded-full p-1.5 bg-elevated group-hover:bg-primary/10 ring ring-accented mb-4 group-hover:ring-primary/50', 'transition' ], linkLeadingIcon: [ 'size-5 shrink-0 text-highlighted group-hover:text-primary', 'transition-[color,translate]' ], linkTitle: 'font-medium text-[15px] text-highlighted mb-1 truncate', linkDescription: 'text-sm text-muted line-clamp-2' }, variants: { direction: { left: { linkLeadingIcon: [ 'group-active:-translate-x-0.5' ] }, right: { link: 'text-right', linkLeadingIcon: [ 'group-active:translate-x-0.5' ] } } } } } }) ``` ## Changelog ::component-changelog{prefix="content"} :: # ContentToc ::warning{to="https://ui.nuxt.com/docs/getting-started/integrations/content"} This component is only available when the `@nuxt/content` module is installed. :: ## Usage Use the `links` prop with the `page?.body?.toc?.links`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} you get when fetching a page. ```vue [ContentTocExample.vue] ``` ### Title Use the `title` prop to change the title of the Table of Contents. ```vue ``` ### Color Use the `color` prop to change the color of the links. ```vue ``` ### Highlight Use the `highlight` prop to display a highlighted border for the active item. Use the `highlight-color` prop to change the color of the border. It defaults to the `color` prop. ```vue ``` ## Examples ### Within a page Use the ContentToc component in a page to display the Table of Contents: ```vue [pages/[...slug\\].vue] {22-24} ``` ## API ### Props ```ts /** * Props for the ContentToc component */ interface ContentTocProps { /** * The element or component this component should render as. * @default "\"nav\"" */ as?: any; /** * The icon displayed to collapse the content. */ trailingIcon?: string | object | undefined; /** * The title of the table of contents. */ title?: string | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; /** * Display a line next to the active link. */ highlight?: boolean | undefined; highlightColor?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; links?: ContentTocLink[] | undefined; ui?: { root?: ClassNameValue; container?: ClassNameValue; top?: ClassNameValue; bottom?: ClassNameValue; trigger?: ClassNameValue; title?: ClassNameValue; trailing?: ClassNameValue; trailingIcon?: ClassNameValue; content?: ClassNameValue; list?: ClassNameValue; listWithChildren?: ClassNameValue; item?: ClassNameValue; itemWithChildren?: ClassNameValue; link?: ClassNameValue; linkText?: ClassNameValue; indicator?: ClassNameValue; } | undefined; /** * The open state of the collapsible when it is initially rendered.
Use when you do not need to control its open state. */ defaultOpen?: boolean | undefined; /** * The controlled open state of the collapsible. Can be binded with `v-model`. */ open?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the ContentToc component */ interface ContentTocSlots { leading(): any; default(): any; trailing(): any; content(): any; link(): any; top(): any; bottom(): any; } ``` ### Emits ```ts /** * Emitted events for the ContentToc component */ interface ContentTocEmits { update:open: (payload: [value: boolean]) => void; move: (payload: [id: string]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { contentToc: { slots: { root: 'sticky top-(--ui-header-height) z-10 bg-default/75 lg:bg-[initial] backdrop-blur -mx-4 px-4 sm:px-6 sm:-mx-6 overflow-y-auto max-h-[calc(100vh-var(--ui-header-height))]', container: 'pt-4 sm:pt-6 pb-2.5 sm:pb-4.5 lg:py-8 border-b border-dashed border-default lg:border-0 flex flex-col', top: '', bottom: 'hidden lg:flex lg:flex-col gap-6', trigger: 'group text-sm font-semibold flex-1 flex items-center gap-1.5 py-1.5 -mt-1.5 focus-visible:outline-primary', title: 'truncate', trailing: 'ms-auto inline-flex gap-1.5 items-center', trailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden', content: 'data-[state=open]:animate-[collapsible-down_200ms_ease-out] data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden focus:outline-none', list: 'min-w-0', listWithChildren: 'ms-3', item: 'min-w-0', itemWithChildren: '', link: 'group relative text-sm flex items-center focus-visible:outline-primary py-1', linkText: 'truncate', indicator: 'absolute ms-2.5 transition-[translate,height] duration-200 h-(--indicator-size) translate-y-(--indicator-position) w-px rounded-full' }, variants: { color: { primary: '', secondary: '', success: '', info: '', warning: '', error: '', neutral: '' }, highlightColor: { primary: { indicator: 'bg-primary' }, secondary: { indicator: 'bg-secondary' }, success: { indicator: 'bg-success' }, info: { indicator: 'bg-info' }, warning: { indicator: 'bg-warning' }, error: { indicator: 'bg-error' }, neutral: { indicator: 'bg-inverted' } }, active: { false: { link: [ 'text-muted hover:text-default', 'transition-colors' ] } }, highlight: { true: { list: 'ms-2.5 ps-4 border-s border-default', item: '-ms-px' } }, body: { true: { bottom: 'mt-6' } } }, compoundVariants: [ { color: 'primary', active: true, class: { link: 'text-primary', linkLeadingIcon: 'text-primary' } }, { color: 'secondary', active: true, class: { link: 'text-secondary', linkLeadingIcon: 'text-secondary' } }, { color: 'success', active: true, class: { link: 'text-success', linkLeadingIcon: 'text-success' } }, { color: 'info', active: true, class: { link: 'text-info', linkLeadingIcon: 'text-info' } }, { color: 'warning', active: true, class: { link: 'text-warning', linkLeadingIcon: 'text-warning' } }, { color: 'error', active: true, class: { link: 'text-error', linkLeadingIcon: 'text-error' } }, { color: 'neutral', active: true, class: { link: 'text-highlighted', linkLeadingIcon: 'text-highlighted' } } ], defaultVariants: { color: 'primary', highlightColor: 'primary' } } } }) ``` ## Changelog ::component-changelog{prefix="content"} :: # ContextMenu ## Usage Use anything you like in the default slot of the ContextMenu, and right-click on it to display the menu. ```vue ``` ### Items Use the `items` prop as an array of objects with the following properties: - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `avatar?: AvatarProps`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `kbds?: string[] | KbdProps[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`type?: "link" | "label" | "separator" | "checkbox"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-checkbox-items) - [`color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-color-items) - [`checked?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-checkbox-items) - `disabled?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot) - `onSelect?: (e: Event) => void`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`onUpdateChecked?: (checked: boolean) => void`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-checkbox-items) - `children?: ContextMenuItem[] | ContextMenuItem[][]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelExternalIcon?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` ::note You can also pass an array of arrays to the `items` prop to create separated groups of items. :: ::tip Each item can take a `children` array of objects with the same properties as the `items` prop to create a nested menu which can be controlled using the `open`, `defaultOpen` and `content` properties. :: ### Size Use the `size` prop to change the size of the ContextMenu. ```vue ``` ### Disabled Use the `disabled` prop to disable the ContextMenu. ```vue ``` ## Examples ### With checkbox items You can use the `type` property with `checkbox` and use the `checked` / `onUpdateChecked` properties to control the checked state of the item. ```vue [ContextMenuCheckboxItemsExample.vue] ``` ::note To ensure reactivity for the `checked` state of items, it's recommended to wrap your `items` array inside a `computed`. :: ### With color items You can use the `color` property to highlight certain items with a color. ```vue [ContextMenuColorItemsExample.vue] ``` ### With custom slot Use the `slot` property to customize a specific item. You will have access to the following slots: - `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-leading`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-label`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-trailing`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue [ContextMenuCustomSlotExample.vue] ``` ::tip{to="https://ui.nuxt.com/#slots"} You can also use the `#item`, `#item-leading`, `#item-label` and `#item-trailing` slots to customize all items. :: ### Extract shortcuts When you have some items with `kbds` property (displaying some [Kbd](https://ui.nuxt.com/docs/components/kbd)), you can easily make them work with the [defineShortcuts](https://ui.nuxt.com/docs/composables/define-shortcuts) composable. Inside the `defineShortcuts` composable, there is an `extractShortcuts` utility that will extract the shortcuts recursively from the items and return an object that you can pass to `defineShortcuts`. It will automatically call the `select` function of the item when the shortcut is pressed. ```vue ``` ::note In this example, `` ``, `` `` ``, `` `` ``, `` `` ``, `` `` `` and `` `` `` would trigger the `select` function of the corresponding item. :: ## API ### Props ```ts /** * Props for the ContextMenu component */ interface ContextMenuProps { size?: "sm" | "md" | "xs" | "lg" | "xl" | undefined; items?: ArrayOrNested | undefined; /** * The icon displayed when an item is checked. */ checkedIcon?: string | object | undefined; /** * The icon displayed when an item is loading. */ loadingIcon?: string | object | undefined; /** * The icon displayed when the item is an external link. * Set to `false` to hide the external icon. * @default "true" */ externalIcon?: string | boolean | object | undefined; /** * The content of the menu. */ content?: (Omit & Partial>) | undefined; /** * Render the menu in a portal. * @default "true" */ portal?: string | boolean | HTMLElement | undefined; /** * The key used to get the label from the item. * @default "\"label\"" */ labelKey?: GetItemKeys> | undefined; /** * The key used to get the description from the item. * @default "\"description\"" */ descriptionKey?: GetItemKeys> | undefined; disabled?: boolean | undefined; ui?: { content?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; label?: ClassNameValue; separator?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemTrailing?: ClassNameValue; itemTrailingIcon?: ClassNameValue; itemTrailingKbds?: ClassNameValue; itemTrailingKbdsSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelExternalIcon?: ClassNameValue; } | undefined; /** * The modality of the dropdown menu. * * When set to `true`, interaction with outside elements will be disabled and only menu content will be visible to screen readers. * @default "true" */ modal?: boolean | undefined; /** * The duration from when the trigger is pressed until the menu opens. */ pressOpenDelay?: number | undefined; } ``` ### Slots ```ts /** * Slots for the ContextMenu component */ interface ContextMenuSlots { default(): any; item(): any; item-leading(): any; item-label(): any; item-description(): any; item-trailing(): any; content-top(): any; content-bottom(): any; } ``` ### Emits ```ts /** * Emitted events for the ContextMenu component */ interface ContextMenuEmits { update:open: (payload: [payload: boolean]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { contextMenu: { slots: { content: 'min-w-32 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-context-menu-content-transform-origin) flex flex-col', viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1', group: 'p-1 isolate', label: 'w-full flex items-center font-semibold text-highlighted', separator: '-mx-1 my-1 h-px bg-border', item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', itemLeadingIcon: 'shrink-0', itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', itemTrailing: 'ms-auto inline-flex gap-1.5 items-center', itemTrailingIcon: 'shrink-0', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0', itemTrailingKbdsSize: '', itemWrapper: 'flex-1 flex flex-col text-start min-w-0', itemLabel: 'truncate', itemDescription: 'truncate text-muted', itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed' }, variants: { color: { primary: '', secondary: '', success: '', info: '', warning: '', error: '', neutral: '' }, active: { true: { item: 'text-highlighted before:bg-elevated', itemLeadingIcon: 'text-default' }, false: { item: [ 'text-default data-highlighted:text-highlighted data-[state=open]:text-highlighted data-highlighted:before:bg-elevated/50 data-[state=open]:before:bg-elevated/50', 'transition-colors before:transition-colors' ], itemLeadingIcon: [ 'text-dimmed group-data-highlighted:text-default group-data-[state=open]:text-default', 'transition-colors' ] } }, loading: { true: { itemLeadingIcon: 'animate-spin' } }, size: { xs: { label: 'p-1 text-xs gap-1', item: 'p-1 text-xs gap-1', itemLeadingIcon: 'size-4', itemLeadingAvatarSize: '3xs', itemTrailingIcon: 'size-4', itemTrailingKbds: 'gap-0.5', itemTrailingKbdsSize: 'sm' }, sm: { label: 'p-1.5 text-xs gap-1.5', item: 'p-1.5 text-xs gap-1.5', itemLeadingIcon: 'size-4', itemLeadingAvatarSize: '3xs', itemTrailingIcon: 'size-4', itemTrailingKbds: 'gap-0.5', itemTrailingKbdsSize: 'sm' }, md: { label: 'p-1.5 text-sm gap-1.5', item: 'p-1.5 text-sm gap-1.5', itemLeadingIcon: 'size-5', itemLeadingAvatarSize: '2xs', itemTrailingIcon: 'size-5', itemTrailingKbds: 'gap-0.5', itemTrailingKbdsSize: 'md' }, lg: { label: 'p-2 text-sm gap-2', item: 'p-2 text-sm gap-2', itemLeadingIcon: 'size-5', itemLeadingAvatarSize: '2xs', itemTrailingIcon: 'size-5', itemTrailingKbds: 'gap-1', itemTrailingKbdsSize: 'md' }, xl: { label: 'p-2 text-base gap-2', item: 'p-2 text-base gap-2', itemLeadingIcon: 'size-6', itemLeadingAvatarSize: 'xs', itemTrailingIcon: 'size-6', itemTrailingKbds: 'gap-1', itemTrailingKbdsSize: 'lg' } } }, compoundVariants: [ { color: 'primary', active: false, class: { item: 'text-primary data-highlighted:text-primary data-highlighted:before:bg-primary/10 data-[state=open]:before:bg-primary/10', itemLeadingIcon: 'text-primary/75 group-data-highlighted:text-primary group-data-[state=open]:text-primary' } }, { color: 'secondary', active: false, class: { item: 'text-secondary data-highlighted:text-secondary data-highlighted:before:bg-secondary/10 data-[state=open]:before:bg-secondary/10', itemLeadingIcon: 'text-secondary/75 group-data-highlighted:text-secondary group-data-[state=open]:text-secondary' } }, { color: 'success', active: false, class: { item: 'text-success data-highlighted:text-success data-highlighted:before:bg-success/10 data-[state=open]:before:bg-success/10', itemLeadingIcon: 'text-success/75 group-data-highlighted:text-success group-data-[state=open]:text-success' } }, { color: 'info', active: false, class: { item: 'text-info data-highlighted:text-info data-highlighted:before:bg-info/10 data-[state=open]:before:bg-info/10', itemLeadingIcon: 'text-info/75 group-data-highlighted:text-info group-data-[state=open]:text-info' } }, { color: 'warning', active: false, class: { item: 'text-warning data-highlighted:text-warning data-highlighted:before:bg-warning/10 data-[state=open]:before:bg-warning/10', itemLeadingIcon: 'text-warning/75 group-data-highlighted:text-warning group-data-[state=open]:text-warning' } }, { color: 'error', active: false, class: { item: 'text-error data-highlighted:text-error data-highlighted:before:bg-error/10 data-[state=open]:before:bg-error/10', itemLeadingIcon: 'text-error/75 group-data-highlighted:text-error group-data-[state=open]:text-error' } }, { color: 'primary', active: true, class: { item: 'text-primary before:bg-primary/10', itemLeadingIcon: 'text-primary' } }, { color: 'secondary', active: true, class: { item: 'text-secondary before:bg-secondary/10', itemLeadingIcon: 'text-secondary' } }, { color: 'success', active: true, class: { item: 'text-success before:bg-success/10', itemLeadingIcon: 'text-success' } }, { color: 'info', active: true, class: { item: 'text-info before:bg-info/10', itemLeadingIcon: 'text-info' } }, { color: 'warning', active: true, class: { item: 'text-warning before:bg-warning/10', itemLeadingIcon: 'text-warning' } }, { color: 'error', active: true, class: { item: 'text-error before:bg-error/10', itemLeadingIcon: 'text-error' } } ], defaultVariants: { size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # DashboardGroup ## Usage The DashboardGroup component is the main layout that wraps the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) and [DashboardPanel](https://ui.nuxt.com/docs/components/dashboard-panel) components to create a responsive dashboard interface. Use it in a layout or in your `app.vue`: ```vue [layouts/dashboard.vue] {2,6} ``` ## API ### Props ```ts /** * Props for the DashboardGroup component */ interface DashboardGroupProps { /** * The element or component this component should render as. */ as?: any; /** * The storage to use for the size. * @default "\"cookie\"" */ storage?: "cookie" | "local" | undefined; /** * Unique id used to auto-save size. * @default "\"dashboard\"" */ storageKey?: string | undefined; /** * Whether to persist the size in the storage. * @default "true" */ persistent?: boolean | undefined; /** * The unit to use for size values. * @default "\"%\"" */ unit?: "%" | "rem" | "px" | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardGroup component */ interface DashboardGroupSlots { default(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardGroup: { base: 'fixed inset-0 flex overflow-hidden' } } }) ``` ## Changelog ::component-changelog :: # DashboardNavbar ## Usage The DashboardNavbar component is a responsive navigation bar that integrates with the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) component. It includes a mobile toggle button to enable responsive navigation in dashboard layouts. Use it inside the `header` slot of the [DashboardPanel](https://ui.nuxt.com/docs/components/dashboard-panel) component: ```vue [pages/index.vue] {9-11} ``` Use the `left`, `default` and `right` slots to customize the navbar. ```vue [DashboardNavbarExample.vue] ``` ::note In this example, we use the [Tabs](https://ui.nuxt.com/docs/components/tabs) component in the right slot to display some tabs. :: ### Title Use the `title` prop to set the title of the navbar. ```vue ``` ### Icon Use the `icon` prop to set the icon of the navbar. ```vue ``` ### Toggle Use the `toggle` prop to customize the toggle button displayed on mobile that opens the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) component. You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue [DashboardNavbarToggleExample.vue] ``` ### Toggle Side Use the `toggle-side` prop to change the side of the toggle button. Defaults to `right`. ```vue [DashboardNavbarToggleSideExample.vue] ``` ## API ### Props ```ts /** * Props for the DashboardNavbar component */ interface DashboardNavbarProps { /** * The element or component this component should render as. */ as?: any; /** * The icon displayed next to the title. */ icon?: string | object | undefined; title?: string | undefined; /** * Customize the toggle button to open the sidebar. * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"} * @default "true" */ toggle?: boolean | Partial | undefined; /** * The side to render the toggle button on. * @default "\"left\"" */ toggleSide?: "left" | "right" | undefined; ui?: { root?: ClassNameValue; left?: ClassNameValue; icon?: ClassNameValue; title?: ClassNameValue; center?: ClassNameValue; right?: ClassNameValue; toggle?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardNavbar component */ interface DashboardNavbarSlots { title(): any; leading(): any; trailing(): any; left(): any; default(): any; right(): any; toggle(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardNavbar: { slots: { root: 'h-(--ui-header-height) shrink-0 flex items-center justify-between border-b border-default px-4 sm:px-6 gap-1.5', left: 'flex items-center gap-1.5 min-w-0', icon: 'shrink-0 size-5 self-center me-1.5', title: 'flex items-center gap-1.5 font-semibold text-highlighted truncate', center: 'hidden lg:flex', right: 'flex items-center shrink-0 gap-1.5', toggle: '' }, variants: { toggleSide: { left: { toggle: '' }, right: { toggle: '' } } } } } }) ``` ## Changelog ::component-changelog :: # DashboardPanel ## Usage The DashboardPanel component is used to display a panel. Its state (size, collapsed, etc.) will be saved based on the `storage` and `storage-key` props you provide to the [DashboardGroup](https://ui.nuxt.com/docs/components/dashboard-group#props) component. Use it inside the default slot of the [DashboardGroup](https://ui.nuxt.com/docs/components/dashboard-group) component, you can put multiple panels next to each other: ```vue [pages/index.vue] {8,10} ``` ::caution It is recommended to set an `id` when using multiple panels in different pages to avoid conflicts. :: ::warning This component does not have a single root element when using the `resizable` prop, so wrap it in a container (e.g., `
`) if you use page transitions or require a single root for layout. :: Use the `header`, `body` and `footer` slots to customize the panel or the default slot if you don't want a scrollable body with padding. ```vue [DashboardPanelExample.vue] ``` ::note Most of the time, you will use the [`DashboardNavbar`](https://ui.nuxt.com/docs/components/dashboard-navbar) component in the `header` slot. :: ### Resizable Use the `resizable` prop to make the panel resizable. ```vue ``` ### Size Use the `min-size`, `max-size` and `default-size` props to customize the size of the panel. ```vue ``` ::tip{to="https://ui.nuxt.com/docs/components/dashboard-group#props"} Sizes are calculated as percentages by default. You can change this using the `unit` prop on the `DashboardGroup` component. :: ## API ### Props ```ts /** * Props for the DashboardPanel component */ interface DashboardPanelProps { ui?: { root?: ClassNameValue; body?: ClassNameValue; handle?: ClassNameValue; } | undefined; /** * The id of the panel. */ id?: string | undefined; /** * The minimum size of the panel. * @default "15" */ minSize?: number | undefined; /** * The maximum size of the panel. */ maxSize?: number | undefined; /** * The default size of the panel. */ defaultSize?: number | undefined; /** * Whether to allow the user to resize the panel. * @default "false" */ resizable?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardPanel component */ interface DashboardPanelSlots { default(): any; header(): any; body(): any; footer(): any; resize-handle(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardPanel: { slots: { root: 'relative flex flex-col min-w-0 min-h-svh lg:not-last:border-e lg:not-last:border-default shrink-0', body: 'flex flex-col gap-4 sm:gap-6 flex-1 overflow-y-auto p-4 sm:p-6', handle: '' }, variants: { size: { true: { root: 'w-full lg:w-(--width)' }, false: { root: 'flex-1' } } } } } }) ``` ## Changelog ::component-changelog :: # DashboardResizeHandle ## Usage The DashboardResizeHandle component is used by the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) and [DashboardPanel](https://ui.nuxt.com/docs/components/dashboard-panel) components. It is automatically displayed when the `resizable` prop is set, **you don't have to add it manually**. ## Examples ### Within `resize-handle` slot Even though this component is automatically displayed when the `resizable` prop is set, you can use the `resize-handle` slot of the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) and [DashboardPanel](https://ui.nuxt.com/docs/components/dashboard-panel) components to customize the handle. ::code-group ```vue [layouts/dashboard.vue] {4-10} ``` ```vue [pages/index.vue] {9-15} ``` :: ::note In this example, we add an `after` pseudo-element to display a vertical line on hover. :: ## API ### Props ```ts /** * Props for the DashboardResizeHandle component */ interface DashboardResizeHandleProps { /** * The element or component this component should render as. */ as?: any; } ``` ### Slots ```ts /** * Slots for the DashboardResizeHandle component */ interface DashboardResizeHandleSlots { default(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardResizeHandle: { base: 'hidden lg:block touch-none select-none cursor-ew-resize relative before:absolute before:inset-y-0 before:-left-1.5 before:-right-1.5' } } }) ``` ## Changelog ::component-changelog :: # DashboardSearch ## Usage The DashboardSearch component extends the [CommandPalette](https://ui.nuxt.com/docs/components/command-palette) component, so you can pass any property such as `icon`, `placeholder`, etc. Use it inside the default slot of the [DashboardGroup](https://ui.nuxt.com/docs/components/dashboard-group) component: ```vue [layouts/dashboard.vue] {3} ``` ::tip You can open the CommandPalette by pressing `` ``, by using the [DashboardSearchButton](https://ui.nuxt.com/docs/components/dashboard-search-button) component or by using a `v-model:open`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts"} directive. :: ### Shortcut Use the `shortcut` prop to change the shortcut used in [defineShortcuts](https://ui.nuxt.com/docs/composables/define-shortcuts) to open the ContentSearch component. Defaults to `meta_k` (`` ``). ```vue [app.vue] {4} ``` ### Color Mode By default, a group of commands will be added to the command palette so you can switch between light and dark mode. This will only take effect if the `colorMode` is not forced in a specific page which can be achieved through `definePageMeta`: ```vue [pages/index.vue] ``` You can disable this behavior by setting the `color-mode` prop to `false`: ```vue [app.vue] {4} ``` ## API ### Props ```ts /** * Props for the DashboardSearch component */ interface DashboardSearchProps { /** * The icon displayed in the input. */ icon?: string | object | undefined; /** * The placeholder text for the input. */ placeholder?: string | undefined; /** * Automatically focus the input when component is mounted. */ autofocus?: boolean | undefined; /** * When `true`, the loading icon will be displayed. */ loading?: boolean | undefined; /** * The icon when the `loading` prop is `true`. */ loadingIcon?: string | object | undefined; /** * Display a close button in the input (useful when inside a Modal for example). * `{ size: 'md', color: 'neutral', variant: 'ghost' }`{lang="ts-type"} * @default "true" */ close?: boolean | Partial | undefined; /** * The icon displayed in the close button. */ closeIcon?: string | object | undefined; /** * Keyboard shortcut to open the search (used by [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts)) * @default "\"meta_k\"" */ shortcut?: string | undefined; groups?: CommandPaletteGroup[] | undefined; /** * Options for [useFuse](https://vueuse.org/integrations/useFuse) passed to the [CommandPalette](https://ui.nuxt.com/docs/components/command-palette). */ fuse?: UseFuseOptions | undefined; /** * When `true`, the theme command will be added to the groups. * @default "true" */ colorMode?: boolean | undefined; ui?: ({ modal?: ClassNameValue; input?: ClassNameValue; } & { root?: ClassNameValue; input?: ClassNameValue; close?: ClassNameValue; back?: ClassNameValue; content?: ClassNameValue; footer?: ClassNameValue; viewport?: ClassNameValue; group?: ClassNameValue; empty?: ClassNameValue; label?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemLeadingChip?: ClassNameValue; itemLeadingChipSize?: ClassNameValue; itemTrailing?: ClassNameValue; itemTrailingIcon?: ClassNameValue; itemTrailingHighlightedIcon?: ClassNameValue; itemTrailingKbds?: ClassNameValue; itemTrailingKbdsSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelBase?: ClassNameValue; itemLabelPrefix?: ClassNameValue; itemLabelSuffix?: ClassNameValue; }) | undefined; title?: string | undefined; description?: string | undefined; /** * Render an overlay behind the modal. */ overlay?: boolean | undefined; /** * Animate the modal when opening or closing. */ transition?: boolean | undefined; /** * The content of the modal. */ content?: (Omit & Partial>) | undefined; /** * When `false`, the modal will not close when clicking outside or pressing escape. */ dismissible?: boolean | undefined; /** * When `true`, the modal will take up the full screen. * @default "false" */ fullscreen?: boolean | undefined; /** * The modality of the dialog When set to `true`,
* interaction with outside elements will be disabled and only dialog content will be visible to screen readers. */ modal?: boolean | undefined; /** * Render the modal in a portal. */ portal?: string | boolean | HTMLElement | undefined; /** * @default "false" */ open?: boolean | undefined; /** * @default "\"\"" */ searchTerm?: string | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardSearch component */ interface DashboardSearchSlots { empty(): any; footer(): any; back(): any; close(): any; item(): any; item-leading(): any; item-label(): any; item-description(): any; item-trailing(): any; content(): any; } ``` ### Emits ```ts /** * Emitted events for the DashboardSearch component */ interface DashboardSearchEmits { update:open: (payload: [value: boolean]) => void; update:searchTerm: (payload: [value: string]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardSearch: { slots: { modal: '', input: '[&>input]:text-base/5' }, variants: { fullscreen: { false: { modal: 'sm:max-w-3xl sm:h-[28rem]' } } } } } }) ``` ## Changelog ::component-changelog :: # DashboardSearchButton ## Usage The DashboardSearchButton component is used to open the [DashboardSearch](https://ui.nuxt.com/docs/components/dashboard-search) modal. ```vue ``` It extends the [Button](https://ui.nuxt.com/docs/components/button) component, so you can pass any property such as `color`, `variant`, `size`, etc. ```vue ``` ::note{to="https://ui.nuxt.com/#collapsed"} The button defaults to `color="neutral"` and `variant="outline"` when not collapsed, `variant="ghost"` when collapsed. :: ### Collapsed Use the `collapsed` prop to hide the button's label and [kbds](https://ui.nuxt.com/#kbds). Defaults to `false`. ```vue ``` ::tip{to="https://ui.nuxt.com/docs/components/dashboard-sidebar#slots"} When using the button in the **DashboardSidebar** component, use the `collapsed` slot prop directly. :: ### Kbds Use the `kbds` prop to display keyboard keys in the button. Defaults to `['meta', 'K']`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} to match the default shortcut of the [DashboardSearch](https://ui.nuxt.com/docs/components/dashboard-search#shortcut) component. ```vue ``` ## API ### Props ```ts /** * Props for the DashboardSearchButton component */ interface DashboardSearchButtonProps { /** * The icon displayed in the button. */ icon?: string | object | undefined; /** * The label displayed in the button. */ label?: string | undefined; /** * The color of the button. * @default "\"neutral\"" */ color?: "error" | "neutral" | "primary" | "secondary" | "success" | "info" | "warning" | undefined; /** * The variant of the button. * Defaults to 'outline' when not collapsed, 'ghost' when collapsed. */ variant?: "solid" | "outline" | "soft" | "subtle" | "ghost" | "link" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; /** * Whether the button is collapsed. * @default "false" */ collapsed?: boolean | undefined; /** * Display a tooltip on the button when is collapsed with the button label. * This has priority over the global `tooltip` prop. * @default "false" */ tooltip?: boolean | TooltipProps | undefined; /** * The keyboard keys to display in the button. * `{ variant: 'subtle' }`{lang="ts-type"} * @default "[\"meta\", \"k\"]" */ kbds?: (string | undefined)[] | KbdProps[] | undefined; ui?: ({ base?: ClassNameValue; trailing?: ClassNameValue; } & { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; }) | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardSearchButton component */ interface DashboardSearchButtonSlots { leading(): any; default(): any; trailing(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardSearchButton: { slots: { base: '', trailing: 'hidden lg:flex items-center gap-0.5 ms-auto' } } } }) ``` ## Changelog ::component-changelog :: # DashboardSidebar ## Usage The DashboardSidebar component is used to display a sidebar. Its state (size, collapsed, etc.) will be saved based on the `storage` and `storage-key` props you provide to the [DashboardGroup](https://ui.nuxt.com/docs/components/dashboard-group#props) component. Use it inside the default slot of the [DashboardGroup](https://ui.nuxt.com/docs/components/dashboard-group) component: ```vue [layouts/dashboard.vue] {3} ``` ::warning This component does not have a single root element when using the `resizable` prop, so wrap it in a container (e.g., `
`) if you use page transitions or require a single root for layout. :: Use the `left`, `default` and `right` slots to customize the sidebar and the `body` or `content` slots to customize the sidebar menu. ```vue [DashboardSidebarExample.vue] ``` ::note Drag the sidebar near the left edge of the screen to collapse it. :: ### Resizable Use the `resizable` prop to make the sidebar resizable. ```vue ``` ### Collapsible Use the `collapsible` prop to make the sidebar collapsible when dragging near the edge of the screen. ::warning The [`DashboardSidebarCollapse`](https://ui.nuxt.com/docs/components/dashboard-sidebar-collapse) component will have no effect if the sidebar is not **collapsible**. :: ```vue ``` ::tip{to="https://ui.nuxt.com/#slots"} You can access the `collapsed` state in the slot props to customize the content of the sidebar when it is collapsed. :: ### Size Use the `min-size`, `max-size`, `default-size` and `collapsed-size` props to customize the size of the sidebar. ```vue ``` ::tip{to="https://ui.nuxt.com/docs/components/dashboard-group#props"} Sizes are calculated as percentages by default. You can change this using the `unit` prop on the `DashboardGroup` component. :: ::note The `collapsed-size` prop is set to `0` by default but the sidebar has a `min-w-16` to make sure it is visible. :: ### Side Use the `side` prop to change the side of the sidebar. Defaults to `left`. ```vue ``` ### Mode Use the `mode` prop to change the mode of the sidebar menu. Defaults to `slideover`. Use the `body` slot to fill the menu body (under the header) or the `content` slot to fill the entire menu. ::tip{to="https://ui.nuxt.com/#props"} You can use the `menu` prop to customize the menu of the sidebar, it will adapt depending on the mode you choose. :: ```vue [DashboardSidebarModeExample.vue] ``` ::note These examples contain the [`DashboardGroup`](https://ui.nuxt.com/docs/components/dashboard-group), [`DashboardPanel`](https://ui.nuxt.com/docs/components/dashboard-panel) and [`DashboardNavbar`](https://ui.nuxt.com/docs/components/dashboard-navbar) components as they are required to demonstrate the sidebar on mobile. :: ### Toggle Use the `toggle` prop to customize the [DashboardSidebarToggle](https://ui.nuxt.com/docs/components/dashboard-sidebar-toggle) component displayed on mobile. You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue [DashboardSidebarToggleExample.vue] ``` ### Toggle Side Use the `toggle-side` prop to change the side of the toggle button. Defaults to `left`. ```vue [DashboardSidebarToggleSideExample.vue] ``` ## Examples ### Control open state You can control the open state by using the `open` prop or the `v-model:open` directive. ```vue [DashboardSidebarOpenExample.vue] ``` ::note In this example, leveraging [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts), you can toggle the open state of the DashboardSidebar by pressing ``. :: ### Control collapsed state You can control the collapsed state by using the `collapsed` prop or the `v-model:collapsed` directive. ```vue [DashboardSidebarCollapsedExample.vue] ``` ::note In this example, leveraging [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts), you can toggle the collapsed state of the DashboardSidebar by pressing ``. :: ## API ### Props ```ts /** * Props for the DashboardSidebar component */ interface DashboardSidebarProps { /** * The mode of the sidebar menu. * @default "\"slideover\" as never" */ mode?: DashboardSidebarMode | undefined; /** * The props for the sidebar menu component. */ menu?: ModalProps | SlideoverProps | DrawerProps | undefined; /** * Customize the toggle button to open the sidebar. * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"} * @default "true" */ toggle?: boolean | Partial | undefined; /** * The side to render the toggle button on. * @default "\"left\"" */ toggleSide?: "left" | "right" | undefined; ui?: { root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; toggle?: ClassNameValue; handle?: ClassNameValue; content?: ClassNameValue; overlay?: ClassNameValue; } | undefined; /** * The id of the panel. */ id?: string | undefined; /** * The side to render the panel on. * @default "\"left\"" */ side?: "left" | "right" | undefined; /** * The minimum size of the panel. * @default "10" */ minSize?: number | undefined; /** * The maximum size of the panel. * @default "20" */ maxSize?: number | undefined; /** * The default size of the panel. * @default "15" */ defaultSize?: number | undefined; /** * Whether to allow the user to resize the panel. * @default "false" */ resizable?: boolean | undefined; /** * Whether to allow the user to collapse the panel. * @default "false" */ collapsible?: boolean | undefined; /** * The size of the panel when collapsed. * @default "0" */ collapsedSize?: number | undefined; /** * @default "false" */ open?: boolean | undefined; /** * @default "false" */ collapsed?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardSidebar component */ interface DashboardSidebarSlots { header(): any; default(): any; footer(): any; toggle(): any; content(): any; resize-handle(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardSidebar: { slots: { root: 'relative hidden lg:flex flex-col min-h-svh min-w-16 w-(--width) shrink-0', header: 'h-(--ui-header-height) shrink-0 flex items-center gap-1.5 px-4', body: 'flex flex-col gap-4 flex-1 overflow-y-auto px-4 py-2', footer: 'shrink-0 flex items-center gap-1.5 px-4 py-2', toggle: '', handle: '', content: 'lg:hidden', overlay: 'lg:hidden' }, variants: { menu: { true: { header: 'sm:px-6', body: 'sm:px-6', footer: 'sm:px-6' } }, side: { left: { root: 'border-e border-default' }, right: { root: '' } }, toggleSide: { left: { toggle: '' }, right: { toggle: 'ms-auto' } } } } } }) ``` ## Changelog ::component-changelog :: # DashboardSidebarCollapse ## Usage The DashboardSidebarCollapse component is used to collapse/expand the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) component **when its `collapsible` prop is set**. ```vue ``` It extends the [Button](https://ui.nuxt.com/docs/components/button) component, so you can pass any property such as `color`, `variant`, `size`, etc. ```vue ``` ::note The button defaults to `color="neutral"` and `variant="ghost"`. :: ## Examples ### Within `header` slot You can put this component in the `header` slot of the [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) component and use the `collapsed` prop to hide the left part of the header for example: ```vue [layouts/dashboard.vue] {4-8} ``` ### Within `leading` slot You can put this component in the `leading` slot of the [DashboardNavbar](https://ui.nuxt.com/docs/components/dashboard-navbar) component to display it before the title for example: ```vue [pages/index.vue] {11-13} ``` ## API ### Props ```ts /** * Props for the DashboardSidebarCollapse component */ interface DashboardSidebarCollapseProps { /** * @default "\"left\"" */ side?: "left" | "right" | undefined; /** * @default "\"neutral\"" */ color?: "error" | "neutral" | "primary" | "secondary" | "success" | "info" | "warning" | undefined; /** * @default "\"ghost\"" */ variant?: "link" | "ghost" | "solid" | "outline" | "soft" | "subtle" | undefined; ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined; /** * The element or component this component should render as when not a link. */ as?: any; disabled?: boolean | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardSidebarCollapse: { base: 'hidden lg:flex', variants: { side: { left: '', right: '' } } } } }) ``` ## Changelog ::component-changelog :: # DashboardSidebarToggle ## Usage The DashboardSidebarToggle component is used by the [DashboardNavbar](https://ui.nuxt.com/docs/components/dashboard-navbar) and [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) components. It is automatically displayed on mobile to toggle the sidebar, **you don't have to add it manually**. ```vue ``` It extends the [Button](https://ui.nuxt.com/docs/components/button) component, so you can pass any property such as `color`, `variant`, `size`, etc. ```vue ``` ::note The button defaults to `color="neutral"` and `variant="ghost"`. :: ## Examples ### Within `toggle` slot Even though this component is automatically displayed on mobile, you can use the `toggle` slot of the [DashboardNavbar](https://ui.nuxt.com/docs/components/dashboard-navbar) and [DashboardSidebar](https://ui.nuxt.com/docs/components/dashboard-sidebar) components to customize the button. ::code-group ```vue [layouts/dashboard.vue] {4-6} ``` ```vue [pages/index.vue] {11-13} ``` :: ::tip When using the `toggle-side` prop of the `DashboardSidebar` and `DashboardNavbar` components, the button will be displayed on the specified side. :: ## API ### Props ```ts /** * Props for the DashboardSidebarToggle component */ interface DashboardSidebarToggleProps { /** * @default "\"left\"" */ side?: "left" | "right" | undefined; /** * @default "\"neutral\"" */ color?: "error" | "neutral" | "primary" | "secondary" | "success" | "info" | "warning" | undefined; /** * @default "\"ghost\"" */ variant?: "link" | "ghost" | "solid" | "outline" | "soft" | "subtle" | undefined; ui?: { base?: ClassNameValue; label?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; } | undefined; /** * The element or component this component should render as when not a link. */ as?: any; disabled?: boolean | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardSidebarToggle: { base: 'lg:hidden', variants: { side: { left: '', right: '' } } } } }) ``` ## Changelog ::component-changelog :: # DashboardToolbar ## Usage The DashboardToolbar component is used to display a toolbar under the [DashboardNavbar](https://ui.nuxt.com/docs/components/dashboard-navbar) component. Use it inside the `header` slot of the [DashboardPanel](https://ui.nuxt.com/docs/components/dashboard-panel) component: ```vue [pages/index.vue] {9-13} ``` Use the `left`, `default` and `right` slots to customize the toolbar. ```vue [DashboardToolbarExample.vue] ``` ::note In this example, we use the [NavigationMenu](https://ui.nuxt.com/docs/components/navigation-menu) component to render some links. :: ## API ### Props ```ts /** * Props for the DashboardToolbar component */ interface DashboardToolbarProps { /** * The element or component this component should render as. */ as?: any; ui?: { root?: ClassNameValue; left?: ClassNameValue; right?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the DashboardToolbar component */ interface DashboardToolbarSlots { default(): any; left(): any; right(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dashboardToolbar: { slots: { root: 'shrink-0 flex items-center justify-between border-b border-default px-4 sm:px-6 gap-1.5 overflow-x-auto min-h-[49px]', left: 'flex items-center gap-1.5', right: 'flex items-center gap-1.5' } } } }) ``` ## Changelog ::component-changelog :: # Drawer ## Usage Use a [Button](https://ui.nuxt.com/docs/components/button) or any other component in the default slot of the Drawer. Then, use the `#content` slot to add the content displayed when the Drawer is open. ```vue ``` You can also use the `#header`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}, `#body`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} and `#footer`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} slots to customize the Drawer's content. ### Title Use the `title` prop to set the title of the Drawer's header. ```vue ``` ### Description Use the `description` prop to set the description of the Drawer's header. ```vue ``` ### Direction Use the `direction` prop to control the direction of the Drawer. Defaults to `bottom`. ```vue ``` ### Inset Use the `inset` prop to inset the Drawer from the edges. ```vue ``` ### Handle Use the `handle` prop to control whether the Drawer has a handle or not. Defaults to `true`. ```vue ``` ### Handle Only Use the `handle-only` prop to only allow the Drawer to be dragged by the handle. ```vue ``` ### Overlay Use the `overlay` prop to control whether the Drawer has an overlay or not. Defaults to `true`. ```vue ``` ### Scale Background Use the `should-scale-background` prop to scale the background when the Drawer is open, creating a visual depth effect. You can set the `set-background-color-on-scale` prop to `false` to prevent changing the background color. ```vue ``` ::warning Make sure to add the `data-vaul-drawer-wrapper` directive to a parent element of your app to make this work. ```vue [app.vue] ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ app: { rootAttrs: { 'data-vaul-drawer-wrapper': '', 'class': 'bg-default' } } }) ``` :: ## Examples ### Control open state You can control the open state by using the `default-open` prop or the `v-model:open` directive. ```vue [DrawerOpenExample.vue] ``` ::note In this example, leveraging [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts), you can toggle the Drawer by pressing ``. :: ::tip This allows you to move the trigger outside of the Drawer or remove it entirely. :: ### Disable dismissal Set the `dismissible` prop to `false` to prevent the Drawer from being closed when clicking outside of it or pressing escape. A `close:prevent` event will be emitted when the user tries to close it. ```vue [DrawerDismissibleExample.vue] ``` ::note In this example, the `header` slot is used to add a close button which is not done by default. :: ### With interactive background Set the `overlay` and `modal` props to `false` alongside the `dismissible` prop to make the Drawer's background interactive without closing the Drawer. ```vue [DrawerModalExample.vue] ``` ### Responsive drawer You can render a [Modal](https://ui.nuxt.com/docs/components/modal) component on desktop and a Drawer on mobile for example. ```vue [DrawerResponsiveExample.vue] ``` ### Nested drawers You can nest drawers within each other by using the `nested` prop. ```vue [DrawerNestedExample.vue] ``` ### With footer slot Use the `#footer` slot to add content after the Drawer's body. ```vue [DrawerFooterSlotExample.vue] ``` ### With command palette You can use a [CommandPalette](https://ui.nuxt.com/docs/components/command-palette) component inside the Drawer's content. ```vue [DrawerCommandPaletteExample.vue] ``` ## API ### Props ```ts /** * Props for the Drawer component */ interface DrawerProps { /** * The element or component this component should render as. */ as?: any; title?: string | undefined; description?: string | undefined; /** * Whether to inset the drawer from the edges. */ inset?: boolean | undefined; /** * The content of the drawer. */ content?: (Omit & Partial>) | undefined; /** * Render an overlay behind the drawer. * @default "true" */ overlay?: boolean | undefined; /** * Render a handle on the drawer. * @default "true" */ handle?: boolean | undefined; /** * Render the drawer in a portal. * @default "true" */ portal?: string | boolean | HTMLElement | undefined; /** * Whether the drawer is nested in another drawer. */ nested?: boolean | undefined; ui?: { overlay?: ClassNameValue; content?: ClassNameValue; handle?: ClassNameValue; container?: ClassNameValue; header?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; } | undefined; /** * When `false` it allows to interact with elements outside of the drawer without closing it. * @default "true" */ modal?: boolean | undefined; open?: boolean | undefined; activeSnapPoint?: string | number | null | undefined; /** * Number between 0 and 1 that determines when the drawer should be closed. * Example: threshold of 0.5 would close the drawer if the user swiped for 50% of the height of the drawer or more. */ closeThreshold?: number | undefined; shouldScaleBackground?: boolean | undefined; /** * When `false` we don't change body's background color when the drawer is open. */ setBackgroundColorOnScale?: boolean | undefined; /** * Duration for which the drawer is not draggable after scrolling content inside of the drawer. */ scrollLockTimeout?: number | undefined; /** * When `true`, don't move the drawer upwards if there's space, but rather only change it's height so it's fully scrollable when the keyboard is open */ fixed?: boolean | undefined; /** * When `false` dragging, clicking outside, pressing esc, etc. will not close the drawer. * Use this in combination with the `open` prop, otherwise you won't be able to open/close the drawer. * @default "true" */ dismissible?: boolean | undefined; /** * Opened by default, skips initial enter animation. Still reacts to `open` state changes */ defaultOpen?: boolean | undefined; /** * Direction of the drawer. Can be `top` or `bottom`, `left`, `right`. * @default "\"bottom\"" */ direction?: DrawerDirection | undefined; /** * When `true` the `body` doesn't get any styles assigned from Vaul */ noBodyStyles?: boolean | undefined; /** * When `true` only allows the drawer to be dragged by the `` component. */ handleOnly?: boolean | undefined; preventScrollRestoration?: boolean | undefined; /** * Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. * Should go from least visible. Example `[0.2, 0.5, 0.8]`. * You can also use px values, which doesn't take screen height into account. */ snapPoints?: (string | number)[] | undefined; } ``` ### Slots ```ts /** * Slots for the Drawer component */ interface DrawerSlots { default(): any; content(): any; header(): any; title(): any; description(): any; body(): any; footer(): any; } ``` ### Emits ```ts /** * Emitted events for the Drawer component */ interface DrawerEmits { update:open: (payload: [open: boolean]) => void; close: (payload: []) => void; drag: (payload: [percentageDragged: number]) => void; close:prevent: (payload: []) => void; release: (payload: [open: boolean]) => void; update:activeSnapPoint: (payload: [val: string | number]) => void; animationEnd: (payload: [open: boolean]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { drawer: { slots: { overlay: 'fixed inset-0 bg-elevated/75', content: 'fixed bg-default ring ring-default flex focus:outline-none', handle: [ 'shrink-0 !bg-accented', 'transition-opacity' ], container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto', header: '', title: 'text-highlighted font-semibold', description: 'mt-1 text-muted text-sm', body: 'flex-1', footer: 'flex flex-col gap-1.5' }, variants: { direction: { top: { content: 'mb-24 flex-col-reverse', handle: 'mb-4' }, right: { content: 'flex-row', handle: '!ml-4' }, bottom: { content: 'mt-24 flex-col', handle: 'mt-4' }, left: { content: 'flex-row-reverse', handle: '!mr-4' } }, inset: { true: { content: 'rounded-lg after:hidden overflow-hidden [--initial-transform:calc(100%+1.5rem)]' } }, snapPoints: { true: '' } }, compoundVariants: [ { direction: [ 'top', 'bottom' ], class: { content: 'h-auto max-h-[96%]', handle: '!w-12 !h-1.5 mx-auto' } }, { direction: [ 'top', 'bottom' ], snapPoints: true, class: { content: 'h-full' } }, { direction: [ 'right', 'left' ], class: { content: 'w-auto max-w-[calc(100%-2rem)]', handle: '!h-12 !w-1.5 mt-auto mb-auto' } }, { direction: [ 'right', 'left' ], snapPoints: true, class: { content: 'w-full' } }, { direction: 'top', inset: true, class: { content: 'inset-x-4 top-4' } }, { direction: 'top', inset: false, class: { content: 'inset-x-0 top-0 rounded-b-lg' } }, { direction: 'bottom', inset: true, class: { content: 'inset-x-4 bottom-4' } }, { direction: 'bottom', inset: false, class: { content: 'inset-x-0 bottom-0 rounded-t-lg' } }, { direction: 'left', inset: true, class: { content: 'inset-y-4 left-4' } }, { direction: 'left', inset: false, class: { content: 'inset-y-0 left-0 rounded-r-lg' } }, { direction: 'right', inset: true, class: { content: 'inset-y-4 right-4' } }, { direction: 'right', inset: false, class: { content: 'inset-y-0 right-0 rounded-l-lg' } } ] } } }) ``` ## Changelog ::component-changelog :: # DropdownMenu ## Usage Use a [Button](https://ui.nuxt.com/docs/components/button) or any other component in the default slot of the DropdownMenu. ```vue ``` ### Items Use the `items` prop as an array of objects with the following properties: - `label?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `icon?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `avatar?: AvatarProps`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `kbds?: string[] | KbdProps[]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`type?: "link" | "label" | "separator" | "checkbox"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-checkbox-items) - [`color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-color-items) - [`checked?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-checkbox-items) - `disabled?: boolean`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`slot?: string`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-custom-slot) - `onSelect?: (e: Event) => void`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - [`onUpdateChecked?: (checked: boolean) => void`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"}](https://ui.nuxt.com/#with-checkbox-items) - `children?: DropdownMenuItem[] | DropdownMenuItem[][]`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `class?: any`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `ui?: { item?: ClassNameValue, label?: ClassNameValue, separator?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelExternalIcon?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue }`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} You can pass any property from the [Link](https://ui.nuxt.com/docs/components/link#props) component such as `to`, `target`, etc. ```vue ``` ::note You can also pass an array of arrays to the `items` prop to create separated groups of items. :: ::tip Each item can take a `children` array of objects with the same properties as the `items` prop to create a nested menu which can be controlled using the `open`, `defaultOpen` and `content` properties. :: ### Content Use the `content` prop to control how the DropdownMenu content is rendered, like its `align` or `side` for example. ```vue ``` ### Arrow Use the `arrow` prop to display an arrow on the DropdownMenu. ```vue ``` ### Size Use the `size` prop to control the size of the DropdownMenu. ```vue ``` ::warning The `size` prop will not be proxied to the Button, you need to set it yourself. :: ::note When using the same size, the DropdownMenu items will be perfectly aligned with the Button. :: ### Disabled Use the `disabled` prop to disable the DropdownMenu. ```vue ``` ## Examples ### With checkbox items You can use the `type` property with `checkbox` and use the `checked` / `onUpdateChecked` properties to control the checked state of the item. ```vue [DropdownMenuCheckboxItemsExample.vue] ``` ::note To ensure reactivity for the `checked` state of items, it's recommended to wrap your `items` array inside a `computed`. :: ### With color items You can use the `color` property to highlight certain items with a color. ```vue [DropdownMenuColorItemsExample.vue] ``` ### Control open state You can control the open state by using the `default-open` prop or the `v-model:open` directive. ```vue [DropdownMenuOpenExample.vue] ``` ::note In this example, leveraging [`defineShortcuts`](https://ui.nuxt.com/docs/composables/define-shortcuts), you can toggle the DropdownMenu by pressing ``. :: ### With custom slot Use the `slot` property to customize a specific item. You will have access to the following slots: - `#{{ item.slot }}`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-leading`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-label`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} - `#{{ item.slot }}-trailing`{.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} ```vue [DropdownMenuCustomSlotExample.vue] ``` ::tip{to="https://ui.nuxt.com/#slots"} You can also use the `#item`, `#item-leading`, `#item-label` and `#item-trailing` slots to customize all items. :: ### With trigger content width You can expand the content to the full width of its button by adding the `w-(--reka-dropdown-menu-trigger-width)` class on the `ui.content` slot. ```vue [DropdownMenuContentWidthExample.vue] ``` ::tip You can also change the content width globally in your `app.config.ts`: ```text export default defineAppConfig({ ui: { dropdownMenu: { slots: { content: 'w-(--reka-dropdown-menu-trigger-width)' } } } }) ``` :: ### Extract shortcuts When you have some items with `kbds` property (displaying some [Kbd](https://ui.nuxt.com/docs/components/kbd)), you can easily make them work with the [defineShortcuts](https://ui.nuxt.com/docs/composables/define-shortcuts) composable. Inside the `defineShortcuts` composable, there is an `extractShortcuts` utility that will extract the shortcuts recursively from the items and return an object that you can pass to `defineShortcuts`. It will automatically call the `select` function of the item when the shortcut is pressed. ```vue ``` ::note In this example, `` ``, `` `` and `` `` would trigger the `select` function of the corresponding item. :: ## API ### Props ```ts /** * Props for the DropdownMenu component */ interface DropdownMenuProps { size?: "sm" | "md" | "xs" | "lg" | "xl" | undefined; items?: ArrayOrNested | undefined; /** * The icon displayed when an item is checked. */ checkedIcon?: string | object | undefined; /** * The icon displayed when an item is loading. */ loadingIcon?: string | object | undefined; /** * The icon displayed when the item is an external link. * Set to `false` to hide the external icon. * @default "true" */ externalIcon?: string | boolean | object | undefined; /** * The content of the menu. */ content?: (Omit & Partial>) | undefined; /** * Display an arrow alongside the menu. */ arrow?: boolean | Omit | undefined; /** * Render the menu in a portal. * @default "true" */ portal?: string | boolean | HTMLElement | undefined; /** * The key used to get the label from the item. * @default "\"label\"" */ labelKey?: GetItemKeys> | undefined; /** * The key used to get the description from the item. * @default "\"description\"" */ descriptionKey?: GetItemKeys> | undefined; disabled?: boolean | undefined; ui?: { content?: ClassNameValue; viewport?: ClassNameValue; arrow?: ClassNameValue; group?: ClassNameValue; label?: ClassNameValue; separator?: ClassNameValue; item?: ClassNameValue; itemLeadingIcon?: ClassNameValue; itemLeadingAvatar?: ClassNameValue; itemLeadingAvatarSize?: ClassNameValue; itemTrailing?: ClassNameValue; itemTrailingIcon?: ClassNameValue; itemTrailingKbds?: ClassNameValue; itemTrailingKbdsSize?: ClassNameValue; itemWrapper?: ClassNameValue; itemLabel?: ClassNameValue; itemDescription?: ClassNameValue; itemLabelExternalIcon?: ClassNameValue; } | undefined; /** * The open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state. */ defaultOpen?: boolean | undefined; /** * The controlled open state of the menu. Can be used as `v-model:open`. */ open?: boolean | undefined; /** * The modality of the dropdown menu. * * When set to `true`, interaction with outside elements will be disabled and only menu content will be visible to screen readers. * @default "true" */ modal?: boolean | undefined; } ``` ### Slots ```ts /** * Slots for the DropdownMenu component */ interface DropdownMenuSlots { default(): any; item(): any; item-leading(): any; item-label(): any; item-description(): any; item-trailing(): any; content-top(): any; content-bottom(): any; } ``` ### Emits ```ts /** * Emitted events for the DropdownMenu component */ interface DropdownMenuEmits { update:open: (payload: [payload: boolean]) => void; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { dropdownMenu: { slots: { content: 'min-w-32 bg-default shadow-lg rounded-md ring ring-default overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-dropdown-menu-content-transform-origin) flex flex-col', viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1', arrow: 'fill-default', group: 'p-1 isolate', label: 'w-full flex items-center font-semibold text-highlighted', separator: '-mx-1 my-1 h-px bg-border', item: 'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75', itemLeadingIcon: 'shrink-0', itemLeadingAvatar: 'shrink-0', itemLeadingAvatarSize: '', itemTrailing: 'ms-auto inline-flex gap-1.5 items-center', itemTrailingIcon: 'shrink-0', itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0', itemTrailingKbdsSize: '', itemWrapper: 'flex-1 flex flex-col text-start min-w-0', itemLabel: 'truncate', itemDescription: 'truncate text-muted', itemLabelExternalIcon: 'inline-block size-3 align-top text-dimmed' }, variants: { color: { primary: '', secondary: '', success: '', info: '', warning: '', error: '', neutral: '' }, active: { true: { item: 'text-highlighted before:bg-elevated', itemLeadingIcon: 'text-default' }, false: { item: [ 'text-default data-highlighted:text-highlighted data-[state=open]:text-highlighted data-highlighted:before:bg-elevated/50 data-[state=open]:before:bg-elevated/50', 'transition-colors before:transition-colors' ], itemLeadingIcon: [ 'text-dimmed group-data-highlighted:text-default group-data-[state=open]:text-default', 'transition-colors' ] } }, loading: { true: { itemLeadingIcon: 'animate-spin' } }, size: { xs: { label: 'p-1 text-xs gap-1', item: 'p-1 text-xs gap-1', itemLeadingIcon: 'size-4', itemLeadingAvatarSize: '3xs', itemTrailingIcon: 'size-4', itemTrailingKbds: 'gap-0.5', itemTrailingKbdsSize: 'sm' }, sm: { label: 'p-1.5 text-xs gap-1.5', item: 'p-1.5 text-xs gap-1.5', itemLeadingIcon: 'size-4', itemLeadingAvatarSize: '3xs', itemTrailingIcon: 'size-4', itemTrailingKbds: 'gap-0.5', itemTrailingKbdsSize: 'sm' }, md: { label: 'p-1.5 text-sm gap-1.5', item: 'p-1.5 text-sm gap-1.5', itemLeadingIcon: 'size-5', itemLeadingAvatarSize: '2xs', itemTrailingIcon: 'size-5', itemTrailingKbds: 'gap-0.5', itemTrailingKbdsSize: 'md' }, lg: { label: 'p-2 text-sm gap-2', item: 'p-2 text-sm gap-2', itemLeadingIcon: 'size-5', itemLeadingAvatarSize: '2xs', itemTrailingIcon: 'size-5', itemTrailingKbds: 'gap-1', itemTrailingKbdsSize: 'md' }, xl: { label: 'p-2 text-base gap-2', item: 'p-2 text-base gap-2', itemLeadingIcon: 'size-6', itemLeadingAvatarSize: 'xs', itemTrailingIcon: 'size-6', itemTrailingKbds: 'gap-1', itemTrailingKbdsSize: 'lg' } } }, compoundVariants: [ { color: 'primary', active: false, class: { item: 'text-primary data-highlighted:text-primary data-highlighted:before:bg-primary/10 data-[state=open]:before:bg-primary/10', itemLeadingIcon: 'text-primary/75 group-data-highlighted:text-primary group-data-[state=open]:text-primary' } }, { color: 'secondary', active: false, class: { item: 'text-secondary data-highlighted:text-secondary data-highlighted:before:bg-secondary/10 data-[state=open]:before:bg-secondary/10', itemLeadingIcon: 'text-secondary/75 group-data-highlighted:text-secondary group-data-[state=open]:text-secondary' } }, { color: 'success', active: false, class: { item: 'text-success data-highlighted:text-success data-highlighted:before:bg-success/10 data-[state=open]:before:bg-success/10', itemLeadingIcon: 'text-success/75 group-data-highlighted:text-success group-data-[state=open]:text-success' } }, { color: 'info', active: false, class: { item: 'text-info data-highlighted:text-info data-highlighted:before:bg-info/10 data-[state=open]:before:bg-info/10', itemLeadingIcon: 'text-info/75 group-data-highlighted:text-info group-data-[state=open]:text-info' } }, { color: 'warning', active: false, class: { item: 'text-warning data-highlighted:text-warning data-highlighted:before:bg-warning/10 data-[state=open]:before:bg-warning/10', itemLeadingIcon: 'text-warning/75 group-data-highlighted:text-warning group-data-[state=open]:text-warning' } }, { color: 'error', active: false, class: { item: 'text-error data-highlighted:text-error data-highlighted:before:bg-error/10 data-[state=open]:before:bg-error/10', itemLeadingIcon: 'text-error/75 group-data-highlighted:text-error group-data-[state=open]:text-error' } }, { color: 'primary', active: true, class: { item: 'text-primary before:bg-primary/10', itemLeadingIcon: 'text-primary' } }, { color: 'secondary', active: true, class: { item: 'text-secondary before:bg-secondary/10', itemLeadingIcon: 'text-secondary' } }, { color: 'success', active: true, class: { item: 'text-success before:bg-success/10', itemLeadingIcon: 'text-success' } }, { color: 'info', active: true, class: { item: 'text-info before:bg-info/10', itemLeadingIcon: 'text-info' } }, { color: 'warning', active: true, class: { item: 'text-warning before:bg-warning/10', itemLeadingIcon: 'text-warning' } }, { color: 'error', active: true, class: { item: 'text-error before:bg-error/10', itemLeadingIcon: 'text-error' } } ], defaultVariants: { size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # Empty ## Usage ::code-preview :::u-empty --- actions: - icon: i-lucide-plus label: Create new - icon: i-lucide-refresh-cw label: Refresh color: neutral variant: subtle description: It looks like you haven't added any projects. Create one to get started. icon: i-lucide-file title: No projects found --- ::: :: ### Title Use the `title` prop to set the title of the empty state. ```vue ``` ### Description Use the `description` prop to set the description of the empty state. ```vue ``` ### Icon Use the `icon` prop to set the icon of the empty state. ```vue ``` ### Avatar Use the `avatar` prop to set the avatar of the empty state. ```vue ``` ### Actions Use the `actions` prop to add some [Button](https://ui.nuxt.com/docs/components/button) actions to the empty state. ```vue ``` ### Variant Use the `variant` prop to change the variant of the empty state. ```vue ``` ### Size Use the `size` prop to change the size of the empty state. ```vue ``` ## Examples ### With slots Use the available slots to create a more complex empty state. ```vue [EmptySlotsExample.vue] ``` ## API ### Props ```ts /** * Props for the Empty component */ interface EmptyProps { /** * The element or component this component should render as. */ as?: any; /** * The icon displayed above the title. */ icon?: string | object | undefined; avatar?: AvatarProps | undefined; title?: string | undefined; description?: string | undefined; /** * Display a list of Button in the body. */ actions?: ButtonProps[] | undefined; variant?: "outline" | "solid" | "soft" | "subtle" | "naked" | undefined; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; ui?: { root?: ClassNameValue; header?: ClassNameValue; avatar?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; body?: ClassNameValue; actions?: ClassNameValue; footer?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the Empty component */ interface EmptySlots { header(): any; leading(): any; title(): any; description(): any; body(): any; actions(): any; footer(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { empty: { slots: { root: 'relative flex flex-col items-center justify-center gap-4 rounded-lg p-4 sm:p-6 lg:p-8 min-w-0', header: 'flex flex-col items-center gap-2 max-w-sm text-center', avatar: 'shrink-0 mb-2', title: 'text-highlighted text-pretty font-medium', description: 'text-balance text-center', body: 'flex flex-col items-center gap-4 max-w-sm', actions: 'flex flex-wrap justify-center gap-2 shrink-0', footer: 'flex flex-col items-center gap-2 max-w-sm' }, variants: { size: { xs: { avatar: 'size-8 text-base', title: 'text-sm', description: 'text-xs' }, sm: { avatar: 'size-9 text-lg', title: 'text-sm', description: 'text-xs' }, md: { avatar: 'size-10 text-xl', title: 'text-base', description: 'text-sm' }, lg: { avatar: 'size-11 text-[22px]', title: 'text-base', description: 'text-sm' }, xl: { avatar: 'size-12 text-2xl', title: 'text-lg', description: 'text-base' } }, variant: { solid: { root: 'bg-inverted', title: 'text-inverted', description: 'text-dimmed' }, outline: { root: 'bg-default ring ring-default', description: 'text-muted' }, soft: { root: 'bg-elevated/50', description: 'text-toned' }, subtle: { root: 'bg-elevated/50 ring ring-default', description: 'text-toned' }, naked: { description: 'text-muted' } } }, defaultVariants: { variant: 'outline', size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # Error ## Usage The Error component works together with the [Header](https://ui.nuxt.com/docs/components/header) component to create a full-height layout that extends to the viewport's available height. ::tip{to="https://ui.nuxt.com/docs/getting-started/theme/css-variables#header"} The Error component uses the `--ui-header-height` CSS variable to position itself correctly below the [Header](https://ui.nuxt.com/docs/components/header). :: ### Error Use the `error` prop to display an error message. ::note --- target: _blank to: https://nuxt.com/docs/guide/directory-structure/error --- In most cases, you will receive the `error` prop in your `error.vue` file. :: ```vue ``` ### Clear Use the `clear` prop to customize or hide the clear button (with `false` value). You can pass any property from the [Button](https://ui.nuxt.com/docs/components/button) component to customize it. ```vue ``` ### Redirect Use the `redirect` prop to redirect the user to a different page when the clear button is clicked. Defaults to `/`. ```vue ``` ## Examples ### Within `error.vue` Use the Error component in your `error.vue`: ```vue [error.vue] {13} ``` ::tip You might want to replicate the code of your `app.vue` inside your `error.vue` file to have the same layout and features, here is an example: {rel="nofollow"} :: ::note You can read more about how to handle errors in the [Nuxt documentation](https://nuxt.com/docs/getting-started/error-handling#error-page){rel="nofollow"}, but when using `nuxt generate` it is recommended to add `fatal: true` inside your `createError` call to make sure the error page is displayed: ```vue [pages/[...slug\\].vue] ``` :: ## API ### Props ```ts /** * Props for the Error component */ interface ErrorProps { /** * The element or component this component should render as. */ as?: any; error?: Partial & { message: string; }> | undefined; /** * The URL to redirect to when the error is cleared. * @default "\"/\"" */ redirect?: string | undefined; /** * Display a button to clear the error in the links slot. * `{ size: 'lg', color: 'primary', variant: 'solid', label: 'Back to home' }`{lang="ts-type"} * @default "true" */ clear?: boolean | Partial | undefined; ui?: { root?: ClassNameValue; statusCode?: ClassNameValue; statusMessage?: ClassNameValue; message?: ClassNameValue; links?: ClassNameValue; } | undefined; } ``` ### Slots ```ts /** * Slots for the Error component */ interface ErrorSlots { default(): any; statusCode(): any; statusMessage(): any; message(): any; links(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { error: { slots: { root: 'min-h-[calc(100vh-var(--ui-header-height))] flex flex-col items-center justify-center text-center', statusCode: 'text-base font-semibold text-primary', statusMessage: 'mt-2 text-4xl sm:text-5xl font-bold text-highlighted text-balance', message: 'mt-4 text-lg text-muted text-balance', links: 'mt-8 flex items-center justify-center gap-6' } } } }) ``` ## Changelog ::component-changelog :: # FieldGroup ## Usage Wrap multiple [Button](https://ui.nuxt.com/components/button) within a FieldGroup to group them together. ```vue ``` ### Size Use the `size` prop to change the size of all the buttons. ```vue ``` ### Orientation Use the `orientation` prop to change the orientation of the buttons. Defaults to `horizontal`. ```vue ``` ## Examples ### With input You can use components like [Input](https://ui.nuxt.com/components/input), [InputMenu](https://ui.nuxt.com/components/input-menu), [Select](https://ui.nuxt.com/components/select) [SelectMenu](https://ui.nuxt.com/components/select-menu), etc. within a field group. ```vue ``` ### With tooltip You can use a [Tooltip](https://ui.nuxt.com/components/tooltip) within a field group. ```vue [FieldGroupTooltipExample.vue] ``` ### With dropdown You can use a [DropdownMenu](https://ui.nuxt.com/components/dropdown-menu) within a field group. ```vue [FieldGroupDropdownExample.vue] ``` ### With badge You can use a [Badge](https://ui.nuxt.com/components/badge) within a field group. ```vue [FieldGroupBadgeExample.vue] ``` ## API ### Props ```ts /** * Props for the FieldGroup component */ interface FieldGroupProps { /** * The element or component this component should render as. */ as?: any; size?: "md" | "xs" | "sm" | "lg" | "xl" | undefined; /** * The orientation the buttons are laid out. * @default "\"horizontal\"" */ orientation?: "horizontal" | "vertical" | undefined; ui?: {} | undefined; } ``` ### Slots ```ts /** * Slots for the FieldGroup component */ interface FieldGroupSlots { default(): any; } ``` ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { fieldGroup: { base: 'relative', variants: { size: { xs: '', sm: '', md: '', lg: '', xl: '' }, orientation: { horizontal: 'inline-flex -space-x-px', vertical: 'flex flex-col -space-y-px' } } } } }) ``` ## Changelog ::component-changelog :: # FileUpload ## Usage Use the `v-model` directive to control the value of the FileUpload. ```vue ``` ### Multiple Use the `multiple` prop to allow multiple files to be selected. ```vue ``` ### Dropzone Use the `dropzone` prop to enable/disable the droppable area. Defaults to `true`. ```vue ``` ### Interactive Use the `interactive` prop to enable/disable the clickable area. Defaults to `true`. ::tip{to="https://ui.nuxt.com/#with-files-bottom-slot"} This can be useful when adding a [`Button`](https://ui.nuxt.com/docs/components/button) component in the `#actions` slot. :: ```vue ``` ### Accept Use the `accept` prop to specify the allowed file types for the input. Provide a comma-separated list of [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types){rel="nofollow"} or file extensions (e.g., `image/png,application/pdf,.jpg`). Defaults to `*` (all file types). ```vue ``` ### Label Use the `label` prop to set the label of the FileUpload. ```vue ``` ### Description Use the `description` prop to set the description of the FileUpload. ```vue ``` ### Icon Use the `icon` prop to set the icon of the FileUpload. Defaults to `i-lucide-upload`. ```vue ``` ::framework-only #nuxt :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/nuxt#theme --- You can customize this icon globally in your `app.config.ts` under `ui.icons.upload` key. ::: #vue :::tip --- to: https://ui.nuxt.com/docs/getting-started/integrations/icons/vue#theme --- You can customize this icon globally in your `vite.config.ts` under `ui.icons.upload` key. ::: :: ### Color Use the `color` prop to change the color of the FileUpload. ```vue ``` ::note The `highlight` prop is used here to show the focus state. It's used internally when a validation error occurs. :: ### Variant Use the `variant` prop to change the variant of the FileUpload. ```vue ``` ### Size Use the `size` prop to change the size of the FileUpload. ```vue ``` ### Layout Use the `layout` prop to change how the files are displayed in the FileUpload. Defaults to `grid`. ::warning This prop only works when `variant` is `area`. :: ```vue ``` ### Position Use the `position` prop to change the position of the files in the FileUpload. Defaults to `outside`. ::warning This prop only works when `variant` is `area` and when `layout` is `list`. :: ```vue ``` ## Examples ### With Form validation You can use the FileUpload within a [Form](https://ui.nuxt.com/docs/components/form) and [FormField](https://ui.nuxt.com/docs/components/form-field) components to handle validation and error handling. ```vue [FileUploadFormValidationExample.vue] ``` ### With default slot You can use the default slot to make your own FileUpload component. ```vue [FileUploadDefaultSlotExample.vue] ``` ### With files-bottom slot You can use the `files-bottom` slot to add a [Button](https://ui.nuxt.com/docs/components/button) under the files list to remove all files for example. ```vue [FileUploadFilesBottomSlotExample.vue] ``` ::note{to="https://ui.nuxt.com/#interactive"} The `interactive` prop is set to `false` in this example to prevent the default clickable area. :: ### With files-top slot You can use the `files-top` slot to add a [Button](https://ui.nuxt.com/docs/components/button) above the files list to add new files for example. ```vue [FileUploadFilesTopSlotExample.vue] ``` ## API ### Props ```ts /** * Props for the FileUpload component */ interface FileUploadProps { /** * The element or component this component should render as. */ as?: any; id?: string | undefined; name?: string | undefined; /** * The icon to display. */ icon?: string | object | undefined; label?: string | undefined; description?: string | undefined; color?: "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" | undefined; /** * The `button` variant is only available when `multiple` is `false`. */ variant?: "button" | "area" | undefined; size?: "xs" | "sm" | "md" | "lg" | "xl" | undefined; /** * The layout of how files are displayed. * Only works when `variant` is `area`. * @default "\"grid\"" */ layout?: "list" | "grid" | undefined; /** * The position of the files. * Only works when `variant` is `area` and when `layout` is `list`. * @default "\"outside\"" */ position?: "inside" | "outside" | undefined; /** * Highlight the ring color like a focus state. */ highlight?: boolean | undefined; /** * Specifies the allowed file types for the input. Provide a comma-separated list of MIME types or file extensions (e.g., "image/png,application/pdf,.jpg"). * @default "\"*\"" */ accept?: string | undefined; /** * @default "false as never" */ multiple?: boolean | undefined; /** * Reset the file input when the dialog is opened. * @default "false" */ reset?: boolean | undefined; /** * Create a zone that allows the user to drop files onto it. * @default "true" */ dropzone?: boolean | undefined; /** * Make the dropzone interactive when the user is clicking on it. * @default "true" */ interactive?: boolean | undefined; required?: boolean | undefined; disabled?: boolean | undefined; /** * The icon to display for the file. */ fileIcon?: string | object | undefined; /** * Configure the delete button for the file. * When `layout` is `grid`, the default is `{ color: 'neutral', variant: 'solid', size: 'xs' }`{lang="ts-type"} * When `layout` is `list`, the default is `{ color: 'neutral', variant: 'link' }`{lang="ts-type"} * @default "true" */ fileDelete?: boolean | Partial | undefined; /** * The icon displayed to delete a file. */ fileDeleteIcon?: string | object | undefined; ui?: { root?: ClassNameValue; base?: ClassNameValue; wrapper?: ClassNameValue; icon?: ClassNameValue; avatar?: ClassNameValue; label?: ClassNameValue; description?: ClassNameValue; actions?: ClassNameValue; files?: ClassNameValue; file?: ClassNameValue; fileLeadingAvatar?: ClassNameValue; fileWrapper?: ClassNameValue; fileName?: ClassNameValue; fileSize?: ClassNameValue; fileTrailingButton?: ClassNameValue; } | undefined; modelValue?: File | File[] | null | undefined; } ``` ### Slots ```ts /** * Slots for the FileUpload component */ interface FileUploadSlots { default(): any; leading(): any; label(): any; description(): any; actions(): any; files(): any; files-top(): any; files-bottom(): any; file(): any; file-leading(): any; file-name(): any; file-size(): any; file-trailing(): any; } ``` ### Emits ```ts /** * Emitted events for the FileUpload component */ interface FileUploadEmits { change: (payload: [event: Event]) => void; update:modelValue: (payload: [value: File | File[] | null | undefined]) => void; } ``` ### Expose When accessing the component via a template ref, you can use the following: | Name | Type | | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `inputRef`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | | `dropzoneRef`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | `Ref`{.language-ts-type.shiki.shiki-themes.material-theme-lighter.material-theme.material-theme-palenight lang="ts-type"} | ## Theme ```ts [app.config.ts] export default defineAppConfig({ ui: { fileUpload: { slots: { root: 'relative flex flex-col', base: [ 'w-full flex-1 bg-default border border-default flex flex-col gap-2 items-stretch justify-center rounded-lg focus-visible:outline-2', 'transition-[background]' ], wrapper: 'flex flex-col items-center justify-center text-center', icon: 'shrink-0', avatar: 'shrink-0', label: 'font-medium text-default mt-2', description: 'text-muted mt-1', actions: 'flex flex-wrap gap-1.5 shrink-0 mt-4', files: '', file: 'relative', fileLeadingAvatar: 'shrink-0', fileWrapper: 'flex flex-col min-w-0', fileName: 'text-default truncate', fileSize: 'text-muted truncate', fileTrailingButton: '' }, variants: { color: { primary: '', secondary: '', success: '', info: '', warning: '', error: '', neutral: '' }, variant: { area: { wrapper: 'px-4 py-3', base: 'p-4' }, button: {} }, size: { xs: { base: 'text-xs', icon: 'size-4', file: 'text-xs px-2 py-1 gap-1', fileWrapper: 'flex-row gap-1' }, sm: { base: 'text-xs', icon: 'size-4', file: 'text-xs px-2.5 py-1.5 gap-1.5', fileWrapper: 'flex-row gap-1' }, md: { base: 'text-sm', icon: 'size-5', file: 'text-xs px-2.5 py-1.5 gap-1.5' }, lg: { base: 'text-sm', icon: 'size-5', file: 'text-sm px-3 py-2 gap-2', fileSize: 'text-xs' }, xl: { base: 'text-base', icon: 'size-6', file: 'text-sm px-3 py-2 gap-2' } }, layout: { list: { root: 'gap-2 items-start', files: 'flex flex-col w-full gap-2', file: 'min-w-0 flex items-center border border-default rounded-md w-full', fileTrailingButton: 'ms-auto' }, grid: { fileWrapper: 'hidden', fileLeadingAvatar: 'size-full rounded-lg', fileTrailingButton: 'absolute -top-1.5 -end-1.5 p-0 rounded-full border-2 border-bg' } }, position: { inside: '', outside: '' }, dropzone: { true: 'border-dashed data-[dragging=true]:bg-elevated/25' }, interactive: { true: '' }, highlight: { true: '' }, multiple: { true: '' }, disabled: { true: 'cursor-not-allowed opacity-75' } }, compoundVariants: [ { color: 'primary', class: 'focus-visible:outline-primary' }, { color: 'secondary', class: 'focus-visible:outline-secondary' }, { color: 'success', class: 'focus-visible:outline-success' }, { color: 'info', class: 'focus-visible:outline-info' }, { color: 'warning', class: 'focus-visible:outline-warning' }, { color: 'error', class: 'focus-visible:outline-error' }, { color: 'primary', highlight: true, class: 'border-primary' }, { color: 'secondary', highlight: true, class: 'border-secondary' }, { color: 'success', highlight: true, class: 'border-success' }, { color: 'info', highlight: true, class: 'border-info' }, { color: 'warning', highlight: true, class: 'border-warning' }, { color: 'error', highlight: true, class: 'border-error' }, { color: 'neutral', class: 'focus-visible:outline-inverted' }, { color: 'neutral', highlight: true, class: 'border-inverted' }, { size: 'xs', layout: 'list', class: { fileTrailingButton: '-me-1' } }, { size: 'sm', layout: 'list', class: { fileTrailingButton: '-me-1.5' } }, { size: 'md', layout: 'list', class: { fileTrailingButton: '-me-1.5' } }, { size: 'lg', layout: 'list', class: { fileTrailingButton: '-me-2' } }, { size: 'xl', layout: 'list', class: { fileTrailingButton: '-me-2' } }, { variant: 'button', size: 'xs', class: { base: 'p-1' } }, { variant: 'button', size: 'sm', class: { base: 'p-1.5' } }, { variant: 'button', size: 'md', class: { base: 'p-1.5' } }, { variant: 'button', size: 'lg', class: { base: 'p-2' } }, { variant: 'button', size: 'xl', class: { base: 'p-2' } }, { layout: 'grid', multiple: true, class: { files: 'grid grid-cols-2 md:grid-cols-3 gap-4 w-full', file: 'p-0 aspect-square' } }, { layout: 'grid', multiple: false, class: { file: 'absolute inset-0 p-0' } }, { interactive: true, disabled: false, class: 'hover:bg-elevated/25' } ], defaultVariants: { color: 'primary', variant: 'area', size: 'md' } } } }) ``` ## Changelog ::component-changelog :: # Footer ## Usage The Footer component renders a `