Vanilla-Extract & Stitches: A Comparison
Nayaab Khan
Posted on March 14, 2022
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.
🧁 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.
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.
🧁 Vanilla-Extract: No negative impact on React Developer Tools.
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?
🧵 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
- A thorough analysis of all the current CSS-in-JS solutions with SSR & TypeScript support for Next.js
- How React-Native-Web uses atomic styles and high-level DOM-agnostic abstractions
- How Facebook uses atomic styles
- RF21 – Naman Goel – Rethinking CSS - Introducing Stylex
- RF21 – Mark Dalgleish – Zero-runtime CSS-in-TypeScript with vanilla-extract
Posted on March 14, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.