Skip to content
NuvoUI

Setup

Astro

Configure NuvoUI once, load the global stylesheet in the shared layout, and style components in scoped SCSS blocks with apply().

Requires @nuvoui/core 2.1.0 or later.

Install

Astro compiles <style lang="scss"> blocks and .scss imports once sass is installed. @use "@nuvoui/core" resolves with no change to astro.config.mjs.

Terminal
npm install -D sass @nuvoui/core

Set up

  1. Write the configuration once

    A partial that forwards @nuvoui/core/mixins with your settings. The mixins entry has the same API as @nuvoui/core (apply(), every mixin, token(), with()) and outputs no CSS.

    src/styles/_nuvo.scss
    @forward "@nuvoui/core/mixins" with ($brand: #4361ee, $generate-utility-classes: true);
  2. Create the global stylesheet

    It emits the tokens, the reset, the base styles and the utility classes with your configuration. @use "nuvo" comes first, so core loads with the configured brand. Nothing else in the site loads @nuvoui/core.

    src/styles/global.scss
    @use "nuvo";
    @use "@nuvoui/core";
  3. Import it in the layout

    Every page renders through this layout, so every page gets the tokens.

    src/layouts/Layout.astro
    ---
    import "../styles/global.scss";
    
    interface Props {
    	title: string;
    }
    
    const { title } = Astro.props;
    ---
    
    <html lang="en">
    	<head>
    		<meta charset="utf-8" />
    		<meta name="viewport" content="width=device-width" />
    		<title>{title}</title>
    	</head>
    	<body>
    		<slot />
    	</body>
    </html>

With utility classes

Classes go straight into the markup.

src/pages/index.astro
---
import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
---

<Layout title="My app">
	<main class="grid gap-4 p-6">
		<div class="flex between x-center gap-3 p-4 bg-surface border rounded-md">
			<span class="font-semibold">Invoices</span>
			<span class="bg-success-subtle text-success text-xs rounded-full px-2 py-1">Paid</span>
		</div>
		<Card title="Quarterly report">Revenue and churn for July to September.</Card>
	</main>
</Layout>

With apply()

Each component loads the partial and writes only its own rules. Numbers are configuration keys (p(4) is var(--nu-space-4)), and modifiers come after the utility: state and breakpoint in either order. Astro scopes the block to the component.

src/components/Card.astro
---
interface Props {
	title: string;
}

const { title } = Astro.props;
---

<article class="card">
	<h2 class="title">{title}</h2>
	<p class="text"><slot /></p>
</article>

<style lang="scss">
@use "../styles/nuvo" as *;

.card {
    @include apply(p(4), bg(surface), rounded(md), shadow(md) hover, p(6) md);
}

.title {
    @include apply(m(0), font-size(lg), font(semibold));
}

.text {
    @include apply(mt(2), mb(0), text-color(muted));
}
</style>

Dark mode

With no data-theme on <html>, the tokens follow the operating system setting. Set data-theme="dark" or data-theme="light" to choose one, and change it from a script to switch at runtime: document.documentElement.dataset.theme = "dark". Any element can carry data-theme to theme only its own subtree. Every class and every apply() rule reads the tokens, so nothing else changes.

src/layouts/Layout.astro
<html lang="en" data-theme="dark">

What gets emitted

The global stylesheet holds the tokens, the light and dark themes, the reset, the base styles, the tooltips, the @property registrations and, with $generate-utility-classes: true, every utility class: about 1 MB minified, 92 KB gzipped. Without the classes it is about 42 KB. Load it on every page, because the tokens live there.

The style block of Card.astro compiles to five rules and nothing else: .card[data-astro-cid-...], its hover, its md padding, the title and the text. The tokens are not repeated per component, and a component cannot reset the configured brand.