The Theme component lets you override the theme of all child components without modifying each one individually. Pass an object to the ui prop where keys are component names and values are their slot class overrides:
The Theme component doesn't render any HTML element. It uses Vue's provide / inject under the hood, so overrides propagate through the entire component tree regardless of nesting depth. Theme components can be nested (innermost wins) and the ui prop on individual components always takes priority.
Thanks to Tailwind CSS v4.2, four new neutral color options are now available: taupe, mauve, mist and olive. Configure them through the ui.neutral option in your app.config.ts.
The Toaster now automatically prevents duplicate toasts and displays a pulse animation when a duplicate is triggered, providing a cleaner notification experience.
Font providers previously returned multiple formats (woff2, woff, truetype, etc.). The default behavior now only resolves woff2 fonts, which is supported by all modern browsers.
Your @font-face declarations will typically have fewer src entries, reducing CSS size. In most cases this is a transparent improvement.
To restore the previous behavior or add additional formats:
Font metadata caches are now isolated per provider and per provider options. After upgrading, your font metadata cache (node_modules/.cache/nuxt/fonts/) will be invalidated and fonts will be re-fetched on the next build. This is a one-time occurrence.
A new built-in npm provider can resolve fonts installed as npm packages. If no other provider matches a font family, @nuxt/fonts will now attempt to find it in your node_modules via CDN metadata.
export default defineNuxtConfig({
fonts: {
npm: {
// options for the npm provider (optional)
},
},
})
If your Nuxt project uses Vite's lightningcss mode for CSS processing (for example, if you're using rolldown-vite!), injected @font-face declarations are now minified with lightningcss instead of esbuild.
Prevent font flashes in development — The dev font proxy now returns Cache-Control: public, max-age=31536000, immutable headers, preventing font flashes during HMR on SSR frameworks.
Broader CSS file matching — Font family injection now matches additional CSS-like file patterns (Vue SFC &lang.css query strings and inline style IDs), aligning with fontless behavior.
Adobe provider race condition — Fixed a race condition in the Adobe (Typekit) provider where concurrent font resolution could clear the font family map mid-flight, causing Adobe fonts to silently fail. (fix in unifont 0.7.4)
Prioritize sliced woff2 over full ttf — When both formats are available, woff2 subsets are now correctly prioritized over full ttf files. (fix in unifont 0.7.2)
Bunny provider subset filtering — The Bunny font provider now correctly filters by subsets. (fix in unifont 0.7.0)
The biggest improvement in this release is how mocking works. Nuxt initialization has been moved from setupFiles to the beforeAll hook (#1516), which means vi.mock and mockNuxtImport calls now take effect before Nuxt starts. This fixes a long-standing issue where mocks for composables used in middleware or plugins wouldn't apply reliably (#750, #836, #1496).
On top of that, mockNuxtImport now passes the original implementation to the factory function (#1552), making partial mocking much more natural:
registerEndpoint now works correctly with query parameters in URLs (#1560), and endpoints registered in setup files are no longer lost when modules reset (#1549).
@nuxt/test-utils v4 contains a few breaking changes, almost all related to requiring at least vitest v4 as a peer dependency (if you are using vitest). It replaces vite-node with Vite's native Module Runner and simplifies pool configuration.
This will mean improvements for test performance and mocking, but does require some changes to your test code.
!TIP
Most of the changes below are straightforward. The biggest thing to watch out for is code that runs at the top level of a describe block — see below.
This is the change that might require most change in your tests. Because we've moved the nuxt environment setup into beforeAll, this means composables called at the top level of a describe block will fail with [nuxt] instance unavailable.
// Before (worked in vitest v3)
describe('my test', () => {
const router = useRouter() // ran lazily, after environment setup
// ...
})
// After (vitest v4)
describe('my test', () => {
let router: ReturnType<typeof useRouter>
beforeAll(() => {
router = useRouter() // runs after environment setup
})
// ...
})
This applies to useRouter(), useNuxtApp(), useRoute(), and any other Nuxt composable or auto-import.
!TIP
If you only need the value within individual tests, beforeEach or directly within the test works too.
If you use vi.mock with a factory function, accessing an export that the factory doesn't return will now throw an error instead of silently returning undefined.
// Before: accessing `bar` would silently return undefined
vi.mock('./module', () => ({ foo: 'mocked' }))
// After: accessing `bar` throws
// Fix: use importOriginal to include all exports
vi.mock('./module', async (importOriginal) => ({
...await importOriginal(),
foo: 'mocked',
}))
!NOTE
If you're mocking a virtual module (like #build/nuxt.config.mjs) where importOriginal can't resolve the real module, you might need to explicitly list all accessed exports in your mock factory.