Skip to content
NuvoUI

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.

npm install -D sass @nuvoui/coreGet startedTry it in the browser

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.

HTML
<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>
SCSS
@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);
}
SCSS

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.

Compiled CSS
@media (min-width: 1024px) {
  .card:hover {
    box-shadow: var(--nu-shadow-md);
  }
}
State and breakpoint follow the utility, in either order.

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 documentation

apply() 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.