Nuxt layers make theming effortless by encapsulating styles, components, and configurations into modular layers that can be swapped with a single configuration change. This approach keeps your application clean, reusable, and flexible—allowing you to manage multiple themes seamlessly across projects.
Get notified when we release new tutorials, lessons, and other expert Nuxt content.
When working with Nuxt layers, you can streamline your theming workflow and keep your application’s look and feel organized. By treating each theme as its own layer, you gain the freedom to swap entire themes with minimal changes.
So if you want to switch from a base design to a dark variant, you just change a single configuration line.
And you can reuse the same design tokens across multiple projects, turning theming into a plug-and-play operation.
But it’s not just about making quick swaps.
By separating your core logic from your theme logic, you get a more modular and maintainable codebase. You keep styling, components, and configurations neatly organized by layer, making updates easy. And you can tweak and refine your themes much faster — whether you need a totally new theme or a quick brand refresh.
Let's get to it.
Nuxt layers let you isolate theme-specific code from the rest of your app. You put this code into dedicated directories — like layers/dark
— and treat each directory as a self-contained "theme package."
Now, let’s say you have a dark
layer. You include it in nuxt.config
by adding it to extends
. If you want to switch themes, you just list a different layer.
By doing this, you quickly apply a whole new set of colors, components, and styles with one change:
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['./layers/dark', './layers/base']
});
And if you decide to distribute your theme as an npm package or host it on a remote Git repo, you can bring it into other projects easily.
You can also combine multiple layers to form a composite theme or disable one altogether by removing it from extends
.
Each theme layer is a mini Nuxt application.
You can have components/
, pages/
, layouts/
, and other directories.
This is how the theming piece works — if you place a component inside a theme layer, it overrides that component from the base layer. For example, in the dark
layer, you might override a button component by creating layers/dark/components/BaseButton.vue
:
<!-- layers/dark/components/BaseButton.vue -->
<template>
<button
:style="{
color: appConfig.primaryColorDark,
backgroundColor: appConfig.backgroundColorDark
}">
Dark Button
</button>
</template>
<script setup lang="ts">
const appConfig = useAppConfig();
</script>
Here’s the thing: layer order matters.
The layers that appear first in the extends
array have higher priority. If you want dark
to override base
, you must list dark
before base
.
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['./layers/dark', './layers/base'] // dark takes priority
});
Yeah, it's a little counterintuitive, since most of the time we think of the last thing listed as taking precedence.
But in this case, the first layer listed takes precedence.
So if the dark
layer appears first, it overrides components and configurations from the base
layer. That way, you can keep your base layer for common design tokens and patterns, and then introduce specialized overrides from other layers above it.
This keeps your configuration minimal and lets layers handle their own styling details.
Each layer must have its own nuxt.config.ts
file — this is what tells Nuxt that it is a layer.
You can set theme-specific CSS, plugins, or modules there. And when you switch layers, Nuxt applies that layer’s configurations automatically. This makes theme toggling as simple as changing a single line in your root config.
If you define design tokens in the base
layer — like global color palettes and fonts — then the dark
layer only needs to override a few keys. You don’t repeat everything. You just focus on what changes, leaving everything else untouched.
Nuxt merges layers behind the scenes.
It discovers components, merges configs, and applies overrides without you needing manual imports or conditional logic. You simply adjust the extends
array and watch your app transform on the fly.
For instance, your dark
layer’s app.config.ts
might look like this:
// layers/dark/app.config.ts
export default defineAppConfig({
primaryColor: '#FAFAFA',
backgroundColor: '#444'
});
If the base
layer defines these tokens differently, the dark
layer overrides them when it has priority. Then, if our component uses these tokens, it will use the dark
layer's values instead of the base
layer's.
You can store layers locally, or you can reference them remotely — like from a Git repo or an npm package. This makes it easy to share a theme across multiple projects:
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['github:my-themes/dark-mode-layer#v2']
});
You might publish a theme as @my-themes/dark
and reuse it in several Nuxt applications. When you update that package, all your projects get the improvements.
Layers can also include a server/
directory for backend logic, so you adapt both the frontend and backend depending on which theme layer is currently active.
Nuxt layers let you keep your styling and logic separate.
By adjusting the extends
array, you can instantly change your app’s entire look. You keep your root configuration small and let each layer handle its own details.
So go ahead and experiment.
Pack up your themes, share them, and try out different variants for special promotions or client projects. With layers, theming becomes simpler and more flexible.
Learn more about Nuxt layers in the Nuxt documentation.