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:
There are a lot of clever innovations in vanilla-extract, which I think makes it meaningful.
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.
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.
vanilla-extract provides first-party integration for all the latest packaging tools. Here is a complete list of integrations it currently supports:
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.
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, });
// title.ts import {titleStyle} from "./title.css"; document.getElementById("root").innerHTML = `<h1> Vanilla Extract</h1> `;
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%)" }
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" }} });
// title.ts import {titleStyle,innerSpan} from "./title.css"; document.getElementById("root").innerHTML = `<h1> Vanilla Extract</h1> Unstyled`;
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.
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, });
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, });
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>`
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; }
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; }
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.
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
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} });
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, }, }, }, });
You can then declare the variant you want to use in the tag:
// button.ts import { buttonStyles } from "./button.css"; Click me
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")', }, }, }, });
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.
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
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" };
We can use the defineProperties
function to declare which CSS properties these values will be applied to:
properties
attributes.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, }, });
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);
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 `;
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
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.
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.
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!