Fashion is an extension to the vanilla-extract library that makes use of Typescript as a style preprocessor (eg. use Typescript instead of Sass, Less, Postcss, etc...).
By using Typescript as a style preprocessor you gain the major benefit to share data between your Css and your Javascript application. Communicating values between Css and Javascript has always been complicated and hacky. Now you have only one source of truth.
It also has the advantage of bringing the power of Typescript as your preprocessor. Loops, objects, arrays, mappings, etc... everything becomes possible.
But while vanilla-extract had this brightest idea, its api lacked some crucial capacities. That is where Fashion comes to the rescue. Fashion comes with all the power of vanilla-extract, plus:
- it makes possible to apply runtime style with the same syntax as static style (vanilla-extract has no mechanisms for runtime styling except by manipulating css variables),
- simplify the api that was too low-level with too many differents paradigms (use variants like stitches as the default paradigm),
- exposes utilities to easily and safely define your design system,
- Fashion works with its own style object that differs from CSSType (used by vanilla-extract) by adding several features:
- possibility to indicate default unit for sizes (
px
is default but can be rem
), - possibility to indicate a default zoom level that will apply to every every space,
- properties that start with the same name are grouped into an object. Ex:
{ fontSize, fontWeight, fontFamily }
becomes { font: { size, weight, family }}
. It's cleaner make it possible to work directly with Font
or Background
objects for example.
Setup
First, you have to install fashion as well as its peer dependencies:
npm install @digitak/fashion @vanilla-extract/css @vanilla-extract/recipes
And then the right vanilla-extract plugin that will preprocess your .css.ts
files into standard .css
files. The right plugin depends on your bundler (vite, esbuild, webpack, Next.js, etc...).
Follow the plugin installation instructions and you should be all set.
Static styling
Static styling has the best performances you can imagine. No css-in-js library could ever overperform static styling. Your .css.ts
will be preprocessed at build time and transformed into plain css.
Basic usage
- Create a
.css.ts
file that will hold some style:
import { style } from "@digitak/fashion"
export const styled = style({
background: 'red',
color: 'white',
})
- Import your style and apply it to a component:
import { styled } from "./styled.css"
export const Button = <button className={styled()}>
I am a styled button
</button>
Variants
Heavily inspired by stitches, the variants system allows to define variations of a given style.
import { style } from "@digitak/fashion"
export const styled = style({
background: "transparent",
color: "black",
variants: {
color: {
blue: {
background: "blue",
color: "white",
},
red: {
background: "red",
color: "white",
},
},
rounded: {
true: {
borderRadius: 99999
}
}
}
})
import { styled } from "./styled.css"
export const Button = <button className={styled({ color: 'red', rounded: true })}>
I am a styled red button
</button>
You can define as many variants as you want.
Flags variants
If you set a true
and / or a false
field as a variant value, its type will be converted into a boolean.
import { style } from "@digitak/fashion"
export const styled = style({
background: "transparent",
color: "black",
border: {
width: 1,
},
variants: {
rounded: {
true: {
borderRadius: 99999
}
}
}
})
Default variants
You can specify default variants.
import { style } from "@digitak/fashion"
export const styled = style({
background: "transparent",
color: "black",
variants: {
color: {
blue: {
background: "blue",
color: "white",
},
red: {
background: "red",
color: "white",
},
},
},
defaultVariants: {
color: "blue",
}
})
Compound variants
You can also apply additional style when several variants are active together.
import { style } from "@digitak/fashion"
export const styled = style({
background: "transparent",
color: "black",
variants: {
color: {
blue: {
background: "blue",
color: "white",
},
red: {
background: "red",
color: "white",
},
},
rounded: {
true: {
borderRadius: 99999
}
}
},
compoundVariants: [
{
variants: {
color: "blue",
rounded: true,
},
style: {
boxShadow: {
spread: 1,
blur: 1,
color: "blue",
}
},
},
]
})
Global style
You can easily define global style:
import { globalStyle } from '@digitak/fashion';
globalStyle('html, body', {
margin: 0,
})
Dynamic styling
Dynamic styling is great when you have a component with a visual state that can have any values.
import { styleToObject } from "@digitak/fashion/camel"
export const Button = <button style={styleToObject({
background: "red",
color: "white",
})}>
I am a styled button
</button>
Fashion exports functions to transform a style object into:
- a plain css string (kebab-case):
import { styleToString } from "@digitak/fashion/kebab"
, - a plain css object (kebab-case):
import { styleToObject } from "@digitak/fashion/kebab"
, - a plain css string (camel-case):
import { styleToString } from "@digitak/fashion/camel"
, - a plain css object (camel-case):
import { styleToObject } from "@digitak/fashion/camel"
.
⚠️ You cannot use variants, status (like :hover
or :focus
), selectors or media queries with dynamic styling.
Define a design system
Defining a strong design system will ensure your project follow strong design guidelines and will be easily configurable in the future.
Define variables and themes
Themes are a set of different objects that must assign values to the same variables.
For defining themes, one must first define which variables his themes rely on. Your Variables type should only contain values that change when you switch theme. We'll see later how to deal with constant values.
import type { Color } from "@digitak/fashion"
export type Variables = {
colors: {
primary: Color;
secondary: Color;
};
};
const { defineThemes } = defineThemeVariables<Variables>();
Once the types of our variables is set, we can create our themes:
export const { themes, variables } = defineThemes({
light: {
colors: {
primary: "#ff55gg",
secondary: "#gg44dd",
}
},
dark: {
colors: {
primary: "#00ee33",
secondary: "#11dd44",
}
}
})
You will now be able to use your reactive theme this way:
import { style } from "@digitak/fashion"
import { variables } from "./theme.css"
export const appStyle = style({
background: variables.colors.primary
})
import { themes } from "./theme.css"
import { appStyle } from "./app.css"
export const App = ({ children }) => <div className={themes.light} >
<div className={appStyle()}>
{children}
</div>
</div>
Define constant values
Fashion comes with some helper functions to safely define your design system.
An example of full design system would be:
export type Variables = {
colors: {
primary: Color;
secondary: Color;
};
};
const { defineThemes } = defineThemeVariables<Variables>();
export const { themes, variables } = defineThemes({
light: {
colors: {
primary: "#ff55gg",
secondary: "#gg44dd",
}
},
dark: {
colors: {
primary: "#00ee33",
secondary: "#11dd44",
}
}
});
export const colors = defineColors({
...variables.colors,
})
export const spaces = defineSizes({
small: 4,
medium: 8,
large: 24,
extraLarge: 48,
})
export const radiuses = defineSizes({
squared: 0,
small: 4,
medium: 8,
large: 24,
rounded: 99999,
})
export const elevations = defineShadows({
flat: {
blur: 0,
spread: 0,
},
low: {
blur: 1,
spread: 2,
color: "rgba(0, 0, 0, 0.15)",
},
medium: {
blur: 1,
spread: 2,
color: "rgba(0, 0, 0, 0.2)",
},
high: {
blur: 2,
spread: 5,
color: "rgba(0, 0, 0, 0.25)",
}
})
export const { biorhyme } = defineFontFamilies({
biorhyme: [
{
src: "url('/BioRhyme-ExtraBold.ttf')",
fontWeight: "bold",
},
{
src: "url('/BioRhyme-Regular.ttf')",
},
],
});
export const fonts = defineFonts({
title: {
family: biorhyme,
weight: "bold",
}
})
export const medias = defineMedias({
mobile: "(width < 480px)",
tablet: "(480px <= width < 1180px)",
desktop: "(1180px <= width < 1980px)",
huge: "(1980px <= width)",
})