Migration
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
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.
- Create a
main.css
file and import it in yournuxt.config.ts
file:
@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:
pnpm add @nuxt/ui@next
yarn add @nuxt/ui@next
npm install @nuxt/ui@next
bun add @nuxt/ui@next
pnpm add @nuxt/ui-pro@next
yarn add @nuxt/ui-pro@next
npm install @nuxt/ui-pro@next
bun add @nuxt/ui-pro@next
- Import it in your CSS:
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
- Wrap you app with the App component:
- Add the
@nuxt/ui-pro
module in yournuxt.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 | green | Main brand color, used as the default color for components. |
secondary | blue | Secondary color to complement the primary color. |
success | green | Used for success states. |
info | blue | Used for informational states. |
warning | yellow | Used for warning states. |
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 toneutral
<template>
- <p class="text-gray-500 dark:text-gray-400" />
+ <p class="text-neutral-500 dark:text-neutral-400" />
</template>
<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
DEFAULT
shade that let you writetext-primary
no 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
,black
andwhite
in thecolor
props 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
color
props, use the new aliases instead:
- <UButton color="red" />
+ <UButton color="error" />
- The color configuration in
app.config.ts
has been moved into acolors
object:
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.ts
to 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
ui
props to override each component's slots using their new theme:
<template>
- <UButton :ui="{ font: 'font-bold' }" />
+ <UButton :ui="{ base: 'font-bold' }" />
</template>
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" |
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
links
andoptions
props have been renamed toitems
for consistency:
<template>
- <USelect :options="countries" />
+ <USelect :items="countries" />
- <UHorizontalNavigation :links="links" />
+ <UNavigationMenu :items="links" />
</template>
Breadcrumb
, HorizontalNavigation
, InputMenu
, RadioGroup
, Select
, SelectMenu
, VerticalNavigation
.- The global
Modals
,Slideovers
andNotifications
components have been removed in favor the App component:
<template>
+ <UApp>
+ <NuxtPage />
+ </UApp>
- <UModals />
- <USlideovers />
- <UNotifications />
</template>
- The
v-model:open
directive anddefault-open
prop are now used to control visibility:
<template>
- <UModal v-model="open" />
+ <UModal v-model:open="open" />
</template>
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 av-model:open
directive 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>
Modal
, Popover
, Slideover
, Tooltip
.- A
#header
,#body
and#footer
slots have been added inside the#content
slot like theCard
component:
<template>
- <UModal>
+ <UModal title="Title" description="Description">
- <div class="p-4">
+ <template #body>
<Placeholder class="h-48" />
+ </template>
- </div>
</UModal>
</template>
Modal
, Slideover
.Changed composables
- The
useToast()
composabletimeout
prop 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
useModal
anduseSlideover
composables have been removed in favor of a more genericuseOverlay
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 closed using
modal.close()
orslideover.close()
, rather, they close automatically: either when aclosed
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
closed
event 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>