---
title: "Migration to v3"
description: "A comprehensive guide to migrate your application from Nuxt UI v2 to Nuxt UI v3."
canonical_url: "https://ui.nuxt.com/docs/getting-started/migration/v3"
last_updated: "2026-04-30"
---
# Migration to v3

> A comprehensive guide to migrate your application from Nuxt UI v2 to Nuxt UI 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

### 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]
> See: 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:

```css [app/assets/css/main.css]
@import "tailwindcss";
```

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  css: ['~/assets/css/main.css']
})
```

1. Run the Tailwind CSS upgrade tool:

```bash
npx @tailwindcss/upgrade
```

### Update Nuxt UI

1. Install the latest version of the package:

```bash [pnpm]
pnpm add @nuxt/ui
```

```bash [yarn]
yarn add @nuxt/ui
```

```bash [npm]
npm install @nuxt/ui
```

```bash [bun]
bun add @nuxt/ui
```

1. Import it in your CSS:

```css [app/assets/css/main.css]
@import "tailwindcss";
@import "@nuxt/ui";
```

1. Wrap your app with the [App](/docs/components/app) component:

```vue [app.vue]
<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](/docs/getting-started/theme/design-system) with 7 color aliases:

<table>
<thead>
  <tr>
    <th>
      Color
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code color="primary">
        primary
      </code>
    </td>
    
    <td>
      <code>
        green
      </code>
    </td>
    
    <td>
      Main brand color, used as the default color for components.
    </td>
  </tr>
  
  <tr>
    <td>
      <code color="secondary">
        secondary
      </code>
    </td>
    
    <td>
      <code>
        blue
      </code>
    </td>
    
    <td>
      Secondary color to complement the primary color.
    </td>
  </tr>
  
  <tr>
    <td>
      <code color="success">
        success
      </code>
    </td>
    
    <td>
      <code>
        green
      </code>
    </td>
    
    <td>
      Used for success states.
    </td>
  </tr>
  
  <tr>
    <td>
      <code color="info">
        info
      </code>
    </td>
    
    <td>
      <code>
        blue
      </code>
    </td>
    
    <td>
      Used for informational states.
    </td>
  </tr>
  
  <tr>
    <td>
      <code color="warning">
        warning
      </code>
    </td>
    
    <td>
      <code>
        yellow
      </code>
    </td>
    
    <td>
      Used for warning states.
    </td>
  </tr>
  
  <tr>
    <td>
      <code color="error">
        error
      </code>
    </td>
    
    <td>
      <code>
        red
      </code>
    </td>
    
    <td>
      Used for form error validation states.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        neutral
      </code>
    </td>
    
    <td>
      <code>
        slate
      </code>
    </td>
    
    <td>
      Neutral color for backgrounds, text, etc.
    </td>
  </tr>
</tbody>
</table>

This change introduces several breaking changes that you need to be aware of:

- The `gray` color has been renamed to `neutral`

```diff
<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](/docs/getting-started/theme/css-variables) to handle light and dark mode:
> 
> ```diff
> <template>
> - <p class="text-gray-500 dark:text-gray-400" />
> + <p class="text-muted" />
> 
> - <p class="text-gray-900 dark:text-white" />
> + <p class="text-highlighted" />
> </template>
> ```

- The `gray`, `black` and `white` in the `color` props have been removed in favor of `neutral`:

```diff
- <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:

```diff
- <UButton color="red" />
+ <UButton color="error" />
```

> [!NOTE]
> See: /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](/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`](/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](/docs/getting-started/theme/components#ui-prop) to override each component's slots using their new theme:

```diff
<template>
- <UButton :ui="{ font: 'font-bold' }" />
+ <UButton :ui="{ base: 'font-bold' }" />
</template>
```

> [!TIP]
> See: /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:

<table>
<thead>
  <tr>
    <th>
      v2
    </th>
    
    <th>
      v3
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        Divider
      </code>
    </td>
    
    <td>
      <a href="/docs/components/separator">
        <code>
          Separator
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Dropdown
      </code>
    </td>
    
    <td>
      <a href="/docs/components/dropdown-menu">
        <code>
          DropdownMenu
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        FormGroup
      </code>
    </td>
    
    <td>
      <a href="/docs/components/form-field">
        <code>
          FormField
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Range
      </code>
    </td>
    
    <td>
      <a href="/docs/components/slider">
        <code>
          Slider
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Toggle
      </code>
    </td>
    
    <td>
      <a href="/docs/components/switch">
        <code>
          Switch
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Meter
      </code>
    </td>
    
    <td>
      Removed
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Notification
      </code>
    </td>
    
    <td>
      <a href="/docs/components/toast">
        <code>
          Toast
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        Radio
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/radio-group">
        <code>
          RadioGroup
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        VerticalNavigation
      </code>
    </td>
    
    <td>
      <a href="/docs/components/navigation-menu">
        <code>
          NavigationMenu
        </code>
      </a>
      
       with <code>
        orientation="vertical"
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        HorizontalNavigation
      </code>
    </td>
    
    <td>
      <a href="/docs/components/navigation-menu">
        <code>
          NavigationMenu
        </code>
      </a>
      
       with <code>
        orientation="horizontal"
      </code>
    </td>
  </tr>
</tbody>
</table>

Here are the Nuxt UI Pro components that have been renamed or removed:

<table>
<thead>
  <tr>
    <th>
      v1
    </th>
    
    <th>
      v3
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        BlogList
      </code>
    </td>
    
    <td>
      <a href="/docs/components/blog-posts">
        <code>
          BlogPosts
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ColorModeToggle
      </code>
    </td>
    
    <td>
      <a href="/docs/components/color-mode-switch">
        <code>
          ColorModeSwitch
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardCard
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/page-card">
        <code>
          PageCard
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardLayout
      </code>
    </td>
    
    <td>
      <a href="/docs/components/dashboard-group">
        <code>
          DashboardGroup
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardModal
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/modal">
        <code>
          Modal
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardNavbarToggle
      </code>
    </td>
    
    <td>
      <a href="/docs/components/dashboard-sidebar-toggle">
        <code>
          DashboardSidebarToggle
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardPage
      </code>
    </td>
    
    <td>
      Removed
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardPanelContent
      </code>
    </td>
    
    <td>
      Removed (use <code>
        #body
      </code>
      
       slot instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardPanelHandle
      </code>
    </td>
    
    <td>
      <a href="/docs/components/dashboard-resize-handle">
        <code>
          DashboardResizeHandle
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardSection
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/page-card">
        <code>
          PageCard
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardSidebarLinks
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/navigation-menu">
        <code>
          NavigationMenu
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DashboardSlideover
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/slideover">
        <code>
          Slideover
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        FooterLinks
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/navigation-menu">
        <code>
          NavigationMenu
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        HeaderLinks
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/navigation-menu">
        <code>
          NavigationMenu
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingCard
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/page-card">
        <code>
          PageCard
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingCTA
      </code>
    </td>
    
    <td>
      <a href="/docs/components/page-cta">
        <code>
          PageCTA
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingFAQ
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/accordion">
        <code>
          Accordion
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingGrid
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/page-grid">
        <code>
          PageGrid
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingHero
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/page-hero">
        <code>
          PageHero
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingLogos
      </code>
    </td>
    
    <td>
      <a href="/docs/components/page-logos">
        <code>
          PageLogos
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingSection
      </code>
    </td>
    
    <td>
      <a href="/docs/components/page-section">
        <code>
          PageSection
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        LandingTestimonial
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/page-card#as-a-testimonial">
        <code>
          PageCard
        </code>
      </a>
      
       instead)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        NavigationAccordion
      </code>
    </td>
    
    <td>
      <a href="/docs/components/content-navigation">
        <code>
          ContentNavigation
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        NavigationLinks
      </code>
    </td>
    
    <td>
      <a href="/docs/components/content-navigation">
        <code>
          ContentNavigation
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        NavigationTree
      </code>
    </td>
    
    <td>
      <a href="/docs/components/content-navigation">
        <code>
          ContentNavigation
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PageError
      </code>
    </td>
    
    <td>
      <a href="/docs/components/error">
        <code>
          Error
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PricingCard
      </code>
    </td>
    
    <td>
      <a href="/docs/components/pricing-plan">
        <code>
          PricingPlan
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PricingGrid
      </code>
    </td>
    
    <td>
      <a href="/docs/components/pricing-plans">
        <code>
          PricingPlans
        </code>
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PricingSwitch
      </code>
    </td>
    
    <td>
      Removed (use <a href="/docs/components/switch">
        <code>
          Switch
        </code>
      </a>
      
       or <a href="/docs/components/tabs">
        <code>
          Tabs
        </code>
      </a>
      
       instead)
    </td>
  </tr>
</tbody>
</table>

### 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
<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 `click` field in different components has been removed in favor of the native Vue `onClick` event:

```diff
<script setup lang="ts">
const items = [{
  label: 'Edit',
-  click: () => {
+  onClick: () => {
    console.log('Edit')
  }
}]
</script>
```

> [!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 of the [App](/docs/components/app) component:

```diff [app.vue]
<template>
+  <UApp>
+    <NuxtPage />
+  </UApp>
-  <UModals />
-  <USlideovers />
-  <UNotifications />
</template>
```

- The `v-model:open` directive and `default-open` prop are now used to control visibility:

```diff
<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 `#content` slot (you don't need to use a `v-model:open` directive with this method):

```diff
<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`, `#body` and `#footer` slots have been added inside the `#content` slot like:

```diff
<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`.

- The `prevent-close` prop has been removed in favor of the `dismissible` prop:

```diff
<template>
- <UModal prevent-close />
+ <UModal :dismissible="false" />
</template>
```

> [!NOTE]
> 
> This change affects the following components: `Modal`, `Slideover`.

- The `Pagination` component `v-model` directive has been renamed to `v-model:page`:

```diff
<template>
- <UPagination v-model="page" />
+ <UPagination v-model:page="page" />
</template>
```

- The `change` event now emits the native `change` event, not the new value, which is now emitted in the `update:modelValue` event:

```diff
<template>
- <USelectMenu v-model="country" :items="countries" @change="console.log(newVal)" />
+ <USelectMenu v-model="country" :items="countries" @update:modelValue="console.log(newVal)" />
</template>
```

> [!NOTE]
> 
> This change affects the following components: `Select`, `SelectMenu`, `RadioGroup`.

- The `SelectMenu` component `searchable` prop has been renamed to `search-input` and now defaults to `true`. To preserve v2 behavior (no search input):

```diff
<template>
- <USelectMenu :items="items" />
+ <USelectMenu :search-input="false" :items="items" />
</template>
```

- The `Accordion` component has been redesigned. The `multiple` prop has been replaced by the `type` prop (defaults to `single`):

```diff
<template>
- <UAccordion multiple :items="items" />
+ <UAccordion type="multiple" :items="items" />
</template>
```

- The `Accordion` component `default-open` prop and `defaultOpen` **item** property have been removed. State is now controlled using `default-value` (uncontrolled) or `v-model` (controlled):

```diff
<template>
- <UAccordion default-open multiple :items="items" />
+ <UAccordion
+   type="multiple"
+   :default-value="['0', '1']"
+   :items="items"
+ />
</template>
```

- The `Accordion` component `#item` slot has been removed in favor of `#content` and `#body`:

```diff
<template>
- <template #item="{ item }">
-   {{ item.content }}
- </template>

+ <template #content="{ item }">
+   {{ item.content }}
+ </template>
</template>
```

> [!NOTE]
> 
> The default slot now only customizes the trigger, with additional slots for finer control (`#leading`, `#trailing`, `#body`).

- The `Accordion` component `unmount` prop has been renamed to `unmount-on-hide` and now defaults to `true`. To preserve v2 behavior (keep content mounted), use `:unmount-on-hide="false"`:

```diff
<template>
- <UAccordion :items="items" />
+ <UAccordion :unmount-on-hide="false" :items="items" />
</template>
```

- The `Table` component now uses [TanStack Table](https://tanstack.com/table/latest) under the hood. The `rows` prop has been renamed to `data`:

```diff
<template>
- <UTable :rows="rows" />
+ <UTable :data="data" />
</template>
```

- The `Table` component columns definition is now explicit and semantic:

```diff
<script setup lang="ts">
const columns = [{
-  label: 'Status',
-  key: 'status'
+  header: 'Status',
+  accessorKey: 'status'
}]
</script>
```

- The `Table` component row cell slot names have been changed from `<column-accessorKey>-data` to `<column-accessorKey>-cell`:

```diff
<template>
- <template #column-data="{ row }">
+ <template #column-cell="{ row }">
</template>
```

- The `Tabs` component `#item` slot has been removed in favor of `#content`:

```diff
<template>
- <template #item="{ item }">
+ <template #content="{ item }">
</template>
```

- The `Tabs` component `default-index` prop has been removed in favor of `default-value`:

```diff
<template>
- <UTabs :default-index="0" :items="tabs" />
+ <UTabs :default-value="0" :items="tabs" />
</template>
```

- The `Tabs` component `unmount` prop has been renamed to `unmount-on-hide` and now defaults to `true`. To preserve v2 behavior where content stayed mounted:

```diff
<template>
- <UTabs :items="tabs" />
+ <UTabs :unmount-on-hide="false" :items="tabs" />
</template>
```

- The `Alert` component `close-button` prop has been replaced by the `close` prop:

```diff
<template>
- <UAlert :close-button="{ icon: 'i-lucide-x', variant: 'link' }" />
+ <UAlert :close="{ icon: 'i-lucide-x', variant: 'link' }" />
</template>
```

- The `Alert` component `close` event has been replaced by the `update:open` event:

```diff
<template>
- <UAlert @close="isOpen = false" />
+ <UAlert @update:open="isOpen = false" />
</template>
```

- The `Alert` component `#icon` and `#avatar` slots have been replaced by a single `#leading` slot:

```diff
<template>
- <UAlert>
-   <template #icon>
-     <UIcon name="i-lucide-terminal" />
-   </template>
- </UAlert>

+ <UAlert>
+   <template #leading>
+     <UIcon name="i-lucide-terminal" />
+   </template>
+ </UAlert>
</template>
```

- The `Form` component now always validates on submit. The `validate-on` prop only controls which input events trigger validation. Pass an empty array to validate only on submit:

```diff
<template>
- <UForm :validate-on="['submit']" />
+ <UForm :validate-on="[]" />
</template>
```

- Form components now use `inline-flex` instead of `block` layout, which means they no longer expand to full width by default. Add `w-full` manually with the `class` prop or configure it globally in your `app.config.ts`:

```ts [app/app.config.ts]
export default defineAppConfig({
  ui: {
    input: { slots: { root: 'w-full' } },
    inputMenu: { slots: { root: 'w-full' } },
    textarea: { slots: { root: 'w-full' } },
    select: { slots: { base: 'w-full' } },
    selectMenu: { slots: { base: 'w-full' } }
  }
})
```

> [!NOTE]
> 
> This change affects the following components: `Input`, `InputMenu`, `Textarea`, `Select`, `SelectMenu`.

- The `popper` prop has been replaced by `content` for positioning:

```diff
<template>
- <UTooltip :popper="{ placement: 'top' }" />
+ <UTooltip :content="{ side: 'top' }" />

- <USelectMenu :popper="{ placement: 'bottom-start' }" />
+ <USelectMenu :content="{ side: 'bottom', align: 'start' }" />
</template>
```

> [!NOTE]
> 
> This change affects the following components: `Tooltip`, `Popover`, `DropdownMenu`, `ContextMenu`, `SelectMenu`, `InputMenu`.

- The `Tooltip` component `shortcuts` prop has been renamed to `kbds` and `prevent` to `disabled`:

```diff
<template>
- <UTooltip text="Open" :shortcuts="['⌘', 'O']" />
+ <UTooltip text="Open" :kbds="['meta', 'O']" />
</template>
```

- The `Popover` component `#panel` slot has been renamed to `#content`:

```diff
<template>
  <UPopover>
    <UButton label="Open" />

-   <template #panel>
+   <template #content>
      <div class="p-4">Content</div>
    </template>
  </UPopover>
</template>
```

- The `ContextMenu` component has been completely redesigned. It now uses items and has a proper trigger/content structure:

```diff
<template>
- <UContextMenu v-model="isOpen" :virtual-element="virtualElement" />
+ <UContextMenu :items="items">
+   <div>Right-click me</div>
+ </UContextMenu>
</template>
```

- The `Progress` component `value` prop has been replaced by `model-value` and `indicator` by `status`:

```diff
<template>
- <UProgress :value="50" indicator />
+ <UProgress :model-value="50" status />
</template>
```

- The `Carousel` component `indicators` prop has been renamed to `dots`:

```diff
<template>
- <UCarousel :items="items" indicators />
+ <UCarousel :items="items" dots />
</template>
```

> [!NOTE]
> 
> The `Carousel` component now uses [Embla Carousel](https://www.embla-carousel.com/) under the hood.

- The `help` prop/property has been renamed to `description`:

```diff
<template>
- <UCheckbox label="Remember me" help="Save my login details" />
+ <UCheckbox label="Remember me" description="Save my login details" />
</template>

<script setup lang="ts">
const items = [{
  label: 'Option 1',
- help: 'Description for option 1'
+ description: 'Description for option 1'
}]
</script>
```

> [!NOTE]
> 
> This change affects the following components: `Checkbox`, `RadioGroup`.

- The `Breadcrumb` component `divider` prop has been renamed to `separator-icon` and `#divider` slot to `#separator`:

```diff
<template>
- <UBreadcrumb :links="links" divider="i-lucide-arrow-right" />
+ <UBreadcrumb :items="items" separator-icon="i-lucide-arrow-right" />
</template>
```

- The `Avatar` component chip props (`chip-color`, `chip-position`, `chip-text`) have been consolidated into a single `chip` prop:

```diff
<template>
- <UAvatar src="..." chip-color="green" chip-position="top-right" chip-text="" />
+ <UAvatar src="..." :chip="{ color: 'success', position: 'top-right' }" />
</template>
```

- The `Button` component `padded` and `truncate` props have been removed. Use `square` instead of `:padded="false"`:

```diff
<template>
- <UButton :padded="false" />
+ <UButton square />
</template>
```

- The `Chip` component `show` prop is now a model (`v-model:show`):

```diff
<template>
- <UChip :show="isVisible" />
+ <UChip v-model:show="isVisible" />
</template>
```

- The `CommandPalette` component `groups` prop structure has changed. Each group now has an `items` array and uses `onSelect` instead of `click`:

```diff
<script setup lang="ts">
const groups = [{
  id: 'actions',
  label: 'Actions',
- commands: [{ id: 'new', label: 'New file' }]
+ items: [{ id: 'new', label: 'New file' }]
}]
</script>
```

### Changed composables

- The `useToast()` composable `timeout` prop has been renamed to `duration`:

```diff
<script setup lang="ts">
const toast = useToast()

- toast.add({ title: 'Invitation sent', timeout: 0 })
+ toast.add({ title: 'Invitation sent', duration: 0 })
</script>
```

- 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 explicitly emit a `close` event with the desired value

```diff
<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:

```diff
<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 `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
<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 instance = modal.open(ModalExampleComponent, {
+     count: count.value
+   })
+
+   const result = await instance.result
+
+   if (result) {
+     toast.add({ title: 'Success!' })
+   }
+ }
</script>
```

### Changed form validation

- The error object property for targeting form fields has been renamed from `path` to `name`:

```diff
<script setup lang="ts">
function validate(state: any): FormError[] {
  const errors = []
  if (!state.email) {
    errors.push({
-     path: 'email',
+     name: 'email',
      message: 'Required'
    })
  }
  if (!state.password) {
    errors.push({
-     path: 'password',
+     name: 'password',
      message: 'Required'
    })
  }
  return errors
}
</script>
```

---

> [!WARNING]
> 
> This page is a work in progress, we'll improve it regularly.


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
