Nuxt Layers for Theming

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.

Michael Thiessen
Nuxt 3

The Mastering Nuxt FullStack Unleashed Course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view course

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.

Core Concept: Encapsulating Theme Logic in Layers

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.

Directory Structure and Layer Hierarchy

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.

Configuration and Merging Behaviors

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.

Distributing and Managing Layers

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.

Conclusion: Clean, Reusable, and Powerful Theming with Nuxt Layers

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.

Michael Thiessen
Michael is a passionate full time Vue.js and Nuxt.js educator. His weekly newsletter is sent to over 11,000 Vue developers, and he has written over a hundred articles for his blog and VueSchool.

Follow MasteringNuxt on