Styling with Vanilla Extract

August 1, 2024
Alex Gabites
Vanilla Extract is a CSS preprocessor (like Less or Sass) but instead of it’s own unique syntax you just use TypeScript. 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.

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

And believe it or not, thats it. Done in 3 steps

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…

For Vanilla Extract to work, styles must be defined in .css.ts files

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:

VSCode giving me code completion for my styles

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!

You may also note that our previously named class of .App_wrapper__uhuo0v0 has been minified to simply .uhuo0v0 in production.

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:

That color: var(--colors-gray-800__dlz7fu3) is a reference to our theme token when it’s generated.

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:

You’ll note that the font has changed and we can see both the color and font family applied via CSS variables

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:

Note the font-family attribute is applied via a css variable

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.

LEARN MORE

Let’s talk

Contact Us

Stories & insights

read the blog