Skip to content
WovenUI

The utility-first SCSS framework that refuses mistakes.

Readable classes, design tokens for every theme, and plain CSS out.

WovenUI is an open-source (MIT) SCSS framework: utility classes you can read, the same utilities asapply() mixins for any value, and --wu- design tokens with light, dark, inverse and high-contrast themes. SCSS in, plain CSS out, and nothing in the browser but CSS.

Light, dark and high-contrast themes from one markup.

Light, dark, inverse, high contrast and right to left. The markup never changes; the tokens do. No per-element dark variants to write, nothing to remember.

Utility classes you can read, apply() for any value.

The first row of that inbox and its markup. The row wants a padding that is not on the scale, 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="flex col row@sm gap-3@sm grow min-w-0">
    <span class="w-24@sm w-32@md no-shrink truncate font-semibold" dir="auto">Mara Lindqvist</span>
    <span class="grow min-w-0 truncate@sm" 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>
  <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 "@wovenui/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(--wu-space-3);
}
.mail-row > * {
  --wu-gap: var(--wu-space-3);
}
.mail-row {
  padding-inline: var(--wu-space-4);
  padding-block: 1.125rem;
  border-bottom-width: var(--wu-border-width-1);
  border-bottom-style: var(--wu-border-style);
  border-bottom-color: var(--wu-border-regular);
  --wu-border-style: solid;
  cursor: pointer;
}
.mail-row:hover {
  background-color: var(--wu-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(--wu-shadow-md);
  }
}
State and breakpoint follow the utility, in either order.

Component styles that compile to their own rules only.

Configure once in a partial. Load the full package once, in the global stylesheet. Every CSS module and component style block then compiles to the rules you wrote and nothing else: no repeated tokens, no repeated base styles, nothing to purge.

src/styles/_woven.scss
@forward "@wovenui/core/mixins" with (
  $brand: #4361ee
);
src/styles/global.scss
@use "woven";
@use "@wovenui/core";
Card.module.scss
@use "../styles/woven" as *;

.card {
  @include apply(
    p(4), bg(surface), rounded(md),
    shadow(md) hover, p(6) md
  );
}
Compiled CSS
.card {
  padding: var(--wu-space-4);
  background-color: var(--wu-bg-surface);
  border-radius: var(--wu-radius-md);
}
.card:hover {
  box-shadow: var(--wu-shadow-md);
}
@media (min-width: 768px) {
  .card {
    padding: var(--wu-space-6);
  }
}

Compile-time errors that stop mistakes.

Write something invalid and the build stops, before the mistake can reach a browser. Keys, colors, tokens and modifiers are all checked.

@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. color: token(text-mutd); stops the build with Error: "token(text-mutd): unknown token. Close matches: text-muted." Written as color: token(text-muted); it compiles.

What ships in WovenUI core

WovenUI core 2.2.1

Output
Plain CSS from SCSS modules loaded with @use. Dart Sass is the only build requirement. No JavaScript, no runtime, no plugin.
Classes and mixins
Every utility is also a mixin for apply(), with states and breakpoints. Utility classes are opt-in; the mixins entry outputs nothing until you use it.
Tokens
Every scale is a --wu- custom property: color, space, type, radius, shadow, motion. Roles and palette are generated in OKLCH from the colors you configure.
Themes
Light, dark and inverse through data-theme, high contrast through data-contrast. Without data-theme the operating system decides. Text on a brand or status fill is checked for contrast at build.
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.
Editor
The WovenUI extension for VS Code: completion, hover, diagnostics and quick fixes for apply(), classes and tokens.MarketplaceSetup
AI assistants
A complete guide ships in the package and at /llms-full.txt, so an assistant writes valid WovenUI code.Read it
Build speed
apply() caches what it parses, so compile time follows the values you write, not the size of the framework.Run the stress test
License
MIT.

Install WovenUI with one line.

npm install -D sass @wovenui/coreRead the documentation

MIT licensed. No runtime, no plugin, plain CSS out.