---
title: "Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode · vuejs.school"
description: "Nuxt UI 3 is rebuilt on Reka UI and Tailwind CSS v4 and now runs in a plain Vite app. Here is the setup for both, plus a form and a colour change."
canonical_url: "https://vuejs.school/community/blog/nuxt-ui-v3-first-look"
meta:
  "og:description": "Nuxt UI 3 is rebuilt on Reka UI and Tailwind CSS v4 and now runs in a plain Vite app. Here is the setup for both, plus a form and a colour change."
  "og:image": "https://vuejs.school/_og/d/a_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZQ%2Cdescription_~TnV4dCBVSSAzIGlzIHJlYnVpbHQgb24gUmVrYSBVSSBhbmQgVGFpbHdpbmQgQ1NTIHY0IGFuZCBub3cgcnVucyBpbiBhIHBsYWluIFZpdGUgYXBwLiBIZXJlIGlzIHRoZSBzZXR1cCBmb3IgYm90aCwgcGx1cyBhIGZvcm0gYW5kIGEgY29sb3VyIGNoYW5nZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXVpLXYzLWZpcnN0LWxvb2si%2Cs_fh30Ac02wafV-kcK.png"
  "og:image:alt": "Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZQ%2Cdescription_~TnV4dCBVSSAzIGlzIHJlYnVpbHQgb24gUmVrYSBVSSBhbmQgVGFpbHdpbmQgQ1NTIHY0IGFuZCBub3cgcnVucyBpbiBhIHBsYWluIFZpdGUgYXBwLiBIZXJlIGlzIHRoZSBzZXR1cCBmb3IgYm90aCwgcGx1cyBhIGZvcm0gYW5kIGEgY29sb3VyIGNoYW5nZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXVpLXYzLWZpcnN0LWxvb2si%2Cs_fh30Ac02wafV-kcK.png"
  "twitter:image:alt": "Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What shipped in Nuxt UI 3.0

The [announcement](https://nuxt.com/blog/nuxt-ui-v3) calls v3 a complete redesign, more than 1500 commits in the making. It ships 54 core components and 42 Prose components for rendered Markdown. Three decisions define the release:

- The accessible primitives underneath are now Reka UI.
- The styling is Tailwind CSS v4, with a theme you edit in `app.config.ts`.
- The whole library works in any Vue project, not only in Nuxt.

Nuxt UI Pro, the add-on with 50 more components for landing pages, documentation sites and dashboards, remains a separate, paid licence. The news in this release is that Pro now works in a plain Vue project as well, through `@nuxt/ui-pro/vite`.

## From Headless UI to Reka UI

Version 2 built its interactive components on Headless UI. Version 3 builds them on Reka UI 2, the unstyled Vue component library that was known as Radix Vue. Unstyled means Reka UI handles behaviour and leaves appearance to Nuxt UI.

The announcement lists what that buys: keyboard navigation, ARIA attributes (the roles, states and labels a screen reader announces) set automatically, focus management and screen reader support, for every menu, dialog, select, tabs and tooltip in the set. If you have ever written a dropdown by hand and then discovered it cannot be opened with the arrow keys, this is the part of the release to be glad about.

## Tailwind CSS v4 and Tailwind Variants

Nuxt UI 3 moves to Tailwind CSS v4, which you configure in CSS rather than in a JavaScript file. The announcement credits the switch with faster builds and smaller CSS. Tailwind's own [v4.0 release post](https://tailwindcss.com/blog/tailwindcss-v4) gives the figures: full builds up to 5 times faster than v3, and incremental builds over 100 times faster.

Component styles are written with Tailwind Variants, a small library that describes a component as one object. The object lists the component's parts, which it calls slots (the elements that receive classes, not Vue's `<slot>`), and its variants, such as `size`, `color` and `variant`. You will use that shape when you restyle a component in the last section.

## Colour aliases and semantic CSS variables

On top of Tailwind sits a design system with seven colour aliases: `primary`, `secondary`, `success`, `info`, `warning`, `error` and `neutral`. Each alias points at a Tailwind palette. Components with a `color` prop take an alias, as in `color="primary"` or `color="error"`, so pointing `primary` at another palette changes every primary button and badge at once.

For your own markup there are semantic CSS variables for text, backgrounds and borders. They are named for their job rather than their colour: `--ui-text-muted`, `--ui-bg-elevated`, `--ui-border` and others. Each has a light and a dark value. Use them with the Tailwind v4 syntax for a CSS variable, `text-(--ui-text-muted)` or `bg-(--ui-bg-elevated)`, and dark mode follows without a second `dark:` class.

## Install in a Nuxt 3 project

This section uses the Nuxt 3 layout, with `assets/` and `pages/` at the project root. A new project can skip these steps: `npx nuxi@latest init my-app -t ui` creates one with Nuxt UI set up. In an existing project, add the package:

```
npm install @nuxt/ui
```

If you use pnpm, either set `shamefully-hoist=true` in `.npmrc` or install `tailwindcss` in the project root yourself, because pnpm's strict `node_modules` layout otherwise hides it from the library.

Next, register the module and a stylesheet in the Nuxt config:

*nuxt.config.ts*

```
export default defineNuxtConfig({
  modules: ['@nuxt/ui'],
  css: ['~/assets/css/main.css'],
})
```

Create that stylesheet. It imports Tailwind, then the library:

*assets/css/main.css*

```
@import "tailwindcss" theme(static);
@import "@nuxt/ui";
```

The `theme(static)` part is required with Nuxt UI 3.0.0. Tailwind 4.0.8 stopped emitting theme variables that your own classes do not use, and the library needs the full set.

Finally, wrap the app in `UApp`. It provides the global config, and toasts, tooltips and programmatic overlays (such as a modal opened from code) need it:

*app.vue*

```
<template>
  <UApp>
    <NuxtPage />
  </UApp>
</template>
```

## Install in a plain Vite project

Without Nuxt, the library plugs in through a Vite plugin and a Vue plugin. Add the Vite plugin next to Vue's:

*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()],
})
```

Then import the stylesheet and register a router and the Vue plugin in your entry file:

*src/main.ts*

```
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')
```

`src/assets/main.css` holds the same two `@import` lines as the Nuxt version, and `App.vue` wraps `<RouterView />` in `<UApp>` instead of `<NuxtPage />`.

The router is part of the official setup because components that take a `to` prop, such as `ULink` and `UButton`, render their links through it. With pnpm and no `shamefully-hoist`, a Vite app needs `vue-router` and `@unhead/vue` installed in the project root, next to `tailwindcss`.

The Vite plugin also sets up auto-imports, so the library's components and composables (`UButton`, `useToast`) work without an import line, as they do in Nuxt. It writes their types to `auto-imports.d.ts` and `components.d.ts`: add both files to `.gitignore`, and to `include` in `tsconfig.app.json` so TypeScript sees them.

## Add a button and a validated form

Components use a `U` prefix. A button takes a colour alias, a variant (`solid`, `outline`, `soft`, `subtle`, `ghost` or `link`) and a size from `xs` to `xl`:

*pages/index.vue*

```
<template>
  <UButton color="primary" variant="solid" size="md">
    Save changes
  </UButton>
  <UButton color="neutral" variant="ghost" size="md">
    Cancel
  </UButton>
</template>
```

Forms are where a library earns its keep. `UForm` takes a reactive `state` object and a `schema` from a validation library. No validator is bundled, so install the one you use. This sign-up form uses Zod:

*pages/signup.vue*

```
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  email: z.string().email('Enter a valid email address'),
  password: z.string().min(8, 'Use at least 8 characters'),
})

type Schema = z.output<typeof schema>

const state = reactive<Partial<Schema>>({ email: undefined, password: undefined })
const toast = useToast()

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Account created', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>
    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>
    <UButton type="submit">Create account</UButton>
  </UForm>
</template>
```

What each part does:

- The `name` on each `UFormField` ties it to a key in the schema, and that key's error message appears under the field.
- A field is checked when the user leaves it, then again as they type. Submitting checks every field.
- `onSubmit` runs only when the whole form passes. `event.data` is already validated and typed as `Schema`, the type Zod infers from the schema, so there is no second check to write.
- `useToast()` and `reactive` are auto-imported in Nuxt. In the Vite setup, import `reactive` from `vue`.

## Change colours and component styles

The default `primary` is green and `neutral` is slate. In Nuxt, change them in `app.config.ts`, which is read at runtime, so no rebuild is needed:

*app.config.ts*

```
export default defineAppConfig({
  ui: {
    colors: {
      primary: 'blue',
      neutral: 'zinc',
    },
  },
})
```

In the Vite setup the same object goes into the plugin options in `vite.config.ts`: `ui({ ui: { colors: { primary: 'blue', neutral: 'zinc' } } })`.

The names are Tailwind palettes. To use your own palette, define all eleven shades from 50 to 950 in your stylesheet, for example `@theme static { --color-brand-500: #2563eb; }` plus the other ten, then set `primary: 'brand'`.

To restyle one component everywhere, add its theme under `ui` in the same file, in the Tailwind Variants shape: `ui.button.slots.base` takes classes for every button, and `ui.button.defaultVariants.size` sets the size a button gets when you pass none.

**Check it stuck**3 questions from the Component libraries quiz.

[**Practice now**](https://vuejs.school/learn/styling/component-libraries?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Tutorial · 7 min read**Component libraries in 2026: Nuxt UI, PrimeVue, Vuetify and when to use none**](https://vuejs.school/community/blog/component-libraries-in-2026) [Tutorial · 7 min read**Accessible components: focus, keyboard and ARIA**](https://vuejs.school/community/blog/accessible-components-focus-keyboard-and-aria)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-18","description":"Nuxt UI 3 is rebuilt on Reka UI and Tailwind CSS v4 and now runs in a plain Vite app. Here is the setup for both, plus a form and a colour change.","name":"Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode","url":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look","breadcrumb":{"@id":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/nuxt-ui-v3-first-look"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2025-03-18","description":"Nuxt UI 3 is rebuilt on Reka UI and Tailwind CSS v4 and now runs in a plain Vite app. Here is the setup for both, plus a form and a colour change.","headline":"Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZQ%2Cdescription_~TnV4dCBVSSAzIGlzIHJlYnVpbHQgb24gUmVrYSBVSSBhbmQgVGFpbHdpbmQgQ1NTIHY0IGFuZCBub3cgcnVucyBpbiBhIHBsYWluIFZpdGUgYXBwLiBIZXJlIGlzIHRoZSBzZXR1cCBmb3IgYm90aCwgcGx1cyBhIGZvcm0gYW5kIGEgY29sb3VyIGNoYW5nZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXVpLXYzLWZpcnN0LWxvb2si%2Cs_fh30Ac02wafV-kcK.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-ui-v3-first-look#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Nuxt UI v3: Reka UI, Tailwind v4 and a Vue-only mode","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZQ%2Cdescription_~TnV4dCBVSSAzIGlzIHJlYnVpbHQgb24gUmVrYSBVSSBhbmQgVGFpbHdpbmQgQ1NTIHY0IGFuZCBub3cgcnVucyBpbiBhIHBsYWluIFZpdGUgYXBwLiBIZXJlIGlzIHRoZSBzZXR1cCBmb3IgYm90aCwgcGx1cyBhIGZvcm0gYW5kIGEgY29sb3VyIGNoYW5nZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXVpLXYzLWZpcnN0LWxvb2si%2Cs_fh30Ac02wafV-kcK.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZSDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBVSSB2MzogUmVrYSBVSSwgVGFpbHdpbmQgdjQgYW5kIGEgVnVlLW9ubHkgbW9kZQ%2Cdescription_~TnV4dCBVSSAzIGlzIHJlYnVpbHQgb24gUmVrYSBVSSBhbmQgVGFpbHdpbmQgQ1NTIHY0IGFuZCBub3cgcnVucyBpbiBhIHBsYWluIFZpdGUgYXBwLiBIZXJlIGlzIHRoZSBzZXR1cCBmb3IgYm90aCwgcGx1cyBhIGZvcm0gYW5kIGEgY29sb3VyIGNoYW5nZS4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXVpLXYzLWZpcnN0LWxvb2si%2Cs_fh30Ac02wafV-kcK.png"}]}
```