Skip to content
WovenUI

WovenUI vs Bulma

The utility-first framework for Sass teams who want components that look like their own product. 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, inverse and high contrast from one markup. Like Bulma it ships no JavaScript, and mistakes stop the build with the fix in the message.

Side by side

TopicWovenUIBulma
Custom valuesAny value, right in your component CSS: apply(p(13px), bg(#bada55)), with the same states and breakpoints as the utilities and checked by the compiler. A unitless number is a scale key; a value with a unit is used as written.Change the Sass variables the helpers and components read, or override a CSS variable at a narrower scope [4][8][10].
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).Not documented.
Dark mode and themesLight, dark, inverse and high contrast from one markup. 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. A new theme token goes into $light-theme and $dark-theme and gets classes and mixin support.A theme is a collection of CSS variables. Light is required, dark is optional, and each is scoped by @media (prefers-color-scheme), [data-theme=dark] and .theme-dark, on any element or on <html> [5][6]. A custom theme is your own variables under a name and a scope [6].
ComponentsComponents that look like your product. 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 are styled from tokens, tooltips are included, and any component library you already use works alongside."Modern CSS framework based on Flexbox" [1], with ready-made elements and components such as buttons, cards, navbars and modals, styled with modifier classes [9][12][13].
Class namesNames you can read: between, x-center, bg-surface, z-modal, with the breakpoint as a suffix: p-6@lg. In SCSS every modifier follows the utility: apply(p(6) lg, bg(brand-hover) hover). Spacing such as px, mx and ms is logical, so layouts follow right-to-left text.Component classes plus modifiers: button is-primary is-large, card, card-content [12][13]. The breakpoint sits at the end of a helper name: is-flex-tablet-only, is-hidden-fullhd [16], and column sizes such as is-half-desktop [17].
UtilitiesOne vocabulary for HTML and SCSS: every utility class is generated from a configuration map, and every utility is also a mixin for apply(). Change a map and the classes and mixins follow.Helper classes for spacing (m* and p* in all directions, sizes 0 to 6), flexbox, visibility, color and typography [14][15][16]. The spacing page counts 112 spacing helpers, reduced to 18 by customizing the Sass variables [14].
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-*), and token(name) raises an error for an unknown name, so a typo never reaches the browser.Every variable is prefixed bulma- and defined on :root or on a component such as .button; a variable set at a more specific scope overrides the global one [4]. Bulma can be customized with CSS variables alone [3][11].
Color and contrastReadable text on every fill: text-on-brand and every other text-on-{role} token are picked for contrast at compile time, with a warning below 4.5:1. Shades 50, 100 to 900 and 950 for every role and hue are generated in OKLCH from your color, and theme tokens such as bg-surface and text-muted flip per theme.For each of text, link, primary, info, success, warning and danger, Bulma generates CSS variables from hue, saturation and lightness (for example --bulma-primary-h), with 21 shades from 00 to 100 and an invert color for each [7].
ConfigurationOne @use ... with (...) block: $brand, scale maps, theme override maps and feature flags. Everything is validated at compile time, and a wrong key raises an error that names it.Five levels: global Sass variables, component Sass variables, global CSS variables, component CSS variables and helper classes. Initial variables hold literal values; derived variables are calculated from them [10].
Component stylesMade for component stylesheets. @wovenui/core/mixins has the full API and outputs no CSS: configure once in a partial, and each CSS module or Vue, Svelte or Astro <style lang="scss"> block compiles to its own rules only.Built with Dart Sass; the docs recommend the sass npm package [3][8]. Override variables with @use "bulma/sass" with (...) [8], or load only the modules you need, such as bulma/sass/elements/button, and configure each with with [9].
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.Ships bulma.css and a minified file, plus prebuilt variants without dark mode, without helpers and with prefixed classes [2]. With Sass you can load only the modules you use [9].
SetupLink the prebuilt dist/wovenui.min.css with no build step, or @use "@wovenui/core" with Dart Sass 1.79 or later (below 2.0) for apply() and your own configuration. No framework plugin, no PostCSS plugin. Setup guides for React, Next.js, Vue, Svelte, Astro and plain HTML, plus a playground to try it in the browser.Install the compiled file from npm or load it from a CDN [1]. Customizing through Sass needs Dart Sass [8].
JavaScriptNone. No runtime, and tooltips are CSS only. Your own scripts or any library add interactive behavior.None. The sole output is a CSS file and the README states that no JavaScript is included [1].
Editor supportThe WovenUI extension for VS Code: completion, hover docs, color swatches and one-click fixes for apply(), classes and tokens.Not documented.
AI assistantsA complete guide ships in the package at docs/llms-full.txt and on this site at /llms-full.txt, with a short index at /llms.txt, so an assistant writes valid WovenUI code.Not documented.
LicenseMIT.MIT.

The same component in both

One card, written both ways. Bulma provides its card and button classes; WovenUI builds the same card from theme token classes (bg-surface, text-muted, border), or as your own BEM classes with apply(), so every value follows your brand and the theme: it is rendered below in a light and a dark section. Then Sass configuration and theme scoping side by side.

WovenUI: HTML with utility classes
<div class="flex col gap-3 p-6 bg-surface border rounded-lg hover:shadow-md">
  <h3 class="text-lg font-semibold">Card title</h3>
  <p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  <a href="#" class="self-start px-4 py-2 rounded-md bg-brand text-on-brand">Go somewhere</a>
</div>
Bulma: HTML (card from the docs, shortened)
<div class="card">
  <header class="card-header">
    <p class="card-header-title">Component</p>
  </header>
  <div class="card-content">
    <div class="content">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    </div>
  </div>
  <footer class="card-footer">
    <a href="#" class="card-footer-item">Save</a>
    <a href="#" class="card-footer-item">Edit</a>
    <a href="#" class="card-footer-item">Delete</a>
  </footer>
</div>

<button class="button is-primary is-large">Go somewhere</button>
WovenUI: configuration, a custom theme token and the card as BEM classes with apply()
@use "@wovenui/core" as * with (
  $brand: #8a4d76,
  $light-theme: ("bg-card": gray 50),
  $dark-theme: ("bg-card": #202020)
);

.card {
  @include apply(
    flex, col, gap(3), p(6),
    bg(card), rounded(lg), border(1),
    shadow(md) hover
  );

  max-width: 18rem;
}

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

.card__text {
  @include apply(text-color(muted));
}

.card__action {
  @include apply(
    self-start, px(4), py(2), rounded(md),
    bg(brand), text-color(on-brand), bg(brand-hover) hover
  );
}
Bulma: Sass customization (from the docs)
$purple: #8a4d76;
$pink: #fa7c91;

// Path to Bulma's sass folder
@use "bulma/sass" with (
  $family-primary: '"Nunito", sans-serif',
  $primary: $purple,
  $link: $pink,
  $control-border-width: 2px,
  $input-shadow: none
);
WovenUI: themes on an element
<html data-theme="dark">
  <body class="bg-base text-regular">
    <aside data-theme="light" class="bg-surface p-4">
      A light section on a dark page
    </aside>
  </body>
</html>
Bulma: themes on an element
<html data-theme="dark">
  <body>
    <div data-theme="light">
      A light section on a dark page
    </div>
  </body>
</html>

Moving from Bulma

  1. Start from the free recipes, such as the card and the navbar, and build each component as your own class with apply() or as utility markup. See apply().
  2. Replace modifier classes with tokens and mixins: is-primary becomes bg(brand) with text-color(on-brand), and size modifiers become values such as px(4) and font-size(lg). See Spacing.
  3. Move the breakpoint into the suffix or the modifier: a helper such as is-flex-tablet becomes flex@md as a class, or flex md inside apply(). See Media queries.
  4. Replace the @use "bulma/sass" with (...) block with one @use "@wovenui/core" with (...) block. See Configuration.
  5. Replace --bulma-* variables and [data-theme=...] blocks with --wu-* tokens and the $light-theme and $dark-theme maps. See Theming and Tokens.
  6. Keep your scripts: both are CSS-only, so interactive parts you wrote on top of Bulma carry over unchanged. Core tooltips are CSS only: see Tooltips.
  7. Install the WovenUI extension for VS Code for completion and hover docs while you convert, and look up any mixin or function in the A to Z reference.