The SCSS foundation for design systems.
Classes you can read, tokens for every theme, and plain CSS out.
NuvoUI is an open-source (MIT) utility-first SCSS framework: readable utility classes, the same utilities asapply() mixins, and --nu- design tokens with light, dark and high-contrast themes.
Every theme. One markup.
Light, dark, inverse, high contrast and right to left. The classes stay the same; only three attributes change.
Read it once. Know what it does.
The first row of that inbox and its markup. The scale has 1rem and 1.25rem; this row wants 1.125rem, andapply() takes it as written.
<label class="flex mail-row">
<input type="checkbox">
<span class="w-2 h-2 rounded-full no-shrink bg-brand"></span>
<span class="w-24 w-32@md no-shrink truncate font-semibold" dir="auto">Mara Lindqvist</span>
<span class="grow min-w-0 truncate" dir="auto">
<span class="font-semibold">Q4 roadmap review moved to Thursday</span>
<span class="hide inline@md text-muted">Pushing it to 14:00 so finance can join. Agenda is unchanged.</span>
</span>
<span class="hide flex@lg pill px-2 text-xs bg-blue-subtle text-blue">Planning</span>
<time class="w-16@md no-shrink text-end text-xs text-muted">09:42</time>
</label>@use "@nuvoui/core" as *;
.mail-row {
@include apply(
flex, x-center, gap(3),
px(4), py(1.125rem),
border-bottom(1), cursor(pointer),
bg(hover) hover
);
}Compiled CSS
.mail-row {
display: flex;
align-items: center;
gap: var(--nu-space-3);
}
.mail-row > * {
--nu-gap: var(--nu-space-3);
}
.mail-row {
padding-inline: var(--nu-space-4);
padding-block: 1.125rem;
border-bottom-width: var(--nu-border-width-1);
border-bottom-style: var(--nu-border-style);
border-bottom-color: var(--nu-border-regular);
--nu-border-style: solid;
cursor: pointer;
}
.mail-row:hover {
background-color: var(--nu-bg-hover);
}apply(shadow(md) hover lg) reads as shadow on hover, on large screens; apply(shadow(md) lg hover) reads as shadow on large screens, on hover; both compile to the same rule.
@media (min-width: 1024px) {
.card:hover {
box-shadow: var(--nu-shadow-md);
}
}It refuses mistakes.
Write something invalid and the build stops, before the mistake can reach a browser.
@include apply(px(4), py(1.125)); stops the build with Error: "py: 1.125 is not a configured key. Valid keys: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64. Write a unit for a custom value (1.125px, 1.125rem)." Written as @include apply(px(4), py(1.125rem)); it compiles. @include apply(bg(primary) hover); stops the build with Error: "bg(primary): unknown color. Use a role (brand, success, danger, warning, info), a palette name (gray, red, blue, green, yellow, purple, pink), a theme word (base, surface, alternate, raised, hover, selected, disabled), a hex color, a CSS color name, ..." Written as @include apply(bg(brand) hover); it compiles.
Specifications
NuvoUI core 2.1.1
- Output
- Plain CSS from SCSS modules loaded with @use. Requires Dart Sass 1.79.0 or later, below 2.0. Core ships no JavaScript and needs no runtime or PostCSS plugin.
- Classes and mixins
- Every utility is also a mixin for apply(), with states and breakpoints. Utility classes are opt-in through $generate-utility-classes.
- Tokens
- Every scale is a --nu- custom property: color, space, type, radius, shadow, motion. Roles and palette are generated in OKLCH, in stops 50, 100 to 900, and 950.
- Themes
- Light, dark and inverse through data-theme, high-contrast overrides through data-contrast="high". Without data-theme, the operating system color scheme decides. Text on a brand or status fill below 4.5:1 raises a warning.
- Direction
- Logical utilities such as ps, me, start, border-s and text-start mirror under dir="rtl".
- Errors
- Unknown keys and color names stop the compile and list the valid options.
- Performance
- About 0.6 ms per unique raw value in apply(), plus about 0.2 s per compiled entry for loading the package.Read how we tested itRun the stress test
- License
- MIT.
Start with one line.
npm install -D sass @nuvoui/coreRead the documentationapply() works in CSS modules and component style blocks. Install sass and Vite, Next.js and Astro compile it with no plugin; each component adds only its own rules. See the React guide.