NuvoUI vs Tailwind CSS
Both are utility-first. Tailwind is a CSS build tool that generates classes from your markup, including arbitrary values. NuvoUI is an SCSS framework: readable class names, the same utilities as apply() mixins, and theme tokens, compiled by Dart Sass alone.
Side by side
| Topic | Tailwind CSS | NuvoUI |
|---|---|---|
| Philosophy | Utility-first: style elements by combining single-purpose utility classes directly in markup [1]. | Utility-first SCSS with human-readable names. Every utility class is also a mixin for apply(), so the same vocabulary works in HTML and in component stylesheets. |
| Syntax | Variants are prefixes: hover:bg-sky-700, md:uppercase (mobile-first, applies from the breakpoint up) [1][2]. | Breakpoints are suffixes, states are prefixes: p-8@lg, hover:shadow-md. In SCSS, modifiers follow the utility: apply(p(8) lg, shadow(md) hover). |
| Custom values | Arbitrary values in square brackets in markup, generated on the fly: top-[117px], bg-[#bada55] [3]. | No arbitrary values in class names. Custom values go through mixins: apply(top(117px), bg(#bada55)). A unitless number is a scale key; a value with a unit is used as written. |
| Errors | Scans source files as plain text and throws away any token that does not map to a utility it knows, so a class name it does not recognize generates no CSS [11]. | Mixins, configuration and token() are checked at compile time. An unknown key, color or unit stops the compile and lists the valid options. apply(p(7)) fails with: 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). A misspelled class name in markup is not checked; it matches no rule. |
| Customization | CSS-first configuration with the @theme directive; theme variables decide which utility classes exist [4]. | Sass configuration in the first @use ... with (...): $brand, scale maps, theme override maps and feature flags, validated at compile time. |
| Theming and dark mode | dark: variant, based on prefers-color-scheme by default; a manual toggle is set up with @custom-variant [5]. Dark styles are written per utility unless you define your own semantic theme variables. | Semantic tokens switch with data-theme="light", "dark" or "inverse" on any element, with an operating system fallback. A [data-contrast="high"] hook applies $high-contrast-light and $high-contrast-dark override maps; both maps are empty by default. |
| RTL | rtl: and ltr: variants [6], and logical utilities such as ms-*, me-*, ps-*, pe-* [7][8]. | Logical utilities and mixins (ms, me, ps, pe, start-*, end-*, border-s, rounded-s-*, text-start) alongside physical ones. No rtl: variant; direction-specific overrides are written in SCSS. |
| Design tokens | Theme variables are also emitted as regular CSS variables, for example --color-mint-500 [4]. | --nu-* custom properties for surfaces, text, borders, focus, brand and status roles, the palette and every scale. Classes and mixins output var(--nu-*); token(name) raises an error for an unknown name. |
| Build requirements | Installed as a Vite plugin, a PostCSS plugin or the Tailwind CLI [9]. v4 targets Chrome 111, Safari 16.4 and Firefox 128 and later [10]. | Dart Sass 1.79 or later (below 2.0), the sass package. Vite, Next.js (Turbopack and webpack) and Astro compile SCSS once it is installed, and @use "@nuvoui/core" resolves with no extra configuration; Svelte adds vitePreprocess(). No framework plugin, no JavaScript, no runtime, no CLI and no PostCSS plugin. |
| Bundle approach | Scans source files as plain text and generates only the classes it finds. Class names built by string concatenation are not detected [11]. | No scanning of its own. With $generate-utility-classes: true every configured class is emitted (about 1 MB minified, 93 KB gzipped with the defaults). PurgeCSS with a NuvoUI extractor removes the classes a project does not use: in our React and Vite sample the stylesheet went from 990 KB to 37 KB minified and from 92 KB to 5.6 KB gzipped, with identical computed styles. See Ship only the classes you use. Feature flags switch whole groups off. Mixin-only use emits what you write plus about 43 KB of base CSS (tokens, reset, base elements, tooltips), once, from the global stylesheet. apply() writes its declarations into each selector that uses it, so a style shared by many components is repeated in each of their rules. |
| Where styles live | Utility classes directly in markup [1]. Repeated styles are reused through a component in React, Svelte or Vue, or a template partial [1]. | Utility classes go in markup exactly as in Tailwind, in class or className. Vue, Svelte and Astro components can also keep a <style lang="scss"> block with apply() in the same file; React uses a CSS module next to the component. See the React, Vue, Svelte and Astro guides. |
| Component styles | Each CSS module and each Vue, Svelte or Astro <style> block is processed separately, so @apply needs @reference to the global stylesheet. The docs recommend utility classes in markup instead [10]. | @nuvoui/core/mixins has the same API as @nuvoui/core and outputs no CSS. Configure once in a partial and load the global stylesheet once; each CSS module or <style lang="scss"> block adds only its own rules, about 0.25 KB per component in our builds. |
| JS components | None in the framework. Tailwind Plus is a paid, one-time purchase with components, templates and the Catalyst UI kit [12]; Headless UI provides unstyled, accessible React and Vue components, MIT licensed [13]. | None. Core is a foundation layer with no component library. It styles plain HTML buttons and form fields, and includes CSS-only tooltips. |
| Ecosystem | Official installation guides for Vite, PostCSS, the CLI and many frameworks [9], plus Tailwind Plus and Headless UI. | Small: the core package and its documentation, with setup guides for React, Next.js, Vue, Svelte, Astro and plain HTML. No plugin ecosystem or community templates. |
| License | MIT [14]. | MIT. |
The same component in both
A card with a title, text and an action, a hover shadow and more padding from the large breakpoint. The Tailwind version uses palette classes, so dark mode needs a dark: class for each color. The NuvoUI version uses theme token classes (bg-surface, text-muted, border), so the same markup follows the theme.
<div class="flex flex-col gap-3 rounded-lg border border-gray-200 bg-white p-6
shadow-sm hover:shadow-md lg:p-8 dark:border-gray-700 dark:bg-gray-800">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Card title</h3>
<p class="text-gray-600 dark:text-gray-400">Some quick example text.</p>
<a href="#" class="self-start rounded-md bg-blue-600 px-4 py-2 text-white">Go somewhere</a>
</div><div class="flex col gap-3 p-6 p-8@lg bg-surface border rounded-lg
shadow-sm hover:shadow-md">
<h3 class="text-lg font-semibold">Card title</h3>
<p class="text-muted">Some quick example text.</p>
<a href="#" class="self-start px-4 py-2 rounded-md bg-brand text-on-brand">Go somewhere</a>
</div>@import "tailwindcss";
@theme {
--color-mint-500: oklch(0.72 0.11 178);
}@use "@nuvoui/core" as * with (
$brand: #4361ee
);
.card {
@include apply(
flex, col, gap(3),
p(6), bg(surface), rounded(lg), border(1),
shadow(md) hover, p(8) lg
);
}
.card__text {
@include apply(text-color(muted), mb(1.25rem));
}Moving from Tailwind CSS
- Rename layout classes:
flex-coliscol,justify-betweenisbetweenanditems-centerisx-center, each on a.flexelement. See Flex. - Move breakpoint prefixes to suffixes:
md:p-8becomesp-8@md. See Spacing and Media queries. - Replace arbitrary values in markup with a class from the configured scale, or with a mixin in SCSS:
@include apply(top(117px)). See apply(). - Replace
@themevariables with Sass configuration and theme override maps. See Configuration and Theming. - Replace
dark:class pairs with semantic token classes such asbg-surface,text-mutedandborder. See Tokens. - Look up any mixin or function in the A to Z reference.
Sources
Every statement about Tailwind CSS on this page comes from its official documentation or repository. Statements about NuvoUI come from the core source, its changelog and the core documentation.
- [1] Tailwind CSS: Styling with utility classeshttps://tailwindcss.com/docs/styling-with-utility-classes
- [2] Tailwind CSS: Responsive designhttps://tailwindcss.com/docs/responsive-design
- [3] Tailwind CSS: Adding custom styleshttps://tailwindcss.com/docs/adding-custom-styles
- [4] Tailwind CSS: Theme variableshttps://tailwindcss.com/docs/theme
- [5] Tailwind CSS: Dark modehttps://tailwindcss.com/docs/dark-mode
- [6] Tailwind CSS: Hover, focus and other stateshttps://tailwindcss.com/docs/hover-focus-and-other-states
- [7] Tailwind CSS: Marginhttps://tailwindcss.com/docs/margin
- [8] Tailwind CSS: Paddinghttps://tailwindcss.com/docs/padding
- [9] Tailwind CSS: Installing with Vitehttps://tailwindcss.com/docs/installation/using-vite
- [10] Tailwind CSS: Compatibilityhttps://tailwindcss.com/docs/compatibility
- [11] Tailwind CSS: Detecting classes in source fileshttps://tailwindcss.com/docs/detecting-classes-in-source-files
- [12] Tailwind CSS blog: Tailwind UI is now Tailwind Plushttps://tailwindcss.com/blog/tailwind-plus
- [13] Headless UI repositoryhttps://github.com/tailwindlabs/headlessui
- [14] Tailwind CSS repository (MIT license)https://github.com/tailwindlabs/tailwindcss