## File: README.md
# π§ vanilla-extract
**Zero-runtime Stylesheets-in-TypeScript.**
Write your styles in TypeScript (or JavaScript) with locally scoped class names and CSS Variables, then generate static CSS files at build time.
Basically, itβs [βCSS Modules](https://github.com/css-modules/css-modules)-in-TypeScriptβ but with scoped CSS Variables + heaps more.
π₯ All styles generated at build time β just like [Sass](https://sass-lang.com), [Less](http://lesscss.org), etc.
β¨ Minimal abstraction over standard CSS.
π¦ Works with any front-end framework β or even without one.
π³ Locally scoped class names β just like [CSS Modules.](https://github.com/css-modules/css-modules)
π Locally scoped [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), `@keyframes` and `@font-face` rules.
π¨ High-level theme system with support for simultaneous themes. No globals!
π Utils for generating variable-based `calc` expressions.
πͺ Type-safe styles via [CSSType.](https://github.com/frenic/csstype)
πββοΈ Optional runtime version for development and testing.
π Optional API for dynamic runtime theming.
---
π [Check out the documentation site for setup guides, examples and API docs.](https://vanilla-extract.style)
---
π₯ [Try it out for yourself in CodeSandbox.](https://codesandbox.io/s/github/vanilla-extract-css/vanilla-extract/tree/master/examples/webpack-react?file=/src/App.css.ts)
---
**Write your styles in `.css.ts` files.**
```ts
// styles.css.ts
import { createTheme, style } from '@vanilla-extract/css';
export const [themeClass, vars] = createTheme({
color: {
brand: 'blue'
},
font: {
body: 'arial'
}
});
export const exampleStyle = style({
backgroundColor: vars.color.brand,
fontFamily: vars.font.body,
color: 'white',
padding: 10
});
```
> π‘ Once you've [configured your build tooling,](https://vanilla-extract.style/documentation/getting-started/) these `.css.ts` files will be evaluated at build time. None of the code in these files will be included in your final bundle. Think of it as using TypeScript as your preprocessor instead of Sass, Less, etc.
**Then consume them in your markup.**
```ts
// app.ts
import { themeClass, exampleStyle } from './styles.css.ts';
document.write(`
`);
```
---
Want to work at a higher level while maximising style re-use? Check out π¨ [Sprinkles](https://vanilla-extract.style/documentation/packages/sprinkles), our official zero-runtime atomic CSS framework, built on top of vanilla-extract.
---
## [Contributing]
[Contributing]: ./CONTRIBUTING.md
## Thanks
- [Nathan Nam Tran](https://twitter.com/naistran) for creating [css-in-js-loader](https://github.com/naistran/css-in-js-loader), which served as the initial starting point for [treat](https://seek-oss.github.io/treat), the precursor to this library.
- [Stitches](https://stitches.dev/) for getting us excited about CSS-Variables-in-JS.
- [SEEK](https://www.seek.com.au) for giving us the space to do interesting work.
## License
MIT.
---
## File: .changeset/README.md
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md)
---
## File: docs/treat-migration-guide.md
# π¬ treat migration guide
π First of all, thanks for using treat, and thanks for your interest in vanilla-extract!
When we first started work on vanilla-extract, it actually began life as `treat-next` β an experiment with replacing our custom webpack-based theming system with CSS Variables. However, we quickly realised that the theming system was too different to carry the same name. That said, they're similar enough that migrating shouldn't be too hard (relative to migrating to another library).
We've made sure that both treat and vanilla-extract can run simultaneously in the same project, so if you have a lot of treat files, don't feel like you have to migrate everything at once!
## New file extension
The file extension has changed from `*.treat.ts` to `*.css.ts`.
## `.css.ts` files can import other `.css.ts` files
This wasn't possible in treat with `*.treat.ts` files. If you've had to work around this limitation in the past, you don't need to worry anymore!
## Your webpack config must handle CSS files
In treat, we automatically handled all generated CSS files for you. With vanilla-extract we've taken a more lightweight approach. Instead, we generate regular global CSS files and assume you've configured webpack to handle this. This makes things a lot simpler, but also a lot more configurable.
For more detail, check out the [webpack setup guide.](https://github.com/vanilla-extract-css/vanilla-extract#webpack)
## Autoprefixer is no longer included
If you want Autoprefixer, you'll need to [manually add it to your webpack config.](https://github.com/webpack-contrib/postcss-loader#autoprefixer)
Note that this also means you have a lot more control over the handling of generated CSS. For example, you might want to use [postcss-preset-env](https://github.com/webpack-contrib/postcss-loader#postcss-preset-env) instead.
## URL handling should be disabled for `*.vanilla.css` files
In treat, we set css-loader's [`url` option](https://webpack.js.org/loaders/css-loader/#url) to `false`. This was to ensure that JavaScript import statements were always used for assets (e.g. `import logoUrl from './logo.png'`) rather than allowing the CSS to create implicit imports (e.g. `background: "url('./logo.png')"`).
If you want to reinstate treat's approach to asset imports without affecting other CSS files, you can configure css-loader separately for `*.vanilla.css` files. For example:
```ts
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
oneOf: [
{
test: /\.vanilla\.css$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { url: false }
}
]
},
{
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
}
]
},
plugins: [
new VanillaExtractPlugin(),
new MiniCssExtractPlugin()
]
};
```
## `createTheme`
If you only have a single theme, you can keep things simple by using `createGlobalTheme` and targeting `:root`. If you do this, your variables will just work without having to wire anything up to the document.
```ts
// vars.css.ts
import { createGlobalTheme } from '@vanilla-extract/css';
export const vars = createGlobalTheme(':root', {
...tokens
});
```
If you have multiple themes, or if you want to avoid the global scope, use the [`createTheme`](https://github.com/vanilla-extract-css/vanilla-extract#createtheme) function instead.
```ts
// vars.css.ts
import { createTheme } from '@vanilla-extract/css';
export const [themeA, vars] = createTheme({
...tokens
});
export const themeB = createTheme(vars, {
...tokens
});
```
If you're bundle-splitting your themes, you'll probably want the [`createThemeContract`](https://github.com/vanilla-extract-css/vanilla-extract#createthemecontract) function.
## `TreatProvider`
> π‘ This isn't required if you only have a single global theme set up via `createGlobalTheme`.
You no longer need React bindings to switch themes at runtime since we're just using standard CSS Variables. Instead of using something like `TreatProvider` at the root of your app, you need to attach your theme class to an element instead.
```tsx
// App.ts
import { themeClass } from './vars.css';
export const App = () => (
...
);
```
## Theme tokens must be strings
To avoid errors when migrating unitless numbers to CSS Variables, numbers are no longer accepted as theme token values. This forces you to be explicit about `px` units, or lack thereof, since we can't know ahead of time where the variable will be used. Think of it as writing a CSS string rather than a JavaScript value since this goes directly into the generated CSS Variable definition.
```diff
const themeClass = createGlobalTheme(':root', {
- grid: 4,
+ grid: '4px',
});
```
If you need a unitless number, just convert it to a string as-is.
```diff
const themeClass = createGlobalTheme(':root', {
- headingWeight: 600,
+ headingWeight: '600',
});
```
## Theme classes must be forwarded through React portals
> π‘ This isn't required if you only have a single global theme set up via `createGlobalTheme`.
CSS Variables don't follow the rules of React context, which means that theming won't automatically work when [rendering to a portal.](https://reactjs.org/docs/portals.html) To handle this, you'll need to ensure your theme class is available on context so you can access it when needed.
As a basic example, let's set up a `VanillaThemeContext`.
```tsx
// VanillaThemeContext.tsx
import { createContext, useContext } from 'react';
const VanillaThemeContext = createContext(
null
);
export const VanillaThemeProvider =
VanillaThemeContext.Provider;
export const useVanillaTheme = () => {
const themeClass = useContext(VanillaThemeContext);
if (themeClass === null) {
throw new Error('Must be inside VanillaThemeProvider');
}
return themeClass;
};
```
We can then use this `VanillaThemeProvider` at the root of our app.
```tsx
// App.tsx
import { createContext, useContext } from 'react';
import { themeClass } from './vars.css';
import { VanillaThemeProvider } from './VanillaThemeContext';
export const App = () => (
...
);
```
We can now access the theme via our custom `useVanillaTheme` Hook and apply it to the root element of our portal.
```tsx
// MyPortalComponent.tsx
import { createPortal } from 'react-dom';
import { useVanillaTheme } from './VanillaThemeContext';
export const MyPortalComponent = () => {
const themeClass = useVanillaTheme();
return createPortal(
...,
document.body
);
};
```
## `useStyles` / `resolveStyles`
The whole concept of `styleRefs` and `useStyles`/`resolveStyles` goes away. No more plumbing required, just access the CSS exports directly.
```diff
-import { useStyles } from 'react-treat';
-import * as styleRefs from './styles.treat';
+import * as styles from './styles.css';
export const Foo = () => {
- const styles = useStyles(styleRefs);
return (
...
);
}
```
## `style`
Since theme variables are now managed by the browser, the `style` function no longer accepts a theme callback.
To get access to variables, we now import theme variables from the `.css.ts` file that defines them.
```diff
-import { style } from 'treat';
-
-const className = style(theme => ({
- paddingTop: theme.space.small
-}))
+import { style } from '@vanilla-extract/css';
+import { vars } from '../vars.css';
+
+export const className = style({
+ paddingTop: vars.space.small
+});
```
Note that this means the theme is no longer global! You don't need to worry about setting up [global theme types,](https://seek-oss.github.io/treat/data-types#theme) and you can run multiple sets of theme variables in parallel.
## Style calculations
> **β οΈ When it comes to inline style calculations, CSS Variables are inherently more limited than treat's theming system. This is because you can only use standard CSS functions rather than being able to execute arbitrary JavaScript code.**
>
> If your app only has a single theme and you think this change is too limiting, you might want to consider avoiding CSS Variables entirely and using a shared object of constants and calculating all theme-based styles at build time instead.
Theme variables are now opaque CSS Variables (i.e. `"var(--g7vce91)"`) rather than actual token values that differ per theme, which means you can't perform JavaScript calculations on them.
Simple calculations (addition, subtraction, multiplication, division) are covered by CSS's `calc` function. To make this a bit easier in TypeScript, we provide a [`calc`](https://github.com/vanilla-extract-css/vanilla-extract#calc) function in the `@vanilla-extract/css-utils` package.
```diff
-import { style } from 'treat';
-
-const className = style(theme => ({
- marginTop: theme.space.small * -1
-}))
+import { style } from '@vanilla-extract/css';
+import { calc } from '@vanilla-extract/css-utils';
+import { vars } from '../vars.css';
+
+const className = style({
+ marginTop: calc.negate(vars.space.small)
+});
```
If you're doing anything more advanced with theme variables that the browser doesn't natively support (e.g. rounding numbers, modifying colours), you'll need to hoist this logic into your theme as CSS Variables.
For example, let's assume you've calculated a lighter colour variant inline using [Polished.](https://polished.js.org/)
```ts
import { style } from 'treat';
import { lighten } from 'polished';
export const className = style((theme) => ({
background: lighten(0.2, theme.color.brand)
}));
```
Since this calculation is not yet supported natively in CSS, this lighter background would need to become part of your theme definition. In this case, we'll introduce a new `color` variable called `brandLight`. Notice that in this context we're able to execute arbitrary JavaScript code.
```ts
// vars.css.ts
import { createGlobalTheme } from '@vanilla-extract/css';
import { lighten } from 'polished';
const brandColor = 'blue';
export const vars = createGlobalTheme(':root', {
color: {
brand: brandColor,
brandLight: lighten(0.2, brandColor)
}
});
```
You would then update your styles to use this new CSS Variable instead.
```diff
-import { style } from 'treat';
-import { lighten } from 'polished';
+import { style } from '@vanilla-extract/css';
+import { vars } from '../vars.css';
-export const className = style(theme => ({
- background: lighten(0.2, theme.color.brand)
-}));
+export const className = style({
+ background: vars.color.brandLight
+});
```
## `styleMap`
You can use [`styleVariants`](https://github.com/vanilla-extract-css/vanilla-extract#stylevariants) as a drop-in replacement. Note that it now accepts a map function as the second argument, so there may be some opportunities to simplify your code if you were mapping over objects before passing them to `styleMap`.
## `styleTree`
Since you now have direct access to theme objects outside of a style block, this function is no longer necessary.
## `@keyframes`
The `@keyframes` property is no longer supported on style objects. Instead, you should create keyframes separately with the `keyframes` function.
```diff
-import { style } from 'treat';
-
-const className = style({
- '@keyframes': { ... },
- animationName: '@keyframes'
-});
+import { keyframes, style } from '@vanilla-extract/css';
+const myAnimationName = keyframes({ ... });
+
+const className = style({
+ animationName: myAnimationName
+});
```
## Did we forget anything?
[Please let us know.](https://github.com/vanilla-extract-css/vanilla-extract/issues/new)
---
## File: packages/sprinkles/README.md
# π¨ Sprinkles
**Zero-runtime atomic CSS framework for [vanilla-extract.](https://github.com/vanilla-extract-css/vanilla-extract)**
Generate a static set of custom utility classes and compose them either statically at build time, or dynamically at runtime, without the usual style generation overhead of CSS-in-JS.
Basically, itβs like building your own zero-runtime, type-safe version of [Tailwind](https://tailwindcss.com), [Styled System](https://styled-system.com), etc.
---
**Compose sprinkles statically at build time.**
```ts
// styles.css.ts
export const className = sprinkles({
display: 'flex',
paddingX: 'small',
flexDirection: {
mobile: 'column',
desktop: 'row'
},
background: {
lightMode: 'blue-50',
darkMode: 'gray-700'
}
});
```
**Or compose them dynamically at runtime! πββοΈ**
```ts
// app.ts
import { sprinkles } from './sprinkles.css.ts';
const flexDirection = Math.random() > 0.5 ? 'column' : 'row';
document.write(`
`);
```
---
π₯ Zero-runtime CSS-in-TypeScript with all styles generated at build time via [vanilla-extract.](https://vanilla-extract.style)
π Create your own custom set of atomic classes with declarative config.
πͺ Type-safe functional API for accessing sprinkles.
πββοΈ Compose sprinkles statically in `.css.ts` files, or dynamically at runtime (<0.5KB Gzip)
π¨ Generate theme-based scales with CSS Variables using [vanilla-extract themes.](https://vanilla-extract.style/documentation/api/create-theme)
βοΈ Configure shorthands for common property combinations, e.g. `paddingX` / `paddingY`.
π¦ Conditional sprinkles to target media/feature queries and selectors.
β¨ Scope conditions to individual properties.
---
π₯ [Try it out for yourself in CodeSandbox.](https://codesandbox.io/s/github/vanilla-extract-css/vanilla-extract/tree/master/examples/webpack-react?file=/src/sprinkles.css.ts)
---
## Setup
> π‘ Before starting, ensure you've set up [vanilla-extract.](https://github.com/vanilla-extract-css/vanilla-extract)
Install Sprinkles.
```bash
$ npm install @vanilla-extract/sprinkles
```
Create a `sprinkles.css.ts` file, then configure and export your `sprinkles` function.
> π‘ This is just an example! Feel free to customise properties, values and conditions to match your requirements.
```ts
// sprinkles.css.ts
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
const space = {
'none': 0,
'small': '4px',
'medium': '8px',
'large': '16px',
// etc.
};
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile',
properties: {
display: ['none', 'flex', 'block', 'inline'],
flexDirection: ['row', 'column'],
justifyContent: ['stretch', 'flex-start', 'center', 'flex-end', 'space-around', 'space-between'],
alignItems: ['stretch', 'flex-start', 'center', 'flex-end'],
paddingTop: space,
paddingBottom: space,
paddingLeft: space,
paddingRight: space,
// etc.
},
shorthands: {
padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
paddingX: ['paddingLeft', 'paddingRight'],
paddingY: ['paddingTop', 'paddingBottom'],
placeItems: ['justifyContent', 'alignItems'],
}
});
const colors = {
'blue-50': '#eff6ff',
'blue-100': '#dbeafe',
'blue-200': '#bfdbfe',
'gray-700': '#374151',
'gray-800': '#1f2937',
'gray-900': '#111827',
// etc.
};
const colorProperties = defineProperties({
conditions: {
lightMode: {},
darkMode: { '@media': '(prefers-color-scheme: dark)' }
},
defaultCondition: 'lightMode',
properties: {
color: colors,
background: colors,
// etc.
}
});
export const sprinkles = createSprinkles(responsiveProperties, colorProperties);
// It's a good idea to export the Sprinkles type too
export type Sprinkles = Parameters[0];
```
**π That's it β youβre ready to go!**
## Usage
You can now use your `sprinkles` function in `.css.ts` files for zero-runtime usage.
```ts
// styles.css.ts
import { sprinkles } from './sprinkles.css.ts';
export const container = sprinkles({
display: 'flex',
paddingX: 'small',
// Conditional sprinkles:
flexDirection: {
mobile: 'column',
desktop: 'row',
},
background: {
lightMode: 'blue-50',
darkMode: 'gray-700',
}
});
```
If you want, you can even use your `sprinkles` function at runtime! πββοΈ
```tsx
// app.ts
import { sprinkles } from './sprinkles.css.ts';
const flexDirection = Math.random() > 0.5 ? 'column' : 'row';
document.write(`
`);
```
> π‘ Although you donβt need to use this library at runtime, itβs designed to be as small and performant as possible. The runtime is only used to look up pre-existing class names. All styles are still generated at build time!
Within `.css.ts` files, combine with any custom styles by providing an array to vanilla-extractβs [`style`](https://vanilla-extract.style/documentation/api/style) function.
```ts
// styles.css.ts
import { style } from '@vanilla-extract/css';
import { sprinkles } from './sprinkles.css.ts';
export const container = style([
sprinkles({
display: 'flex',
padding: 'small'
}),
{
':hover': {
outline: '2px solid currentColor'
}
}
]);
```
Sprinkles uses this internally, which means that a class list returned by `sprinkles` can be treated as if it were a single class within vanilla-extract selectors.
```ts
// styles.css.ts
import { globalStyle } from '@vanilla-extract/css';
import { sprinkles } from './sprinkles.css.ts';
export const container = sprinkles({
padding: 'small'
});
globalStyle(`${container} *`, {
boxSizing: 'border-box'
});
```
---
βοΈ Using React? Turn your sprinkles into a `` component with π° [Dessert Box.](https://github.com/TheMightyPenguin/dessert-box)
---
- [API](#api)
- [defineProperties](#defineproperties)
- [`properties`](#properties)
- [`shorthands`](#shorthands)
- [`conditions`](#conditions)
- [`defaultCondition`](#defaultcondition)
- [`responsiveArray`](#responsivearray)
- [createSprinkles](#createsprinkles)
- [Utilities](#utilities)
- [createMapValueFn](#createmapvaluefn)
- [createNormalizeValueFn](#createnormalizevaluefn)
- [Types](#types)
- [ConditionalValue](#conditionalvalue)
- [RequiredConditionalValue](#requiredconditionalvalue)
- [Thanks](#thanks)
- [License](#license)
---
## API
### defineProperties
Defines a collection of utility classes with [properties](#properties), [conditions](#conditions) and [shorthands.](#shorthands)
If you need to scope different conditions to different properties (e.g. some properties support breakpoints, some support light mode and dark mode, some are unconditional), you can provide as many collections of properties to [`createSprinkles`](#createsprinkles) as you like.
```ts
import {
defineProperties,
createSprinkles
} from '@vanilla-extract/sprinkles';
const space = {
none: 0,
small: '4px',
medium: '8px',
large: '16px'
};
const colors = {
blue50: '#eff6ff',
blue100: '#dbeafe',
blue200: '#bfdbfe'
// etc.
};
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile',
properties: {
display: ['none', 'block', 'flex'],
flexDirection: ['row', 'column'],
padding: space
// etc.
}
});
const colorProperties = defineProperties({
conditions: {
lightMode: { '@media': '(prefers-color-scheme: light)' },
darkMode: { '@media': '(prefers-color-scheme: dark)' }
},
defaultCondition: false,
properties: {
color: colors,
background: colors
}
// etc.
});
export const sprinkles = createSprinkles(
responsiveProperties,
colorProperties
);
```
> π‘ If you want a good color palette to work with, you might want to consider importing [`tailwindcss/colors.`](https://tailwindcss.com/docs/customizing-colors#color-palette-reference)
#### `properties`
Define which CSS properties and values should be available.
For simple mappings (i.e. valid CSS values), values can be provided as an array.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
properties: {
display: ['none', 'block', 'flex'],
flexDirection: ['row', 'column'],
alignItems: [
'stretch',
'flex-start',
'center',
'flex-end'
],
justifyContent: [
'stretch',
'flex-start',
'center',
'flex-end'
]
// etc.
}
});
```
For semantic mappings (e.g. space scales, color palettes), values can be provided as an object.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
properties: {
gap: {
none: 0,
small: 4,
medium: 8,
large: 16
}
// etc.
}
});
```
You can also use [vanilla-extract themes](/documentation/api/create-theme) to configure themed values.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
import { vars } from './vars.css.ts';
const responsiveProperties = defineProperties({
properties: {
gap: vars.space
// etc.
}
});
```
For more complicated scenarios, values can even be entire style objects. This works especially well when combined with CSS Variables.
> π‘ Styles are created in the order that they were defined in your config. Properties that are less specific should be higher in the list.
```ts
import { createVar } from '@vanilla-extract/css';
import { defineProperties } from '@vanilla-extract/sprinkles';
const alpha = createVar();
const responsiveProperties = defineProperties({
properties: {
background: {
red: {
vars: { [alpha]: '1' },
background: `rgba(255, 0, 0, ${alpha})`
}
},
backgroundOpacity: {
1: { vars: { [alpha]: '1' } },
0.1: { vars: { [alpha]: '0.1' } }
}
// etc.
}
});
```
#### `shorthands`
Maps custom shorthand properties to multiple underlying CSS properties. This is useful for mapping values like `padding`/`paddingX`/`paddingY` to their underlying longhand values.
> π‘ Shorthands are evaluated in the order that they were defined in your configuration. Shorthands that are less specific should be higher in the list, e.g. `padding` should come before `paddingX`/`paddingY`.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
import { vars } from './vars.css.ts';
const responsiveProperties = defineProperties({
properties: {
paddingTop: vars.space,
paddingBottom: vars.space,
paddingLeft: vars.space,
paddingRight: vars.space
},
shorthands: {
padding: [
'paddingTop',
'paddingBottom',
'paddingLeft',
'paddingRight'
],
paddingX: ['paddingLeft', 'paddingRight'],
paddingY: ['paddingTop', 'paddingBottom']
}
});
```
#### `conditions`
Define a set of media/feature queries for the provided properties.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile'
// etc.
});
```
Properties can also be scoped to selectors.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const properties = defineProperties({
conditions: {
default: {},
hover: { selector: '&:hover' },
focus: { selector: '&:focus' }
},
defaultCondition: 'default'
// etc.
});
```
#### `defaultCondition`
Defines which condition(s) should be used when a non-conditional value is requested, e.g. `sprinkles({ display: 'flex' })`.
If you're using mobile-first responsive conditions, this should be your lowest breakpoint.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile'
// etc.
});
```
If your conditions are mutually exclusive (e.g. light mode and dark mode), you can provide an array of default conditions. For example, the following configuration would automatically expand `sprinkles({ background: 'white' })` to the equivalent of `sprinkles({ background: { lightMode: 'white', darkMode: 'white' }})`.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
conditions: {
lightMode: { '@media': '(prefers-color-scheme: light)' },
darkMode: { '@media': '(prefers-color-scheme: dark)' }
},
defaultCondition: ['lightMode', 'darkMode']
// etc.
});
```
You can also set `defaultCondition` to `false`, which forces you to be explicit about which conditions youβre targeting.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
conditions: {
lightMode: {
'@media': '(prefers-color-scheme: light)'
},
darkMode: { '@media': '(prefers-color-scheme: dark)' }
},
defaultCondition: false
// etc.
});
```
#### `responsiveArray`
Providing an array of condition names enables the responsive array notation (e.g. `['column', 'row']`) by defining the order of conditions.
```ts
import { defineProperties } from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
conditions: {
mobile: {},
tablet: { '@media': 'screen and (min-width: 768px)' },
desktop: { '@media': 'screen and (min-width: 1024px)' }
},
defaultCondition: 'mobile',
responsiveArray: ['mobile', 'tablet', 'desktop']
// etc.
});
```
### createSprinkles
Creates a type-safe function for accessing your [defined properties](#defineProperties). You can provide as many collections of properties as you like.
```ts
import {
defineProperties,
createSprinkles
} from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
/* ... */
});
const unconditionalProperties = defineProperties({
/* ... */
});
const colorProperties = defineProperties({
/* ... */
});
export const sprinkles = createSprinkles(
responsiveProperties,
unconditionalProperties,
colorProperties
);
```
The sprinkles function also exposes a static `properties` key that lets you check whether a given property can be handled by the function.
```ts
sprinkles.properties.has('paddingX');
// -> boolean
```
> π‘ This is useful when building a Box component with sprinkles available at the top level (e.g. ``) since youβll need some way to filter sprinkle props from non-sprinkle props.
## Utilities
### createMapValueFn
Creates a function for mapping over conditional values.
> π‘ This is useful for converting high-level prop values to low-level sprinkles, e.g. converting left/right to flex-start/end.
This function should be created and exported from your `sprinkles.css.ts` file using the conditions from your defined properties.
You can name the generated function whatever you like, typically based on the name of your conditions.
```ts
import {
defineProperties,
createSprinkles,
createMapValueFn
} from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
/* ... */
});
export const sprinkles = createSprinkles(
responsiveProperties
);
export const mapResponsiveValue = createMapValueFn(
responsiveProperties
);
```
You can then import the generated function in your app code.
```ts
import { mapResponsiveValue } from './sprinkles.css.ts';
const alignToFlexAlign = {
left: 'flex-start',
center: 'center',
right: 'flex-end',
stretch: 'stretch'
} as const;
mapResponsiveValue(
'left',
(value) => alignToFlexAlign[value]
);
// -> 'flex-start'
mapResponsiveValue(
{
mobile: 'center',
desktop: 'left'
} as const,
(value) => alignToFlexAlign[value]
);
// -> { mobile: 'center', desktop: 'flex-start' }
mapResponsiveValue(
['center', null, 'left'] as const,
(value) => alignToFlexAlign[value]
);
// -> { mobile: 'center', desktop: 'flex-start' }
```
> π‘ You can generate a custom conditional value type with the [ConditionalValue](#conditionalvalue) type.
### createNormalizeValueFn
Creates a function for normalizing conditional values into a consistent object structure. Any primitive values or responsive arrays will be converted to conditional objects.
This function should be created and exported from your `sprinkles.css.ts` file using the conditions from your defined properties.
> π‘ You can name the generated function whatever you like, typically based on the name of your conditions.
```ts
import {
defineProperties,
createSprinkles,
createNormalizeValueFn
} from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
/* ... */
});
export const sprinkles = createSprinkles(
responsiveProperties
);
export const normalizeResponsiveValue =
createNormalizeValueFn(responsiveProperties);
```
You can then import the generated function in your app code.
```ts
import { normalizeResponsiveValue } from './sprinkles.css.ts';
normalizeResponsiveValue('block');
// -> { mobile: 'block' }
normalizeResponsiveValue(['none', null, 'block']);
// -> { mobile: 'block', desktop: 'block' }
normalizeResponsiveValue({
mobile: 'none',
desktop: 'block'
});
// -> { mobile: 'block', desktop: 'block' }
```
## Types
### ConditionalValue
Creates a custom conditional value type.
> π‘ This is useful for typing high-level prop values that are [mapped to low-level sprinkles,](#createmapvaluefn) e.g. supporting left/right prop values that map to flex-start/end.
This type should be created and exported from your `sprinkles.css.ts` file using the conditions from your defined properties.
> π‘ You can name the generated type whatever you like, typically based on the name of your conditions.
```ts
import {
defineProperties,
ConditionalValue
} from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
/* ... */
});
export type ResponsiveValue =
ConditionalValue;
```
You can then import the generated type in your app code.
```ts
import { ResponsiveValue } from './sprinkles.css.ts';
type ResponsiveAlign = ResponsiveValue<
'left' | 'center' | 'right'
>;
const a: ResponsiveAlign = 'left';
const b: ResponsiveAlign = {
mobile: 'center',
desktop: 'left'
};
const c: ResponsiveAlign = ['center', null, 'left'];
```
### RequiredConditionalValue
Same as [ConditionalValue](#conditionalvalue) except the default condition is required. For example, if your default condition was `'mobile'`, then a conditional value of `{ desktop: '...' }` would be a type error.
```ts
import {
defineProperties,
RequiredConditionalValue
} from '@vanilla-extract/sprinkles';
const responsiveProperties = defineProperties({
defaultCondition: 'mobile'
// etc.
});
export type RequiredResponsiveValue<
Value extends string | number
> = RequiredConditionalValue<
typeof responsiveProperties,
Value
>;
```
You can then import the generated type in your app code.
```ts
import { RequiredResponsiveValue } from './sprinkles.css.ts';
type ResponsiveAlign = RequiredResponsiveValue<
'left' | 'center' | 'right'
>;
const a: ResponsiveAlign = 'left';
const b: ResponsiveAlign = {
mobile: 'center',
desktop: 'left'
};
const c: ResponsiveAlign = ['center', null, 'left'];
// Type errors:
const d: ResponsiveAlign = [null, 'center'];
const e: ResponsiveAlign = { desktop: 'center' };
```
---
## Thanks
- [Styled System](https://styled-system.com) for inspiring our approach to responsive props.
- [Tailwind](https://tailwindcss.com) for teaching us to think utility-first.
- [SEEK](https://www.seek.com.au) for giving us the space to do interesting work.
## License
MIT.
---
## File: packages/integration/README.md
# @vanilla-extract/integration
This package is not intended for public consumption.
---
## File: packages/compiler/README.md
# @vanilla-extract/compiler
This package is not intended for public consumption.
---
## File: site/docs/packages/css-utils.md
---
title: CSS Utils
parent: packages
---
# CSS Utils
An optional package providing utility functions that make it easier to work with CSS in TypeScript.
```bash
npm install @vanilla-extract/css-utils
```
This package is not limited to vanilla-extractβit can be used with any CSS-in-JS library.
## calc
Streamlines the creation of CSS calc expressions.
### Simple expressions
```tsx
import { calc } from '@vanilla-extract/css-utils';
const styles = {
height: calc.multiply('var(--grid-unit)', 2)
};
```
The following functions are available.
- `calc.add`
- `calc.subtract`
- `calc.multiply`
- `calc.divide`
- `calc.negate`
### Chainable expressions
The `calc` export is also a function, providing a chainable API for complex calc expressions.
> When using expression chains it is necessary to call `toString()` to return the constructed expression as the final value.
```tsx
import { calc } from '@vanilla-extract/css-utils';
const styles = {
marginTop: calc('var(--space-large)')
.divide(2)
.negate()
.toString()
};
```
---
## File: site/docs/packages/dynamic.md
---
title: Dynamic
parent: packages
---
# Dynamic
A tiny ([< 1kB compressed](https://bundlephobia.com/package/@vanilla-extract/dynamic@2.0.2)) runtime for performing dynamic updates to scoped theme variables.
```bash
npm install @vanilla-extract/dynamic
```
## assignInlineVars
Allows variables to be assigned dynamically that have been created using vanilla-extract APIs, e.g. `createVar`, `createTheme`, etc.
As these APIs produce variable references that contain the CSS var function, e.g. `var(--brandColor__8uideo0)`, it is necessary to remove the wrapping function when setting its value.
Variables with a value of `null` or `undefined` will be omitted from the resulting inline style.
> π§ `null` and `undefined` values can only be passed to `assignInlineVars` if a theme contract is not provided
```tsx compiled
// app.tsx
import { assignInlineVars } from '@vanilla-extract/dynamic';
import {
container,
brandColor,
textColor
} from './styles.css.ts';
// If `tone` is `undefined`, the following inline style becomes:
// { '--brandColor__8uideo0': 'pink' }
const MyComponent = ({ tone }: { tone?: critical }) => (
);
// styles.css.ts
import { createVar, style } from '@vanilla-extract/css';
export const brandColor = createVar();
export const textColor = createVar();
export const container = style({
background: brandColor,
color: textColor
});
```
Even though this function returns an object of inline styles, it implements the `toString` method, returning a valid `style` attribute value so that it can be used in string templates.
```ts
// app.ts
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { container, brandColor } from './styles.css.ts';
// The following inline style becomes:
// "--brandColor__8uideo0: pink;"
document.write(`
`);
```
### Assigning theme contracts dynamically
[Theme contracts](/documentation/theming/) can also be assigned dynamically by passing one as the first argument.
All variables must be assigned or itβs a type error.
This API makes the concept of dynamic theming much simpler.
```tsx compiled
// app.tsx
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { container, themeVars } from './theme.css.ts';
interface ContainerProps {
brandColor: string;
fontFamily: string;
}
const Container = ({
brandColor,
fontFamily
}: ContainerProps) => (
);
const App = () => (
...
);
// theme.css.ts
import {
createThemeContract,
style
} from '@vanilla-extract/css';
export const themeVars = createThemeContract({
color: {
brand: null
},
font: {
body: null
}
});
export const container = style({
background: themeVars.color.brand,
fontFamily: themeVars.font.body
});
```
## setElementVars
An imperative API, allowing variables created using vanilla-extract APIs, e.g. `createVar`, `createTheme`, etc, to be assigned dynamically on a DOM element.
Variables with a value of `null` or `undefined` will not be assigned a value.
> π§ `null` and `undefined` values can only be passed to `setElementVars` if a theme contract is not provided
```ts compiled
// app.ts
import { setElementVars } from '@vanilla-extract/dynamic';
import { brandColor, textColor } from './styles.css.ts';
const el = document.getElementById('myElement');
setElementVars(el, {
[brandColor]: 'pink',
[textColor]: null
});
// styles.css.ts
import { createVar, style } from '@vanilla-extract/css';
export const brandColor = createVar();
export const textColor = createVar();
```
### Setting theme contracts dynamically
[Theme contracts](/documentation/theming/) can also be set dynamically by passing one as the second argument.
All variables must be assigned or itβs a type error.
```ts compiled
// app.ts
import { setElementVars } from '@vanilla-extract/dynamic';
import { themeVars } from './theme.css.ts';
const el = document.getElementById('myElement');
setElementVars(el, themeVars, {
color: { brand: 'pink' },
font: { body: 'Arial' }
});
// theme.css.ts
import { createThemeContract } from '@vanilla-extract/css';
export const themeVars = createThemeContract({
color: {
brand: null
},
font: {
body: null
}
});
```
---
## File: site/docs/packages/recipes.md
---
title: Recipes
parent: packages
---
# Recipes
Create multi-variant styles with a type-safe runtime API, heavily inspired by [Stitches](https://stitches.dev).
As with the rest of vanilla-extract, all styles are generated at build time.
> π‘ Recipes is an optional package built on top of vanilla-extract using its [function serialization API.](/documentation/api/add-function-serializer) It doesn't have privileged access to vanilla-extract internals so you're also free to build alternative implementations.
## Setup
```bash
npm install @vanilla-extract/recipes
```
## recipe
Creates a multi-variant style function that can be used at runtime or statically in `.css.ts` files.
Accepts an optional set of `base` styles, `variants`, `compoundVariants` and `defaultVariants`.
```ts compiled
// button.css.ts
import { recipe } from '@vanilla-extract/recipes';
export const button = recipe({
base: {
borderRadius: 6
},
variants: {
color: {
neutral: { background: 'whitesmoke' },
brand: { background: 'blueviolet' },
accent: { background: 'slateblue' }
},
size: {
small: { padding: 12 },
medium: { padding: 16 },
large: { padding: 24 }
},
rounded: {
true: { borderRadius: 999 }
}
},
// Applied when multiple variants are set at once
compoundVariants: [
{
variants: {
color: 'neutral',
size: 'large'
},
style: {
background: 'ghostwhite'
}
}
],
defaultVariants: {
color: 'accent',
size: 'medium'
}
});
```
With this recipe configured, you can now use it in your templates.
```ts
// app.ts
import { button } from './button.css.ts';
document.write(`
Hello world
`);
```
Your recipe configuration can also make use of existing variables, classes and styles.
For example, you can pass in the result of your [`sprinkles`](/documentation/packages/sprinkles) function directly.
```ts
// button.css.ts
import { recipe } from '@vanilla-extract/recipes';
import { reset } from './reset.css.ts';
import { sprinkles } from './sprinkles.css.ts';
export const button = recipe({
base: [reset, sprinkles({ borderRadius: 'round' })],
variants: {
color: {
neutral: sprinkles({ background: 'neutral' }),
brand: sprinkles({ background: 'brand' }),
accent: sprinkles({ background: 'accent' })
},
size: {
small: sprinkles({ padding: 'small' }),
medium: sprinkles({ padding: 'medium' }),
large: sprinkles({ padding: 'large' })
}
},
defaultVariants: {
color: 'accent',
size: 'medium'
}
});
```
The recipes function also exposes an array property `variants` that includes all the variants from your recipe.
```ts
button.variants();
// -> ['color', 'size']
```
## Recipe class name selection
Recipes function exposes internal class names in `classNames` property.
The property has two predefined props: `base` and `variants`. The `base` prop includes base class name. It is always defined even if you do not have any base styles. The `variants` prop includes class names for each defined variant.
```ts
// app.css.ts
console.log(button.classNames.base);
// -> app_button__129pj250
console.log(button.classNames.variants.color.neutral);
// -> app_button_color_neutral__129pj251
console.log(button.classNames.variants.size.small);
// -> app_button_size_small__129pj254
```
## RecipeVariants
A utility to make use of the recipeβs type interface. This can be useful when typing functions or component props that need to accept recipe values as part of their interface.
```ts
// button.css.ts
import {
recipe,
RecipeVariants
} from '@vanilla-extract/recipes';
export const button = recipe({
variants: {
color: {
neutral: { background: 'whitesmoke' },
brand: { background: 'blueviolet' },
accent: { background: 'slateblue' }
},
size: {
small: { padding: 12 },
medium: { padding: 16 },
large: { padding: 24 }
}
}
});
// Get the type
export type ButtonVariants = RecipeVariants;
// the above will result in a type equivalent to:
export type ButtonVariants = {
color?: 'neutral' | 'brand' | 'accent';
size?: 'small' | 'medium' | 'large';
};
```