Nuxt

Add @nibcss/nuxt to modules and everything is wired: the Vite plugin, the Vue plugin, SSR, hot config reload and dark mode before the first paint.

3 min readv1

1. Create a project#

Terminal
npx nuxi@latest init my-app
cd my-app
npm install
npm run dev          # http://localhost:3000

2. Install#

Terminal
npm i @nibcss/vue @nibcss/runtime
npm i -D @nibcss/nuxt

3. Add the module#

TypeScript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@nibcss/nuxt"],
});

That is the whole setup. There is nothing to import and no provider to write — the stylesheet is added to nuxt.options.css for you. The module wires four pieces, all reading the same nibcss.config.ts:

Piece

What it does

the Vite plugin

scans your components, writes the stylesheet, publishes @nibcss/config, compiles static attributes into classes

a Vue plugin

one runtime for the page, v-nib, and the runtime for useNib / useNibRuntime — on the server too

a server plugin (dev)

re-reads the config per render, so the server-rendered half never lags behind an edit, and sets the dark-mode class before the first paint

two client plugins (dev)

apply a config change to the open page, and mount the diagnostics overlay

Start the app once and nibcss.config.ts is created beside nuxt.config.ts — not inside app/.

4. Write components#

HTML
<!-- app/app.vue -->
<script setup lang="ts">
import { ref } from "vue";
import { useNib, useNibRuntime } from "@nibcss/vue";

const rt = useNibRuntime();
const pad = ref(16);
const box = useNib(() => `p=${pad.value}px; bd=1px solid $color.border; bdrd=$radius.md`);
</script>

<template>
  <main nib="mxw=720px; mi=auto; p=$space.xl $space.md; d=flex; flxd=column; g=$space.lg">
    <header nib="d=flex; ali=center; jsc=space-between; g=$space.md">
      <h1 nib="m=0; fts=28px; ftw=800; lts=-.02em">Dashboard</h1>
      <button nib="btn" @click="rt.toggleDarkMode()">
        {{ rt.darkMode.isDark() ? "Light" : "Dark" }}
      </button>
    </header>

    <section nib="d=flex; flxd=column; g=$space.md; md:flxd=row">
      <article nib="card-hover; flg=1"><h2 nib="m=0; fts=18px">Revenue</h2></article>
      <article nib="card-hover; flg=1"><h2 nib="m=0; fts=18px">Users</h2></article>
    </section>

    <input type="range" min="4" max="48" v-model.number="pad" nib="w=100%; acc=$color.brand" />
    <div :class="box">padding: {{ pad }}px</div>
  </main>
</template>

5. Options#

They go under a nib key, and only when you need them:

Option

Default

What it does

runtime

true

install the Vue plugin automatically

devtools

true

mount the overlay in dev (needs debug: true)

config

beside nuxt.config.ts

path to the config file

include / exclude

project sources

extra globs to scan, or skip

override

—

config fields that win over the file

scaffold

true

false stops the module creating the config file

TypeScript
export default defineNuxtConfig({
  modules: ["@nibcss/nuxt"],
  nib: {
    include: ["content/**/*.md"],
    override: { debug: process.env.CI !== "true" },
  },
});

6. Editing the config while the server runs#

Save nibcss.config.ts and the open page updates by itself — no manual refresh, no reload, no restart. prefix, darkMode, debug, pseudos, preflight, strict, tokens, recipes and shorthands are all picked up in about a second, and rules the change removes go away too.

It works by being told rather than by reloading: the build pushes the new config and the new stylesheet over the dev channel, and the page swaps the stylesheet and re-resolves its elements. The one exception is language — the overlay's labels are baked in when its module loads, so those follow on the next reload.

7. Dark mode arrives dark#

With darkMode: "class" the module inlines a few bytes at the top of <head> that set html.dark from the stored choice, or the OS preference when there is none, before the first paint. Without it the runtime adds the class after hydration, which in a dark app is a white flash on every load. Nothing to configure.

8. What the module generates#

Path

What it is

node_modules/.nib/styles.css

the stylesheet, added to nuxt.options.css

node_modules/.nib/config.mjs, config.json

the resolved config, as a module and as data

node_modules/@nibcss/config

a real package re-exporting it, so the import type-checks

.nuxt/nib-vue.mjs, nib-vue-server.mjs, nib-hot.client.mjs, nib-devtools.client.mjs

the generated plugins (virtual in dev)

Common pitfalls#

  • Putting nibcss.config.ts inside app/ — the module looks beside nuxt.config.ts.
  • Adding the Vite plugin by hand as well as the module: two scans, two stylesheets.
  • Setting runtime: false and then calling useNibRuntime() — nothing provides it.

Next steps#