--- title: Migration description: 'A comprehensive guide to migrate your application from Nuxt UI v2 to Nuxt UI v3.' --- Nuxt UI v3.0 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{to="https://tailwindcss.com/docs/upgrade-guide#changes-from-v3" target="_blank"} 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: ::module-only #ui :::div ::::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 ``` :::: ::: #ui-pro :::div ::::code-group{sync="pm"} ```bash [pnpm] pnpm add @nuxt/ui-pro ``` ```bash [yarn] yarn add @nuxt/ui-pro ``` ```bash [npm] npm install @nuxt/ui-pro ``` ```bash [bun] bun add @nuxt/ui-pro ``` :::: ::: :: 4. Import it in your CSS: ::module-only #ui :::div ```css [app/assets/css/main.css]{2} @import "tailwindcss"; @import "@nuxt/ui"; ``` ::: #ui-pro :::div ```css [app/assets/css/main.css]{2} @import "tailwindcss"; @import "@nuxt/ui-pro"; ``` ::: :: ::module-only #ui :::div 5. Wrap your app with the [App](/components/app) component: ::: #ui-pro :::div 5. Add the `@nuxt/ui-pro` module in your `nuxt.config.ts` file as it's no longer a layer: ```diff [nuxt.config.ts] export default defineNuxtConfig({ - extends: ['@nuxt/ui-pro'], - modules: ['@nuxt/ui'] + modules: ['@nuxt/ui-pro'] }) ``` 6. Wrap your app with the [App](/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](/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](/getting-started/theme#neutral-palette) 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="/getting-started/theme#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](/getting-started/theme#components-theme), which makes all the overrides you made using the `app.config.ts` and the `ui` prop obsolete. - Update your [`app.config.ts`](/getting-started/theme#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](/getting-started/theme#props) to override each component's slots using their new theme: ```diff ``` ::tip{to="/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`](/components/separator) | | `Dropdown` | [`DropdownMenu`](/components/dropdown-menu) | | `FormGroup` | [`FormField`](/components/form-field) | | `Range` | [`Slider`](/components/slider) | | `Toggle` | [`Switch`](/components/switch) | | `Notification` | [`Toast`](/components/toast) | | `VerticalNavigation` | [`NavigationMenu`](/components/navigation-menu) with `orientation="vertical"` | | `HorizontalNavigation` | [`NavigationMenu`](/components/navigation-menu) with `orientation="horizontal"` | ::module-only #ui-pro :::div Here are the Nuxt UI Pro components that have been renamed or removed: | v1 | v3 | | --- | --- | | `BlogList` | [`BlogPosts`](/components/blog-posts) | | `ColorModeToggle` | [`ColorModeSwitch`](/components/color-mode-switch) | | `DashboardCard` | Removed (use [`PageCard`](/components/page-card) instead) | | `DashboardLayout` | [`DashboardGroup`](/components/dashboard-group) | | `DashboardModal` | Removed (use [`Modal`](/components/modal) instead) | | `DashboardNavbarToggle` | [`DashboardSidebarToggle`](/components/dashboard-sidebar-toggle) | | `DashboardPage` | Removed | | `DashboardPanelContent` | Removed (use `#body` slot instead) | | `DashboardPanelHandle` | [`DashboardResizeHandle`](/components/dashboard-resize-handle) | | `DashboardSection` | Removed (use [`PageCard`](/components/page-card) instead) | | `DashboardSidebarLinks` | Removed (use [`NavigationMenu`](/components/navigation-menu) instead) | | `DashboardSlideover` | Removed (use [`Slideover`](/components/slideover) instead) | | `FooterLinks` | Removed (use [`NavigationMenu`](/components/navigation-menu) instead) | | `HeaderLinks` | Removed (use [`NavigationMenu`](/components/navigation-menu) instead) | | `LandingCard` | Removed (use [`PageCard`](/components/page-card) instead) | | `LandingCTA` | [`PageCTA`](/components/page-cta) | | `LandingFAQ` | Removed (use [`PageAccordion`](/components/page-accordion) instead) | | `LandingGrid` | Removed (use [`PageGrid`](/components/page-grid) instead) | | `LandingHero` | Removed (use [`PageHero`](/components/page-hero) instead) | | `LandingLogos` | [`PageLogos`](/components/page-logos) | | `LandingSection` | [`PageSection`](/components/page-section) | | `LandingTestimonial` | Removed (use [`PageCard`](/components/page-card#as-a-testimonial) instead) | | `NavigationAccordion` | [`ContentNavigation`](/components/content-navigation) | | `NavigationLinks` | [`ContentNavigation`](/components/content-navigation) | | `NavigationTree` | [`ContentNavigation`](/components/content-navigation) | | `PageError` | [`Error`](/components/error) | | `PricingCard` | [`PricingPlan`](/components/pricing-plan) | | `PricingGrid` | [`PricingPlans`](/components/pricing-plans) | | `PricingSwitch` | Removed (use [`Switch`](/components/switch) or [`Tabs`](/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](/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 a promise that resolves to the result of the modal whenever the modal is close: ```diff ``` --- ::warning This page is a work in progress, we'll improve it regularly. ::