Vanilla-Extract & Stitches: A Comparison

nayaabkhan

Nayaab Khan

Posted on March 14, 2022

Vanilla-Extract & Stitches: A Comparison

How do you pick between these two great options?

Both focus on providing the best-in-class developer experience with minimal runtime.

So, how do they compare? If you had to pick one which one would you pick given your context?

Let’s get the similarities out of the way first.


Similarities

Both libraries support:

  • Type-safe API: Both have a typed library API to assist the developers.
  • Token-based theming: Tokens are the building block of a system, both libraries provide a first-class support for typed tokens.
  • Variants: Both libraries provide an API to add variants to a component.
  • CSS custom properties: Both libraries convert theme tokens to CSS custom properties. They also provide API for user-defined CSS custom properties.

In a gist, they provide a very comparable developer experience. So what’s different?


Differences

Library Runtime

This is the amount of JavaScript the libraries ship by default.

🧵 Stitches claims a “near-zero runtime.” Bundlephobia reports 6 kB gzipped which is quite slim.

🧁 Vanilla-Extract runs at build-time. So no runtime is added. If you use the @vanilla-extract/dynamic package, it’d add 718 bytes to your bundle.

Userland Runtime

This is the amount of runtime added due to the user-land code.

🧵 Stitches: User-land runtime code scales linearly as CSS is bundled in JavaScript. Theoretically, this will be as big as the design system itself.

🧁 Vanilla-Extract: None if you use the styling API only. If you use the Sprinkles API for generating Atoms, it generates a script to look-up the classnames which doesn’t impact rendering performance. This script grows along with the amount of atoms generated which usually stop growing over time.

Rendering Performance

The way in which CSS is delivered to the users can have an impact on several metrics like FCP and TTI of the first page and subsequent pages.

🧵 Stitches

Because the styles are rendered inline, FCP happens much earlier for the first page load.

New styles are injected dynamically which invalidates a lot of the work that has already been done by the browser, no matter how little CSS was injected.

TTI happens later because a significant time of main thread is spent on “Style & Layout”. The “uncanny valley” is quite wide.

🧁 Vanilla-Extract

Because CSS files are generated statically and are <link />ed and requested separately, FCP happens later in comparison to Stitches for the first page load.

Subsequent renders are fast as the common CSS (atoms) are cached on the first request. Only new styles are fetched, if any.

TTI isn’t delayed because the main thread isn’t busy with “Style & Layout.” The “uncanny valley” is narrow.

Token typing and mapping

This is whether the tokens themselves are strictly typed and any deviations from token usage are reported.

🧵 Stitches sensibly and conveniently maps the tokens to the CSS properties. But they are not typed to be limited to the tokens only. Though, there is a provision to have a stricter experience.

You may use any string and the type-checker won’t complain.

🧁 Vanilla-Extract doesn’t give you any mappings if you use the styling API. Though, if you use Sprinkles (for atoms), which you should, you can do your own mapping. The CSS properties are strictly limited to the tokens.

CSS Properties are strictly typed to the tokens when using Sprinkles

Component Debug-ability (React Developer Tools)

The library should not come in the way of debug-ability using Devtools.

🧵 Stitches: Using styled API causes component names to be mangled, also you cannot view component source code if you use styled. However, using the css function doesn’t have any of these problems.

When using the styled API, the components names are lost

🧁 Vanilla-Extract: No negative impact on React Developer Tools.

Component names are preserved with Vanilla-Extract

Documentation

How thorough and thoughtful the library documentation is.

🧵 Stitches comes with an excellent documentation. Even the, otherwise usually neglected, TypeScript experience is given full attention.

🧁 Vanilla-Extract has terse and API like docs. They’re difficult to get started with and require a lot of jumping around.

Custom Tokens and Names

Does the libary allow custom tokens and token names (e.g., spacings instead of space) as per the vocabulary that suits their team? E.g., timingFunctions.

🧵 Stitches only supports tokens from system-ui specs. Also not possible to customise token names either.

🧁 Vanilla-Extract supports custom tokens with custom names.

Atomic style generation

Does the library provide a way to generate a set of atomic classes to reuse and reduce the amount of CSS to be shipped?

Atomic CSS stops growing after a while

🧵 Stitches doesn’t support atomic generation at this time.

🧁 Vanilla-Extract supports atomic generation using Sprinkles. You can even use the atoms inside variants.

Takeaway

For us, performance and user experience are important as a lot of our users come from a low powered mobile device. The choice was obvious, we decided to go with Vanilla-Extract:

  • Zero runtime
  • Ability to generate atoms that helps in limiting the amount of CSS we ship to our users
  • Better rendering performance overall
  • Ability to use expensive libraries like Polished in .css.ts files without worrying about the impact on bundle-size as they’re evaluated at build time.

A Note On React 18

If you care about performance and the nitty-gritty details, you should definitely read the library upgrade guides by the React Working Group:

Here’s an interesting excerpt from the upgrade guide for <style>:

While this technique for generating CSS is popular today, we’ve found that it has a number of problems that we’d like to avoid. Therefore we don’t have plans for adding any solutions upstream to handle this in React. For the time being, we expect this to have to be handled by third-party libraries such as in this guide.

Our preferred solution is to use <link rel="stylesheet"> for statically extracted styles and plain inline styles for dynamic values. E.g. <div style={{...}}>. You could however build a CSS-in-JS library that extracts static rules into external files using a compiler. That’s what we use at Facebook.

It’s tempting to use dynamically generated style sheets since it contains only the rules that are active on the page right now. However, this comes at a cost. Usually at the expense of runtime cost. It also comes at a cost of inserting new rules frequently which invalidates a lot of the work that has already been done.

Shared stylesheets are efficient to parse and are very cacheable. By extracting them into shared styles you are also effectively batching a lot of that work up front. This lets you avoid doing that work later on as the user starts interacting with the page.

References

💖 💪 🙅 🚩
nayaabkhan
Nayaab Khan

Posted on March 14, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024