Home > Web Front-end > CSS Tutorial > CSS in TypeScript with vanilla-extract

CSS in TypeScript with vanilla-extract

Jennifer Aniston
Release: 2025-03-19 09:16:16
Original
798 people have browsed it

CSS in TypeScript with vanilla-extract

vanilla-extract: A new, framework-in-TypeScript library. It provides a lightweight, robust and intuitive way to write styles. vanilla-extract is not a mandatory CSS framework, but a flexible developer tool. In recent years, the CSS tool field has been relatively stable, with tools such as PostCSS, Sass, CSS Modules and styled-components appearing before 2017 (some even earlier) and are still very popular today. Tailwind is one of the few tools that have brought about changes in the CSS tool field in recent years.

vanilla-extract aims to trigger change again. It was released this year and benefited from some of the latest trends, including:

  • JavaScript developers turn to TypeScript
  • Browser support for CSS custom properties
  • Practical priority style

There are a lot of clever innovations in vanilla-extract, which I think makes it meaningful.

Zero runtime overhead

The CSS-in-JS library usually injects styles into a document at runtime. This has some benefits, including critical CSS extraction and dynamic styles.

But usually, individual CSS files perform better. This is because JavaScript code needs to go through a more expensive parsing/compilation process, while separate CSS files can be cached, while HTTP2 protocol reduces the cost of additional requests. Additionally, custom properties are now available for many dynamic styles for free.

Therefore, vanilla-extract follows Linaria and astroturf instead of injecting styles at runtime. These libraries allow you to style using JavaScript functions that are extracted at build time and used to build CSS files. Although you write vanilla-extract using TypeScript, this does not affect the overall size of the production JavaScript package.

TypeScript Support

One of the great value propositions of vanilla-extract is that it provides type checking. If it's very important to keep the codebase type safe, why not do the same with styles too?

TypeScript offers many benefits. The first is automatic completion. If you type "fo", in the editor that supports TypeScript, you get a drop-down list with font options (fontFamily, fontKerning, fontWeight, or other matches) for you to choose from. This makes CSS properties easy to discover with the convenience of the editor. If you don't remember the name of fontVariant, but know that it starts with "font", you can type it and scroll through the options. In VS Code, you do not need to download any additional tools to achieve this.

This greatly speeds up the writing of styles:

This also means your editor will monitor you all the time, ensuring you don't make any spelling errors that can lead to frustrating bugs.

The vanilla-extract type also provides syntax interpretation and links to the MDN document for the CSS properties you are editing in its type definition. This saves the step of crazy Google searches when the style behaves abnormally.

Writing with TypeScript means you use camel nomenclature to represent CSS properties, such as backgroundColor. This may require some changes for developers who are accustomed to using regular CSS syntax such as background-color.

integrated

vanilla-extract provides first-party integration for all the latest packaging tools. Here is a complete list of integrations it currently supports:

  • webpack
  • esbuild
  • Vite
  • Snowpack
  • NextJS
  • Gatsby

It also has nothing to do with the framework. You simply import class names from vanilla-Extract, which are converted to strings at build time.

How to use

To use vanilla-Extract, you can write a .css.ts file that your components can import. Calls to these functions are converted into hash and scoped class name strings in the build step. This may sound similar to CSS Modules, and it's no coincidence: Mark Dalgleish, one of the creators of vanilla-Extract, is also co-creator of CSS Modules.

style() function

You can use style() function to create an automatically scoped CSS class. You pass in the style of the element and then export the return value. Import this value somewhere in your user code and it will be converted to scope class name.

 // title.css.ts
import {style} from "@vanilla-extract/css";

export const titleStyle = style({
  backgroundColor: "hsl(210deg, 30%, 90%)",
  fontFamily: "helvetica, Sans-Serif",
  color: "hsl(210deg, 60%, 25%)",
  padding: 30,
  borderRadius: 20,
});
Copy after login
 // title.ts
import {titleStyle} from "./title.css";

document.getElementById("root").innerHTML = `<h1> Vanilla Extract</h1> `;
Copy after login

Media queries and pseudo-selectors can also be included in the style declaration:

 // title.css.ts
backgroundColor: "hsl(210deg, 30%, 90%)",
fontFamily: "helvetica, Sans-Serif",
color: "hsl(210deg, 60%, 25%)",
padding: 30,
borderRadius: 20,
"@media": {
  "screen and (max-width: 700px)": {
    padding: 10
  }
},
":hover":{
  backgroundColor: "hsl(210deg, 70%, 80%)"
}
Copy after login

These style function calls are a lightweight abstraction of CSS – all attribute names and values ​​are mapped to CSS properties and values ​​that you are familiar with. One change to get used to is that values ​​can sometimes be declared as numbers (e.g. padding: 30), defaulting to pixel unit values, while some values ​​need to be declared as strings (e.g. padding: "10px 20px 15px 15px").

Properties in style functions can only affect a single HTML node. This means you can't use nesting to declare the style of element child elements - which you might be used to in Sass or PostCSS. Instead, you need to style the child elements separately. If the child element needs to be based on different styles of the parent element, you can use selectors attribute to add styles that depend on the parent element:

 // title.css.ts
export const innerSpan = style({
  selectors:{[`${titleStyle} &`]:{
    color: "hsl(190deg, 90%, 25%)",
    fontStyle: "italic",
    textDecoration: "underline"
  }}
});
Copy after login
 // title.ts
import {titleStyle,innerSpan} from "./title.css";
document.getElementById("root").innerHTML = 
`<h1> Vanilla Extract</h1>
Unstyled`;
Copy after login

Alternatively, you can use the Topic API (which we will talk about next) to create custom properties in the parent element, which are used by the child nodes. This may sound strict, but it is deliberately designed to improve the maintainability of large code bases. This means you will know exactly where the style of each element in the project is declared.

theme

You can use createTheme function to build variables in a TypeScript object:

 // title.css.ts
import {style,createTheme } from "@vanilla-extract/css";

// Create theme export const [mainTheme,vars] = createTheme({
  color:{
    text: "hsl(210deg, 60%, 25%)",
    background: "hsl(210deg, 30%, 90%)"
  },
  lengths:{
    mediumGap: "30px"
  }
})

// Use topic export const titleStyle = style({
  backgroundColor:vars.color.background,
  color: vars.color.text,
  fontFamily: "helvetica, Sans-Serif",
  padding: vars.lengths.mediumGap,
  borderRadius: 20,
});
Copy after login

vanilla-extract then allows you to create variations of the theme. TypeScript helps it ensure that your variants use the same attribute name, so if you forget to add background attributes to your topic, you will get a warning.

Here is how to create a regular theme and dark mode:

 // title.css.ts
import {style,createTheme } from "@vanilla-extract/css";

export const [mainTheme,vars] = createTheme({
  color:{
    text: "hsl(210deg, 60%, 25%)",
    background: "hsl(210deg, 30%, 90%)"
  },
  lengths:{
    mediumGap: "30px"
  }
})
// Theme variant - Note that this part does not use array syntax export const darkMode = createTheme(vars,{
  color:{
    text:"hsl(210deg, 60%, 80%)",
    background: "hsl(210deg, 30%, 7%)",
  },
  lengths:{
    mediumGap: "30px"
  }
})
// Use topic export const titleStyle = style({
  backgroundColor: vars.color.background,
  color: vars.color.text,
  fontFamily: "helvetica, Sans-Serif",
  padding: vars.lengths.mediumGap,
  borderRadius: 20,
});
Copy after login

Then, using JavaScript, you can dynamically apply the class name returned by vanilla-extract to switch topics:

 // title.ts
import {titleStyle,mainTheme,darkMode} from "./title.css";

document.getElementById("root").innerHTML = 
`<div>
  <h1>Vanilla Extract</h1>
  Dark mode
</div>`
Copy after login

How does this work at the bottom? The object you declare in the createTheme function will be converted to a CSS custom property attached to the element class. These custom properties are hashed to prevent conflicts. The output CSS of our mainTheme example looks like this:

 .src__ohrzop0 {
  --color-brand__ohrzop1: hsl(210deg, 80%, 25%);
  --color-text__ohrzop2: hsl(210deg, 60%, 25%);
  --color-background__ohrzop3: hsl(210deg, 30%, 90%);
  --lengths-mediumGap__ohrzop4: 30px;
}
Copy after login

And the CSS output of our darkMode theme is as follows:

 .src__ohrzop5 {
  --color-brand__ohrzop1: hsl(210deg, 80%, 60%);
  --color-text__ohrzop2: hsl(210deg, 60%, 80%);
  --color-background__ohrzop3: hsl(210deg, 30%, 10%);
  --lengths-mediumGap__ohrzop4: 30px;
}
Copy after login

Therefore, we just need to change the class name in the user code. Apply darkmode class name to the parent element, mainTheme custom attribute will be replaced by darkMode custom attribute.

Recipes API

style and createTheme functions are sufficient to style a website by themselves, but vanilla-extract provides some extra APIs for improved reusability. The Recipes API allows you to create many variations for elements that you can choose from in tags or user code.

First, you need to install it separately:

 npm install @vanilla-extract/recipes
Copy after login

Here is how it works. You import recipe function and pass in an object containing base and variants properties:

 // button.css.ts
import { recipe } from '@vanilla-extract/recipes';

export const buttonStyles = recipe({
  base:{
    // The styles applied to all buttons are placed here},
  variants:{
    // The style we choose from here}
});
Copy after login

In base you can declare the styles that will be applied to all variants. In variants , you can provide different ways to customize elements:

 // button.css.ts
import { recipe } from '@vanilla-extract/recipes';
export const buttonStyles = recipe({
  base: {
    fontWeight: "bold",
  },
  variants: {
    color: {
      normal: {
        backgroundColor: "hsl(210deg, 30%, 90%)",
      },
      callToAction: {
        backgroundColor: "hsl(210deg, 80%, 65%)",
      },
    },
    size: {
      Large: {
        padding: 30,
      },
      medium: {
        padding: 15,
      },
    },
  },
});
Copy after login

You can then declare the variant you want to use in the tag:

 // button.ts
import { buttonStyles } from "./button.css";

Click me
Copy after login

vanilla-extract provides automatic completion for your own variant names with TypeScript!

You can name the variants as you like and put any attributes you want in it, for example:

 // button.css.ts
export const buttonStyles = recipe({
  variants: {
    animal: {
      dog: {
        backgroundImage: 'url("./dog.png")',
      },
      cat: {
        backgroundImage: 'url("./cat.png")',
      },
      rabbit: {
        backgroundImage: 'url("./rabbit.png")',
      },
    },
  },
});
Copy after login

You can see this is very useful for building design systems because you can create reusable components and control how they change. These changes become easy to spot with TypeScript – you just need to type CMD/CTRL Space (on most editors) and you get a drop-down list listing different ways to customize your components.

Practical Priority with Sprinkles

Sprinkles is a practical priority framework built on vanilla-extract. The vanilla-extract document describes it like this:

Basically, it's like building your own version of zero runtime, type-safe Tailwind, Styled System, and more.

So if you don't like naming things (we all have nightmares, create an external wrapper div, and then realize we need to wrap it with...external external wrapper), Sprinkles may be your preferred way to use vanilla-extract.

The Sprinkles API also needs to be installed separately:

 npm install @vanilla-extract/sprinkles
Copy after login

Now we can create some building blocks for our practical functions to use. Let's create a color and length list by declaring several objects. The JavaScript key name can be anything we want. The value needs to be a valid CSS value for the CSS attribute we plan to use:

 // sprinkles.css.ts
const colors = {
  blue100: "hsl(210deg, 70%, 15%)",
  blue200: "hsl(210deg, 60%, 25%)",
  blue300: "hsl(210deg, 55%, 35%)",
  blue400: "hsl(210deg, 50%, 45%)",
  blue500: "hsl(210deg, 45%, 55%)",
  blue600: "hsl(210deg, 50%, 65%)",
  blue700: "hsl(207deg, 55%, 75%)",
  blue800: "hsl(205deg, 60%, 80%)",
  blue900: "hsl(203deg, 70%, 85%)",
};

const lengths = {
  small: "4px",
  medium: "8px",
  large: "16px",
  humungous: "64px"
};
Copy after login

We can use the defineProperties function to declare which CSS properties these values ​​will be applied to:

  • Pass it an object containing properties attributes.
  • In properties we declare an object where the keys are CSS properties that the user can set (these need to be valid CSS properties), and the value is the object we created earlier (our list of colors and lengths).
 // sprinkles.css.ts
import { defineProperties } from "@vanilla-extract/sprinkles";

const colors = {
  blue100: "hsl(210deg, 70%, 15%)"
  // etc.
}

const lengths = {
  small: "4px",
  // etc.
}

const properties = defineProperties({
  properties: {
    // The key of this object needs to be a valid CSS attribute // The value is the option we provide to the user color: colors,
    backgroundColor: colors,
    padding: lengths,
  },
});
Copy after login

Then the last step is to pass the return value of defineProperties to the createSprinkles function and export the return value:

 // sprinkles.css.ts
import { defineProperties, createSprinkles } from "@vanilla-extract/sprinkles";

const colors = {
  blue100: "hsl(210deg, 70%, 15%)"
  // etc.
}

const lengths = {
  small: "4px",
  // etc. 
}

const properties = defineProperties({
  properties: {
    color: colors,
    // etc. 
  },
});
export const springles = createSprinkles(properties);
Copy after login

We can then start inline styles within the component by calling the sprinkles function in the class attribute and selecting the option we want for each element.

 // index.ts
import { sprinkles } from "./sprinkles.css";
document.getElementById("root").innerHTML = `Click me
`;
Copy after login

JavaScript output saves a class name string for each style attribute. These class names match a single rule in the output CSS file.

 Click me
Copy after login

As you can see, this API allows you to style elements within a tag using a set of predefined constraints. You can also avoid the difficult task of coming up with a class name for each element. The result is something that feels very much like Tailwind, but also benefits from all the infrastructure built around TypeScript.

The Sprinkles API also allows you to write conditions and abbreviations to create responsive styles using utility classes.

Summarize

vanilla-extract feels like a big advance in the field of CSS tools. A lot of thought was invested in building it into an intuitive, robust styled solution that leverages all the capabilities that static typing offers.

Further reading

  • vanilla-extract Documentation
  • Mark's speech on vanilla-extract
  • vanilla-extract Discord
  • CodeSandbox Code Sample

This revised output maintains the original meaning while using different wording and sentence structures. The images remain in their original format and location.

The above is the detailed content of CSS in TypeScript with vanilla-extract. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template