14 KiB
title, description
| title | description |
|---|---|
| Migration | 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. ::
- Create a
main.cssfile and import it in yournuxt.config.tsfile:
::code-group
@import "tailwindcss" theme(static);
export default defineNuxtConfig({
css: ['~/assets/css/main.css']
})
::
- Run the Tailwind CSS upgrade tool:
npx @tailwindcss/upgrade
Update Nuxt UI
- Install the latest version of the package:
::module-only #ui :::div
::::code-group{sync="pm"}
pnpm add @nuxt/ui
yarn add @nuxt/ui
npm install @nuxt/ui
bun add @nuxt/ui
::::
:::
#ui-pro :::div
::::code-group{sync="pm"}
pnpm add @nuxt/ui-pro
yarn add @nuxt/ui-pro
npm install @nuxt/ui-pro
bun add @nuxt/ui-pro
::::
:::
::
- Import it in your CSS:
::module-only #ui :::div
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
:::
#ui-pro :::div
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
::: ::
::module-only #ui
:::div 5. Wrap you app with the 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:
export default defineNuxtConfig({
- extends: ['@nuxt/ui-pro'],
- modules: ['@nuxt/ui']
+ modules: ['@nuxt/ui-pro']
})
- Wrap you app with the App component: :::
::
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
::
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 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
graycolor has been renamed toneutral
<template>
- <p class="text-gray-500 dark:text-gray-400" />
+ <p class="text-neutral-500 dark:text-neutral-400" />
</template>
::note You can also use the new design tokens to handle light and dark mode:
<template>
- <p class="text-gray-500 dark:text-gray-400" />
+ <p class="text-(--ui-text-muted)" />
- <p class="text-gray-900 dark:text-white" />
+ <p class="text-(--ui-text-highlighted)" />
</template>
::
- The
DEFAULTshade that let you writetext-primaryno longer exists, you can use color shades instead:
<template>
- <p class="text-primary">Hello</p>
+ <p class="text-(--ui-primary)">Hello</p>
</template>
- The
gray,blackandwhitein thecolorprops have been removed in favor ofneutral:
- <UButton color="black" />
+ <UButton color="neutral" />
- <UButton color="gray" />
+ <UButton color="neutral" variant="subtle" />
- <UButton color="white" />
+ <UButton color="neutral" variant="outline" />
- You can no longer use Tailwind CSS colors in the
colorprops, use the new aliases instead:
- <UButton color="red" />
+ <UButton color="error" />
::note{to="/getting-started/theme#colors"} Learn how to extend the design system to add new color aliases. ::
- The color configuration in
app.config.tshas been moved into acolorsobject:
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, which makes all the overrides you made using the app.config.ts and the ui prop obsolete.
- Update your
app.config.tsto override components with their new theme:
export default defineAppConfig({
ui: {
button: {
- font: 'font-bold',
- default: {
- size: 'md',
- color: 'primary'
- }
+ slots: {
+ base: 'font-medium'
+ },
+ defaultVariants: {
+ size: 'md',
+ color: 'primary'
+ }
}
}
})
- Update your
uiprops to override each component's slots using their new theme:
<template>
- <UButton :ui="{ font: 'font-bold' }" />
+ <UButton :ui="{ base: 'font-bold' }" />
</template>
::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 |
Dropdown |
DropdownMenu |
FormGroup |
FormField |
Range |
Slider |
Toggle |
Switch |
Notification |
Toast |
VerticalNavigation |
NavigationMenu with orientation="vertical" |
HorizontalNavigation |
NavigationMenu 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 |
ColorModeToggle |
ColorModeSwitch |
DashboardCard |
Removed (use PageCard instead) |
DashboardLayout |
DashboardGroup |
DashboardModal |
Removed (use Modal instead) |
DashboardNavbarToggle |
DashboardSidebarToggle |
DashboardPage |
Removed |
DashboardPanelContent |
Removed (use #body slot instead) |
DashboardPanelHandle |
DashboardResizeHandle |
DashboardSection |
Removed (use PageCard instead) |
DashboardSidebarLinks |
Removed (use NavigationMenu instead) |
DashboardSlideover |
Removed (use Slideover instead) |
FooterLinks |
Removed (use NavigationMenu instead) |
HeaderLinks |
Removed (use NavigationMenu instead) |
LandingCard |
Removed (use PageCard instead) |
LandingCTA |
PageCTA |
LandingFAQ |
Removed (use PageAccordion instead) |
LandingGrid |
Removed (use PageGrid instead) |
LandingHero |
Removed (use PageHero instead) |
LandingLogos |
PageLogos |
LandingSection |
PageSection |
LandingTestimonial |
Removed (use PageCard instead) |
NavigationAccordion |
ContentNavigation |
NavigationLinks |
ContentNavigation |
NavigationTree |
ContentNavigation |
PageError |
Error |
PricingCard |
PricingPlan |
PricingGrid |
PricingPlans |
PricingSwitch |
Removed (use Switch or 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
linksandoptionsprops have been renamed toitemsfor consistency:
<template>
- <USelect :options="countries" />
+ <USelect :items="countries" />
- <UHorizontalNavigation :links="links" />
+ <UNavigationMenu :items="links" />
</template>
::note
This change affects the following components: Breadcrumb, HorizontalNavigation, InputMenu, RadioGroup, Select, SelectMenu, VerticalNavigation.
::
- The global
Modals,SlideoversandNotificationscomponents have been removed in favor the App component:
<template>
+ <UApp>
+ <NuxtPage />
+ </UApp>
- <UModals />
- <USlideovers />
- <UNotifications />
</template>
- The
v-model:opendirective anddefault-openprop are now used to control visibility:
<template>
- <UModal v-model="open" />
+ <UModal v-model:open="open" />
</template>
::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
#contentslot (you don't need to use av-model:opendirective with this method):
<script setup lang="ts">
- const open = ref(false)
</script>
<template>
- <UButton label="Open" @click="open = true" />
- <UModal v-model="open">
+ <UModal>
+ <UButton label="Open" />
+ <template #content>
<div class="p-4">
<Placeholder class="h-48" />
</div>
+ </template>
</UModal>
</template>
::note
This change affects the following components: Modal, Popover, Slideover, Tooltip.
::
- A
#header,#bodyand#footerslots have been added inside the#contentslot like theCardcomponent:
<template>
- <UModal>
+ <UModal title="Title" description="Description">
- <div class="p-4">
+ <template #body>
<Placeholder class="h-48" />
+ </template>
- </div>
</UModal>
</template>
::note
This change affects the following components: Modal, Slideover.
::
Changed composables
- The
useToast()composabletimeoutprop has been renamed toduration:
<script setup lang="ts">
const toast = useToast()
- toast.add({ title: 'Invitation sent', timeout: 0 })
+ toast.add({ title: 'Invitation sent', duration: 0 })
</script>
- The
useModalanduseSlideovercomposables have been removed in favor of a more genericuseOverlaycomposable:
Some important differences:
- The
useOverlaycomposable is now used to create overlay instances - Overlays that are opened, can be awaited for their result
- Overlays can no longer be closed using
modal.close()orslideover.close(), rather, they close automatically: either when aclosedevent 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
closedevent with the desired value
<script setup lang="ts">
import { ModalExampleComponent } from '#components'
- const modal = useModal()
+ const overlay = useOverlay()
- modal.open(ModalExampleComponent)
+ const modal = overlay.create(ModalExampleComponent)
</script>
Props are now passed through a props attribute:
<script setup lang="ts">
import { ModalExampleComponent } from '#components'
- const modal = useModal()
+ const overlay = useOverlay()
const count = ref(0)
- modal.open(ModalExampleComponent, {
- count: count.value
- })
+ const modal = overlay.create(ModalExampleComponent, {
+ props: {
+ count: count.value
+ }
+ })
</script>
Closing a modal is now done through the closed event. The modal.open method now returns a promise that resolves to the result of the modal whenever the modal is closed:
<script setup lang="ts">
import { ModalExampleComponent } from '#components'
- const modal = useModal()
+ const overlay = useOverlay()
+ const modal = overlay.create(ModalExampleComponent)
- function openModal() {
- modal.open(ModalExampleComponent, {
- onSuccess() {
- toast.add({ title: 'Success!' })
- }
- })
- }
+ async function openModal() {
+ const result = await modal.open(ModalExampleComponent, {
+ count: count.value
+ })
+
+ if (result) {
+ toast.add({ title: 'Success!' })
+ }
+ }
</script>
::warning This page is a work in progress, we'll improve it regularly. ::