Skip to content

Comparison

varia lives in a small but real gap between Tailwind/UnoCSS utilities and traditional CSS components. The closest peers each occupy a slightly different point in the design space; the most useful question is "when would you pick this?"

At a glance

variaCVAtailwind-variantsvanilla-extract recipesPanda CSS
Build-timeyesnonoyesyes
Framework-agnostic consumptionyesno (JS only)no (JS only)yes (any HTML)no (React/Vue/Svelte/Solid)
Readable class namesyesyesyeshashedhashed
Owns the CSS pipelineno (UnoCSS)nonoyesyes
Slot supportyesnoyesnono
Compound variantsyesyesyesyesyes
Theming via CSS variablesyes (recommended pattern)manualmanualyesyes
Runtime costnonesmallsmallnonenone

When to pick varia

  • You're building a design-system or component library that ships a vocabulary, not a runtime.
  • Consumers come from many ecosystems (Rails + ViewComponent, Astro, Eleventy, Hugo, Phoenix HEEx, Django) and you want one library that works across all of them.
  • You're already using UnoCSS, or are happy to adopt it.
  • You want readable, grep-able class names (btn-c-primary) instead of hashed atomic IDs.

When to pick something else

CVA (class-variance-authority)

CVA is the API-shape ancestor of varia — the config feels almost identical. The difference is what it returns: CVA returns a JS function you call from JSX (button({ color: 'primary' })); varia returns class names you write directly in markup.

Pick CVA if:

  • You're shipping a React/Vue/Svelte component library and want the callable.
  • You need default variants computed at the call site (CVA does this at runtime).
  • You don't mind the small runtime cost.
  • You don't need consumption from non-JS template languages.

tailwind-variants

Pick tailwind-variants if:

  • You're React-first and want the slots-and-compounds API as a runtime function call from JSX.
  • You don't care about consumption from non-JS template languages.

tailwind-variants is roughly CVA plus slots and compound variants. varia covers the same authoring surface (slots and compoundVariants are both first-class on defineComponent) but emits class names you write directly in markup instead of returning a callable from JSX. The choice is mostly about consumption model: callable function vs. plain HTML.

vanilla-extract recipes

Pick vanilla-extract recipes if:

  • You want a fully build-time CSS pipeline that doesn't depend on Tailwind/UnoCSS.
  • You're comfortable with hashed class names, and have tooling that doesn't grep for class strings.
  • You want first-class typed CSS values in TypeScript, not just utility strings.

vanilla-extract owns its own extractor and CSS engine. varia deliberately doesn't; UnoCSS does that part.

Panda CSS

Panda is the closest peer to varia in concept: recipes are similar to variants, both are build-time, both are JIT. Panda differs on three axes: it's framework-coupled, it owns its own CSS engine, and it emits hashed class names.

Pick Panda if:

  • You want a complete framework-coupled styling solution (recipes, patterns, conditions, semantic tokens, layout primitives) all in one tool.
  • You're committed to React, Vue, Svelte, or Solid.
  • Hashed atomic class names are acceptable.

Panda is excellent at what it does. The reason varia exists isn't a complaint about Panda; the JS-framework coupling and hashed class names make Panda a non-option for the Rails / Phoenix / Astro / Hugo audience that ships server-rendered HTML. If you're React-first and the framework coupling is fine, try Panda first.

Why varia vs. just writing UnoCSS shortcuts manually

You can express the same component vocabulary by hand-writing UnoCSS shortcuts:

ts
// unocss.config.ts (manual)
shortcuts: [
  ['btn', 'inline-block font-medium rounded'],
  ['btn-c-primary', 'bg-blue-600 text-white hover:bg-blue-700'],
  ['btn-c-danger', 'bg-red-600 text-white hover:bg-red-700'],
  ['btn-s-sm', 'px-2 py-1 text-sm'],
  // ...repeated for every variant
]

defineComponent gives you:

  1. Structure. A variants-shaped config separates "this is the base" from "these are the colors" from "these are the sizes." Six shortcuts collapse into one readable block.
  2. Validation. Catches duplicate names, empty expansions, and invalid identifiers at config time, with error messages that name the offending component and class.
  3. Manifest output. The VariaClasses union for type-strict tooling, which you'd hand-roll alongside manual shortcuts.
  4. Boolean shorthand. outline: '...' produces btn-outline (no value suffix). Hand-rolled shortcuts can't represent this without ad-hoc naming conventions.

If your component library has fewer than ~5 variants total, manual shortcuts are fine. Past that, the structure starts to pay off.

Released under the MIT License.