Styling with Vanilla Extract
I’d like to introduce everyone to yet another of many options to use when styling your application: Vanilla Extract. There’s probably a whole seperate blog post to write on the philosophy of how you can pick your tools/packages, which is best for what kind of project or teams… but at least for this post the tool in question is Vanilla Extract.
What is Vanilla Extract
Vanilla Extract is a CSS preprocessor (like Less or Sass) but instead of it’s own unique syntax you just use TypeScript.
The difference between Vanilla Extract and styling options like styled-components or Emotion is that when your app is built for production deployment, Vanilla Extract generates minified .css
files to go alongside your JS scripts in the build/dist directory. This alleviates an issue seen with CSS-in-JS solutions where the client has to generate component <style>
tags at runtime as they render (and at larger scales/number of styles, this is not very performant on some devices).
In TypeScript projects I see it as quick win because you’re not growing the toolset but are still able to leverage and reap the benefits of any existing TypeScript tooling you’ve set up and without needing to onboard developers into a whole new library or styling solution space.
Because it’s TypeScript, your existing linting and formatters already implemented for the TypeScript code continue to work as-is with it, you get VSCode’s type completion features when writing it and refactoring Vanilla Extract styles is a breeze as it’s TypeScript top to bottom (so renaming a symbol in the styles file will also rename all the usages in your code via imports).
Setting up Vanilla Extract with Vite
Vanilla Extract supports a range of integrations such as esbuild, Webpack, Next.js, Parcel, Rollup and Gatsby - but I’m focusing on the Vite integration.
Adding Vanilla Extract to a Vite project is super easy!
First, install Vanilla Extract to your project with npm i @vanilla-extract/css
Next, add the Vite plugin npm i @vanilla-extract/vite-plugin -D
And lastly, add the Vite plugin to your vite.config.ts
like so
Styling your first element
Writing styles with Vanilla Extract is just as fast as setting it up; consider the following React component:
We want to turn that div
element into a wrapper so the content is centred and style the text.
To do that, I’ll create a new file called App.css.ts
with the following contents:
The .css.ts
extension on this file is important. When Vite is building and the vanillaExtractPlugin
is run, it finds all files ending with .css.ts
and creates the corresponding CSS styles for them.
If you try to mix a style()
function into say App.tsx
then you’ll see errors that you are creating styles outside of a .css.ts
context and the app won’t load…
Once I’ve created that App.css.ts
file, I import it to the App.tsx
file and assign the class to the div
element similar to how you normally apply CSS class names:
Note that I’m using a barrel import so that I can just type styles
and then get code completion on all the exported members of that style file:
At this point, I’ll save the file and let’s take a look at what happened in our app:
Vanilla Extract has generated a unique classname for this style (.App_wrapper__uhuo0v0
) based on what I named the const
export and the file that it was used in - very helpful for debugging styles.
Also, since we’re running this Vite app in development it’s placed the styles in a document <style>
tag for now - but if we build for production now we’ll see this in the console output:
Then if we re-run the React app from the dist
output (using npm run preview
) and check the network tab we’ll see that there’s CSS stylesheets being used in the built app on the client!
The style
function also supports media queries and selectors (nested, complex and pseudo) - I was going to write some additional sections on these but this post started getting a bit long.
Building Themes
tyling elements is core to any web application but pretty soon you’ll get sick of writing #3b82f6
over and over again every time you need that specific hue of blue. Most likely there’s other common style tokens you’re using in the app such as sizes, colors, shadows, typography styles and more. Ideally these are not defined every time they’re needed but instead defined once and referenced by multiple components and elements.
Well, Vanilla Extract’s theming capabilities is what you’re after to help out on this front.
Vanilla Extract offers several ways to create themes your app depending on the requirements you have - global themes, theme contracts and dynamic themes are all available to you.
I’m going to create a global theme with some colors, fonts and font sizes so you can see how this works in practice - so I start off by creating a file called theme.css.ts
and adding the following:
Calling this sets up a theme that is scoped to the body tag of the application and we add our theme variables to the empty object like so:
I’ve also included two fonts using Fontsource packages and the imports for them have been placed at the top of the App.tsx
file (and they just reference the .css
files that contain @fontface
declarations for the fonts)
But, if we run our application now you won’t see this theme loaded yet as it hasn’t been called by any components or other styles - so the next step is to use it somewhere.
Using Theme Variables
Back in our App.css.ts
we import the theme
const and reference it's properties like so:
And if we check back in our app after writing theme.colors.gray[800]
we’ll see:
Vanilla Extract uses CSS variables under the hood for a lot of things and we can see on the body
tag that there is a few other CSS variables for other properties we defined:
But, we don’t need to remember the named variables ourselves as by simply importing the theme
const and referencing the variable, Vanilla Extract has sorted out linking it up for us when it generates the CSS styles.
Global Styles
Usually there is low level set of styles you build on top of, these may be browser resets, the default font stack and font sizing for your app. To create these sorts of styles I created a new file called globalStyle.css.ts
we use the globalStyle
function inside it:
Note that just like in the component styles before, to use the theme properties we just import and use them like any other TypeScript variable (It’s just TypeScript all the way down…).
After importing this file into the App.tsx
we can check the browser again:
globalStyle
can also have multiple selectors passed as it’s first argument too - e.g. to style all h1 to h3 tags:
Which results in:
Parting Thoughts
So you’re ready to make some stuff with Vanilla Extract now, right?
Hopefully you can see some benefits or value in Vanilla Extract, understand how to install it, create some styles and apply those to a component as well as refer to theme variables for commonly used tokens.
If you made it this far, it’d be super to hear your thoughts on Vanilla Extract too.