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.
<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>@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);
}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(--wu-shadow-md);
}
}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.
@forward "@wovenui/core/mixins" with (
$brand: #4361ee
);@use "woven";
@use "@wovenui/core";@use "../styles/woven" as *;
.card {
@include apply(
p(4), bg(surface), rounded(md),
shadow(md) hover, p(6) md
);
}.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 documentationMIT licensed. No runtime, no plugin, plain CSS out.