Core

Styles

One Tailwind build for the app, and a separate theme for the admin.

An app that uses Pluto has exactly one Tailwind build. The CSS entry of the app owns it. No Pluto layer registers a Tailwind entry of its own.

Why

If two Tailwind builds are in one page, they declare the same cascade layers. A utility from the later stylesheet overrides the same utility of the earlier one. The responsive layout of the admin then breaks.

The CSS entry

Start your CSS entry with these three imports. Add your own theme after them.

app/assets/css/main.css
@import 'tailwindcss';
@import '@nuxt/ui';
@import '@plutocms/css';

@theme {
  /* your site's tokens */
}

Register the file in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  css: ['~/assets/css/main.css']
})

@plutocms/css brings in the CSS of every Pluto layer, including the admin theme. Nuxt UI already scans the source of every layer.

@plutocms/css is not an npm package, and you do not install it. Core makes it: it is a Nuxt alias of a file that core writes for your app. So it works with every package manager.

The pluto-css module

The module pluto-css is part of core. It does these things:

  1. It finds the pluto-layer.css file of every layer.
  2. It writes #build/pluto.css, which imports each layer file. The base layer comes first, so a higher layer wins. @plutocms/css is an alias of this file, and @plutocms/css/site is an alias of the site theme.
  3. It reads your CSS entries. If none imports tailwindcss, it writes a fallback entry and adds it. This keeps a layer repository that runs on its own styled. An app with its own entry gets no fallback file.

The module has one option, plutoCss.check. The default is true. Set it to false to turn the warnings off. The module prints nothing during nuxt prepare.

The build prints a pluto-css warning, once for each problem, when:

  • A layer registers its own Tailwind build.
  • More than one Tailwind build exists.
  • No entry imports @plutocms/css.
  • No entry imports @nuxt/ui.

The module does not add a second build when your app forgets the import.

Rules for a layer

A layer can ship app/assets/css/pluto-layer.css.

  • Never import tailwindcss or @nuxt/ui in it.
  • Use only @theme, @custom-variant, @utility, unlayered rules, and imports of plain CSS files next to it.
  • Add no @source for the app/ directory of the layer.
  • Register no css entry in the nuxt.config of the layer.

Check the build

In the browser console, count the Tailwind builds:

[...document.querySelectorAll('style[data-vite-dev-id]')].filter(s => s.textContent.includes('tailwindcss v4')).length

The result must be 1, on the admin and on the public site. The build log must have no pluto-css warning.

Editor setup

The Tailwind CSS IntelliSense extension for VS Code makes a project of each CSS file that it finds, and the generated files in .nuxt/ are CSS files too. If a generated file wins over your CSS entry, the editor does not know your @theme: your own colors show no color swatch, and your own classes show no hover.

Tell the extension to ignore the generated files. Add this to .vscode/settings.json:

.vscode/settings.json
{
  "files.associations": {
    "*.css": "tailwindcss"
  },
  "tailwindCSS.files.exclude": [
    "**/.git/**",
    "**/node_modules/**",
    "**/.hg/**",
    "**/.svn/**",
    "**/.nuxt/**",
    "**/.output/**"
  ]
}

The first four patterns are the defaults of the extension. The setting replaces them, so keep them in the list. Then reload the editor window.

The semantic colors of Nuxt UI, such as text-muted or bg-default, show no swatch. Their values are CSS variables that change with the color mode and with app.config, so the extension cannot show one color. Hover shows their CSS.

The Pluto UI tokens

Core has the official palette and fonts of Pluto. Every app gets them as Tailwind tokens, so you can use them in your site:

TokenUse
tholin-50 to tholin-950The red-orange of Pluto's surface. The action color.
haze-50 to haze-950The blue of Pluto's atmosphere.
heart-50 to heart-950The tan and cream of the planet's heart.
kuiper-50 to kuiper-950A cool, deep neutral for text, borders, and dark surfaces.
font-plutoPublic Sans.
font-pluto-displayFredoka, the rounded face of the wordmark.
<span class="bg-tholin-600 font-pluto-display text-white">Launch</span>

The tokens add new names only. They do not change the colors or the fonts of your site. To use the Pluto colors for Nuxt UI, set them in app.config: ui: { colors: { primary: 'tholin', neutral: 'kuiper' } }.

The Pluto site theme

To give your site the look of these docs, import the site theme after the core CSS:

app/assets/css/main.css
@import 'tailwindcss';
@import '@nuxt/ui';
@import '@plutocms/css';
@import '@plutocms/css/site';

And set the Pluto colors in app.config:

app/app.config.ts
export default defineAppConfig({
  ui: {
    colors: { primary: 'tholin', secondary: 'haze', neutral: 'kuiper' },
  },
})

The site theme gives:

  • Public Sans for text, and Fredoka for the page titles (h1) and the font-display utility.
  • The shades of your colors that read well: primary 600 in light mode, primary 400 in dark mode.
  • A dark mode of deep space (neutral 950).
  • The heart color for selected text.
  • pluto-sky: a soft sky for the top of a page.
  • pluto-site-header: put it on a sticky header, and the header moves below the admin bar while a signed-in editor sees the bar.

The theme does not choose your colors, so it works with your own palette too. It does not change the admin. The official templates use it.

The admin theme

The admin has its own look, made from the Pluto UI tokens. Your app.config colors and @theme tokens do not change it. To give the admin your brand, see White label.

RoleColor
primarytholin
secondaryhaze
successgreen
infohaze
warningamber
errorrose
neutralkuiper

Errors use rose, not red, so an error does not look like the red-orange primary. The admin text font is Public Sans. Page titles use Fredoka. The admin radius is 0.25rem.

The theme applies under two selectors:

  • :root[data-pluto-admin]. The composable usePlutoAdminTheme() sets data-pluto-admin on <html> while an admin layout is mounted. Overlays such as modals and menus also get the theme.
  • [data-pluto-admin-scope]. It is for admin chrome that shows on the public site, such as the navbar of a signed-in admin.

Every value is a literal OKLCH color, so an app cannot redefine it.

Surfaces

In dark mode, the surfaces go from the darkest at the top to the lightest in the content: navbar, sidebar, canvas, cards. The navbar is near black in both modes.

The admin bar height

The token --pluto-admin-bar-height is 2.5rem on :root. A public site can use it to offset its own header under the admin bar. PlutoRoot sets data-pluto-admin-bar on <html> while the bar shows. See Admin shell.

Rules for admin code

  • Use Nuxt UI semantic classes such as bg-default, text-muted, and border-default. Do not use raw Tailwind colors such as bg-zinc-100 or bg-tholin-600. Raw colors do not follow the admin theme.
  • Call usePlutoAdminTheme() in every new admin layout.
  • Put data-pluto-admin-scope on admin chrome that renders outside an admin layout.
  • Never put the admin radius in @theme. It would reach the public site.
Component themes in app.config, such as ui.button.slots.base, still merge across layers and reach the admin. Only colors, tokens, surfaces, and the font are isolated.