@untheme/nuxt
| Install | |
|---|---|
npm install @untheme/nuxt |
|
| Latest Version: | 0.6.0 |
| Vue: | 3 |
| Last Updated: | Oct 12, 2026 |
| Links: | GitHub · npm |
@untheme/nuxt
The Nuxt module for untheme. The module builds a theme from DTCG JSON at build time and adds a base stylesheet, a runtime theme service, a reactive style tag for the applied layer, and cookie storage for the selection and the layer.
Install
pnpm add @untheme/nuxt
Setup
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@untheme/nuxt"],
untheme: { preset: "@untheme/aurora" },
});
The module gets the theme in one of three ways.
-
A preset. Name a package that exports a kit build with
preset. The module takes the base theme, the boot selection, the manifest, and the layers from the package, and serves the layers as a theme catalog. Aurora is a preset. So is any package whoseuntheme buildoutput it exports:./config,./manifest,./layers, and./layers/<id>.json. The module resolves the package from the project root, so it is a dependency of the app. -
Built here. When the options have no
themeand nopreset, the module readsuntheme.config.tsin the project root. The module builds the config in memory with@untheme/kit. Theconfigoption names a different file. Nuxt watches the config file and each JSON document that the build read. A change to one of these files restarts the dev server. The layers of the config are served the same way as the layers of a preset.// untheme.config.ts import { defineConfig } from "@untheme/kit"; export default defineConfig({ source: "./app.resolver.json", layers: "npm:/@untheme/aurora/src/themes", }); -
Built elsewhere. Pass the
themeandinputfrom a kit build. The module uses the values as you pass them, and serves no layers.import config from "@acme/theme/config"; export default defineNuxtConfig({ modules: ["@untheme/nuxt"], untheme: { ...config }, });
Options
defineUnthemeConfig from @untheme/nuxt/config types the options. Pass theme and input together.
| Option | Default | Description |
|---|---|---|
preset |
none | A package that exports a kit build, such as @untheme/aurora. Read when theme is absent. |
config |
untheme.config.ts |
The kit config to build, relative to the project root. Read when theme and preset are absent. |
theme |
none | A built base theme. Pass it with input. |
input |
none | The built boot selection, with one context for each modifier. Pass it with theme. |
When more than one Nuxt layer sets untheme, the module uses the value of the closest layer as a whole.
Generated files
At build time the module validates the theme and the boot selection with defineSchema. The module then writes these build templates.
-
#build/untheme/config.mjsholds thethemeandinputdata. The fileconfig.d.mtstypes the data with theContract. -
#build/untheme/index.mjsholds the token list, the modifier list, and theisTokenandisModifierguards. The fileindex.d.mtshas theTokenunion, theOverridestype, and theModtype.Moddescribes the contexts of each modifier. -
#build/untheme/manifest.mjsholdsmanifest. Each modifier and context has an id, a name, and a description. When the module builds the config, the names and descriptions come from the documents of the theme. A theme from thethemeandinputoptions gets titled ids. -
#build/untheme.cssholds the base cascade. The module links it into the CSS of the app. See CSS. -
#build/untheme/layers.mjsholdslayers, the id, name, and description of each layer of the build, andload, one lazy import for each layer by id. The filelayers.d.mtshas theLayerIdunion. For a preset the list and the layers are the package's own, re-exported. For a kit config the module writes the list and#build/untheme/layers/<id>.json. The list and the map are empty when the build has no layers.
The untheme/ modules are the same modules that untheme build writes.
The module also registers the runtime plugin and these auto-imports: useUntheme(), useUnthemeRenderer(), useUnthemeCatalog(), and accessUntheme(). It registers these type imports from the generated contract: AppUnthemeContract, AppUnthemeTheme, AppUnthemeThemeLayer, AppUnthemePatch, AppUnthemeInput, AppUnthemeConfig, and AppUntheme.
useUntheme()
useUntheme() returns the theme service. This is the Untheme service of @untheme/core for the token contract of your app, with the layers of the build. See that package for the full API: theme, get, resolve, set, swap, apply, create, update, delta, dirty, reset, and more.
<script setup>
const ut = useUntheme();
</script>
<template>
<button @click="ut.swap('color', 'dark')">Dark mode</button>
<select @change="ut.select($event.target.value)">
<option v-for="layer in ut.layers" :key="layer.id" :value="layer.id">
{{ layer.name }}
</option>
</select>
</template>
layers lists the layers of the build: an id, a name, and a description each. select(id) loads one by id and applies it. It resolves the layer, or undefined for an id outside the build. theme().id is the id of the applied layer, or of the base theme when none is applied. The key cookie restores the layer on the next request. See Cookies.
useUnthemeRenderer() returns the CSS renderer for the same service. Use it to get the custom property of a token with var("primary"), to read a live value, or to emit a static set.
accessUntheme() returns the state that the service uses. This is the reactive config container from useState, with the patch and the input, and the input and key cookie refs. Most components need only useUntheme().
useUnthemeCatalog()
The service has one base theme and one applied layer. The app loads a theme as a layer and calls apply with it. useUnthemeCatalog() returns the catalog over the layers of the build, which the plugin provides as $unthemeCatalog. list pages the entries of the layers module. get imports one layer on demand and checks it against the contract of the app. Each layer is its own chunk, so the client loads only the themes it applies, and a new build has new chunk URLs.
import { layers } from "#build/untheme/layers.mjs";
const untheme = useUntheme();
const catalog = useUnthemeCatalog();
layers; // [{ id: "nord", name: "Nord", description: "..." }, ...]
const page = await catalog.list({ limit: 50 }); // the same entries, paged and searchable
const layer = await catalog.get("nord");
if (layer) untheme.apply(layer);
untheme.theme().id; // "nord"
The catalog is empty when the build has no layers. The import works during server rendering, so a layer applied on the server renders on the first response.
See the Nuxt example for aurora served as a preset.
CSS
The module renders the base cascade to #build/untheme.css at build time and links it into the CSS of the app. The module renders the file with defineRenderer(...).sheet() over the base theme. The file has the base bindings under :root. Each modifier context follows as a [data-<modifier>="<context>"] block. The cascade sits in an @layer untheme block, so your own unlayered CSS wins over it. Editors index the file and complete var(--surface).
The runtime plugin sets the selected context of each modifier on <html> as a data-<modifier> attribute, such as data-color="dark". The attribute selects the context blocks of the stylesheet, so a swap renders no CSS. Your stylesheets can select on the attribute too.
[data-color="dark"] .card {
box-shadow: none;
}
The plugin injects the patch alone as one reactive <style> tag: the tokens that an applied layer or update rebinds, and the context overrides a layer carries, made with defineRenderer(...).patch(). The tag is unlayered, so it wins over the base cascade. It is empty when the patch is, so a page with no applied layer carries no runtime CSS. The tag renders again when the patch changes.
Cookies and SSR
The module saves the selection and the id of the patch to two cookies, untheme-input and untheme-key. swap writes the input cookie. apply, select, and update write the key cookie. A patch with no id clears it. On the server, the module reads both cookies before it renders. It checks the stored input with schema.check.input and uses it when it matches the contract. It loads the layer that the key names through the catalog and uses it when it matches. A cookie that fails, or a key that names no layer of the build, is cleared.
The state in useState holds the patch and the selection. The base theme is the build module and does not travel in the payload.
Hooks
The plugin calls four Nuxt hooks.
| Hook | Called when | Payload |
|---|---|---|
untheme:ready |
The plugin finishes the setup of the service | the Untheme service |
untheme:input |
The selection changes with swap |
InputChange: field, before, after |
untheme:patch |
The patch changes with apply or update |
PatchChange: field, before, after, diff |
untheme:change |
The selection or the patch changes | the same Change object, after the event of the field |
untheme:input, untheme:patch, and untheme:change forward the events of the service with the same change object. before and after are the stored values around the write. diff is the difference between the active theme before the write and after it. The plugin calls each hook after the write lands in the state, so a hook reads the new state through useUntheme(). Code that does not need a Nuxt hook can subscribe to the service directly with useUntheme().on(...), in a component or a plugin.
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook("untheme:patch", ({ diff }) => {
console.log("rebound", Object.keys(diff.tokens));
});
});
Related
@untheme/kitbuilds the theme from DTCG JSON.unthemere-exports the core service, the schema, the catalog, the config, and the CSS helpers.@untheme/coreis the theme service thatuseUntheme()returns.@untheme/cssis the CSS renderer that the plugin uses.
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.6.0 | ^3.5.35 | ||
| 0.5.0 | ^3.5.35 | ||
| 0.4.0 | ^3.5.35 | ||
| 0.3.0 | ^3.5.35 | ||
| 0.2.3 | ^3.5.35 | ||
| 0.2.2 | ^3.5.35 | ||
| 0.2.1 | ^3.5.35 | ||
| 0.2.0 | ^3.5.35 | ||
| 0.1.0 | ^3.5.35 | ||
| 0.0.42 | ^3.0 | MIT | |
| 0.0.41 | ^3.0 | MIT | |
| 0.0.40 | ^3.0 | MIT | |
| 0.0.39 | ^3.0 | MIT | |
| 0.0.38 | ^3.0 | MIT | |
| 0.0.37 | ^3.0 | MIT | |
| 0.0.36 | ^3.0 | MIT | |
| 0.0.35 | ^3.0 | MIT | |
| 0.0.34 | ^3.0 | MIT | |
| 0.0.33 | ^3.0 | MIT | |
| 0.0.32 | ^3.0 | MIT | |
| 0.0.31 | ^3.0 | MIT | |
| 0.0.30 | ^3.0 | MIT | |
| 0.0.29 | ^3.0 | MIT | |
| 0.0.28 | ^3.0 | MIT | |
| 0.0.27 | ^3.0 | MIT | |
| 0.0.26 | ^3.0 | MIT | |
| 0.0.25 | ^3.0 | MIT | |
| 0.0.24 | ^3.0 | MIT | |
| 0.0.23 | ^3.0 | MIT | |
| 0.0.22 | ^3.0 | MIT |
Showing the latest 30 of 40. See every release on npm