Skip to content
WovenUI

WovenUI vs Foundation

The utility-first framework for Sass teams moving on from Foundation for Sites. WovenUI gives you classes with readable names, the same utilities as apply() mixins for any custom value, and semantic theme tokens that cover light, dark and high contrast from one markup. One @use block configures everything, with no settings file to copy, and mistakes stop the build with the fix in the message. Foundation for Sites 6 now receives bug fixes, with its last 6.x release named the final planned feature release [7].

Side by side

TopicWovenUIFoundation
Custom valuesAny value, with clean markup. Every utility class is also a mixin, called through apply() with a state and a breakpoint after it: apply(p(13px), bg(brand-hover) hover, p(6) lg). A unitless number is a scale key; a value with a unit is used as written.Layout and helper mixins such as breakpoint(), xy-cell(), xy-grid(), button-style(), clearfix and the prototype utilities (margin, padding, shadow and others) [3][8].
Catching mistakesMistakes stop the build with the fix in the message. An unknown key, color, token or modifier in a mixin, the configuration or token() fails the compile and lists the valid options: apply(p(7)) gives p: 7 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 (7px, 7rem). In markup, the VS Code extension adds completion, hover docs, color swatches and one-click fixes.Some checks exist: Sass raises an error when the first breakpoint is not 0, and deprecated mixins print warnings [9][8].
Dark mode and themesOne markup for every theme. Semantic tokens switch with data-theme="light", "dark" or "inverse" on any element; without data-theme the operating system decides. data-contrast="high" applies your $high-contrast-light and $high-contrast-dark maps. Text on a brand or status fill (text-on-brand) is picked for contrast at compile time, with a warning below 4.5:1.Colors are Sass variables, with a $foundation-palette map for primary, secondary, success, warning and alert [12]. The Sass source has .show-for-dark-mode and .hide-for-dark-mode, which follow prefers-color-scheme [10]. The Sass source has no theme switch.
Design tokensSemantic roles out of the box: --wu-* custom properties for surfaces, text, borders, focus, brand and status roles, plus the palette and every scale. Classes and mixins output var(--wu-*), so they follow the active theme at runtime, and token(name) raises an error for an unknown name.Values are fixed when Sass compiles: a search of the v6 branch for var(-- in its scss folder finds no CSS custom properties [8].
ConfigurationOne @use ... with (...) block: $brand, scale maps, theme override maps and feature flags. Nothing is copied into your project, so an upgrade never means merging a settings file, and every value is validated at compile time.Copy the settings file into your project as _settings.scss, edit its variables and import it before Foundation. The settings file cannot be updated automatically when a new version changes it, so you track releases yourself [1].
Sass modulesWritten for the Sass module system: @use and @forward, with Dart Sass 1.79 or later (below 2.0). No framework plugin, no runtime and no PostCSS plugin. Setup guides for React, Next.js, Vue, Svelte, Astro and plain HTML, plus a playground to try SCSS and markup in the browser.The source loads its partials with @import [14], which Dart Sass has deprecated and will remove in Dart Sass 3.0.0 [13]. The package builds with sass-embedded [11].
ComponentsYour components, styled from your tokens. @wovenui/core/mixins has the full API and outputs no CSS, so each CSS module or <style lang="scss"> block compiles to its own rules only. Free, step-by-step recipes for a card, navbar, form field, dark mode toggle, dashboard layout and pricing table. Plain HTML buttons and form fields get base styles.Ready-made Sass components, including button, card, dropdown, tooltip, badge, breadcrumbs, callout, pagination and table [14].
CSS you shipOnly what you use. By default you ship only the rules you write: tokens and base styles are 5.8 KB gzipped, and each component adds only its own rules, with no purge step. Feature flags switch whole groups off. Using the prebuilt file with every class? See Ship only the classes you use.You choose which parts to output by including their export mixins, such as foundation-everything or foundation-button [1].
GridReadable utilities on CSS grid and flexbox: grid cols-1 cols-2@md cols-3@lg gap-4, col-span-*, flex between x-center. In SCSS the same layout is one line: apply(grid, cols(1), cols(2) md, cols(3) lg, gap(4)).The XY grid is flexbox based and the default grid: .grid-x, .cell, sizes such as .small-6 and .medium-4, offsets, block grids (.small-up-2), gutter classes and a vertical grid [2]. Flexbox helper classes include .flex-container, .align-center and .flex-child-auto [5].
BreakpointsThe utility comes first and the breakpoint follows it: p-4@lg; in apply(), p(4) lg. The $breakpoints map is set through with(), and breakpoints are emitted from the smallest up, so a larger one always wins.A $breakpoints map (small, medium, large, xlarge, xxlarge). Class names put the breakpoint first: medium-6. The first key must be 0 or Sass raises an error [9].
RTLDirection-aware by default: px, mx and space-x are logical, alongside ms, me, ps, pe, start-*, end-*, border-s, rounded-s-* and text-start, so one stylesheet serves both directions. Physical classes stay for sides that must not flip.Text direction is a Sass setting, $global-text-direction: ltr, in the settings file [12].
Functionstoken(name) returns var(--wu-name) and fails on an unknown name. get-color(name, stop) returns a real color for Sass math. Text on a brand or status fill is picked for contrast at compile time.rem-calc(), a color set (color-contrast(), color-pick-contrast(), smart-scale()) and get-color(), which reads $foundation-palette [4].
JavaScriptNo JavaScript dependency. Core includes CSS-only tooltips and works next to any component library or script you already use.JavaScript plugins that need jQuery loaded before Foundation [6]; the package lists jquery, motion-ui and what-input as peer dependencies [11].
Maintenance@wovenui/core is in active development, versioned and published on npm.Foundation for Sites 6 now receives bug fixes, and its maintainers call the last 6.x release the final planned feature release [7].
LicenseMIT.MIT.

The same component in both

A responsive card grid in Foundation's XY grid, and the same grid in WovenUI, once as utility classes and once as BEM classes built with apply(). In SCSS, WovenUI sets every breakpoint in one apply() line instead of nested breakpoint() blocks. The utility version is rendered below in a light and a dark section, because every value reads a theme token.

WovenUI: grid HTML with utility classes
<div class="grid cols-1 cols-2@md cols-3@lg gap-4">
  <div class="p-4 bg-surface border rounded-lg">
    <h3 class="text-lg font-semibold">Card title</h3>
    <p class="text-muted">Some quick example text.</p>
  </div>
  <div class="p-4 bg-surface border rounded-lg">...</div>
  <div class="p-4 bg-surface border rounded-lg">...</div>
</div>
Foundation: XY grid HTML
<div class="grid-container">
  <div class="grid-x grid-margin-x small-up-1 medium-up-2 large-up-3">
    <div class="cell">
      <h3>Card title</h3>
      <p>Some quick example text.</p>
    </div>
    <div class="cell">...</div>
    <div class="cell">...</div>
  </div>
</div>
WovenUI: the same grid as BEM classes with apply()
@use "@wovenui/core" as * with (
  $brand: #4361ee
);

.gallery {
  @include apply(grid, cols(1), cols(2) md, cols(3) lg, gap(4));
}

.gallery__item {
  @include apply(p(4), bg(surface), border(1), rounded(lg), shadow(md) hover);
}

.gallery__title {
  @include apply(font-size(lg), font(semibold));
}

.gallery__text {
  @include apply(text-color(muted));
}
Foundation: grid as mixins (xy-cell and breakpoint)
@import 'settings';
@import 'foundation';

.gallery {
  @include xy-grid;
}

.gallery__item {
  @include xy-cell(12);

  @include breakpoint(medium up) {
    @include xy-cell(6);
  }

  @include breakpoint(large up) {
    @include xy-cell(4);
  }
}
WovenUI: configuration with with()
@use "@wovenui/core" as * with (
  $brand: #4361ee,
  $breakpoints: ("sm": 640px, "md": 768px, "lg": 1024px),
  $light-theme: ("bg-base": #fff),
  $dark-theme: ("bg-base": #101010)
);

.page {
  @include apply(bg(base), text-color(regular), px(4) lg);
}
Foundation: settings file and export mixins
// _settings.scss, copied into your project
$button-padding: 0.85em 1em !default;
$button-background: $primary-color !default;

// app.scss
@import 'settings';
@import 'foundation';

@include foundation-global-styles;
@include foundation-button;
@include foundation-xy-grid-classes;

Moving from Foundation

  1. Install the WovenUI extension for VS Code for completion, hover docs and one-click fixes while you convert. See Editor support.
  2. Replace the copied _settings.scss with one @use "@wovenui/core" with (...) block. Colors move to $brand and the theme maps. See Configuration.
  3. Replace @import of Foundation with @use of WovenUI, and drop the foundation-* export mixins: classes come from $generate-utility-classes and the feature flags.
  4. Rebuild grid markup. grid-x with cell small-12 medium-6 becomes grid cols-1 cols-2@md on the parent, or cols(1), cols(2) md in apply(). Move the breakpoint from the front to the end of the class. See Grid and Media queries.
  5. Replace xy-cell() and breakpoint() with apply() items that carry a breakpoint, such as cols(3) lg. See apply().
  6. Build your components as your own classes with apply(), starting from the free card, navbar and form field recipes. See Component styles.
  7. Replace show-for-dark-mode with data-theme and the theme tokens. See Theming and Tokens.
  8. Keep the JavaScript you choose for interactive behavior; WovenUI adds no JavaScript dependency. Core tooltips are CSS only: see Tooltips.