Apply global CSS styles based on @vanilla-extract/recipes variant classes — style children and siblings by variant.
Vanilla Extract by default only allows selectors that target the element itself. To style other elements (children, siblings), you must use globalStyle with interpolated class selectors:
// Without global-recipes: verbose and hard to maintain
const button = recipe({
variants: {
size: {
sm: { fontSize: "14px" },
md: { fontSize: "16px" },
lg: { fontSize: "18px" },
},
},
});
// Each variant needs its own globalStyle call
globalStyle(`${button.classNames.variants.size.sm} svg`, { width: "16px", height: "16px" });
globalStyle(`${button.classNames.variants.size.md} svg`, { width: "20px", height: "20px" });
globalStyle(`${button.classNames.variants.size.lg} svg`, { width: "24px", height: "24px" });
// ...and this grows with every variant combinationWhile this approach works, .css.ts files quickly become bloated with many globalStyle calls — especially when dealing with multiple variants, states, and targets.
global-recipes solves this by letting you define these globalStyle rules in the same declarative way as recipe, providing better readability and maintainability:
// With global-recipes: declarative and scalable
globalRecipe({
recipe: button,
selectorGenerators: { "&": (v) => `${v} svg` },
compoundVariants: [
{ variants: { size: "sm" }, style: { width: "16px", height: "16px" } },
{ variants: { size: "md" }, style: { width: "20px", height: "20px" } },
{ variants: { size: "lg" }, style: { width: "24px", height: "24px" } },
],
});npm install global-recipes @vanilla-extract/css @vanilla-extract/recipesyarn add global-recipes @vanilla-extract/css @vanilla-extract/recipespnpm add global-recipes @vanilla-extract/css @vanilla-extract/recipesbun add global-recipes @vanilla-extract/css @vanilla-extract/recipes// button.css.ts
import { recipe } from "@vanilla-extract/recipes";
import { globalRecipe } from "global-recipes";
const buttonStyle = recipe({
base: { padding: "8px 16px" },
variants: {
size: {
sm: { fontSize: "14px" },
md: { fontSize: "16px" },
lg: { fontSize: "18px" },
},
},
defaultVariants: { size: "md" },
});
// Style SVG icons inside buttons based on size variant
globalRecipe({
recipe: buttonStyle,
selectorGenerators: {
"&": (v) => `${v} svg`,
},
base: {
pointerEvents: "none",
flexShrink: 0,
},
compoundVariants: [
{ variants: { size: "sm" }, style: { width: "16px", height: "16px" } },
{ variants: { size: "md" }, style: { width: "20px", height: "20px" } },
{ variants: { size: "lg" }, style: { width: "24px", height: "24px" } },
],
});// checkbox.css.ts
import { recipe } from "@vanilla-extract/recipes";
import { globalRecipe } from "global-recipes";
const checkboxStyle = recipe({
base: { display: "inline-flex" },
variants: {
variant: {
checkbox: {},
radio: {},
},
size: {
sm: {},
md: {},
},
},
defaultVariants: { variant: "checkbox", size: "md" },
});
// Style the visual indicator (div after hidden input) with state-based selectors
globalRecipe({
recipe: checkboxStyle,
selectorGenerators: {
"&": (v) => `${v} input + div`,
"&:checked": (v) => `${v} input:checked + div`,
"&:focus": (v) => `${v}:focus-within input + div`,
"&:disabled": (v) => `${v} input:disabled + div`,
},
base: {
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "2px solid gray",
selectors: {
"&:disabled": { opacity: 0.5, cursor: "not-allowed" },
},
},
compoundVariants: [
{
variants: { variant: "checkbox" },
style: {
borderRadius: "4px",
selectors: {
"&:checked": { background: "blue", borderColor: "blue" },
"&:focus": { boxShadow: "0 0 0 2px rgba(0, 0, 255, 0.3)" },
},
},
},
{
variants: { variant: "radio" },
style: {
borderRadius: "50%",
selectors: {
"&:checked": { background: "green", borderColor: "green" },
},
},
},
{
variants: { size: "sm" },
style: { width: "16px", height: "16px" },
},
{
variants: { size: "md" },
style: { width: "20px", height: "20px" },
},
],
});Applies global CSS styles based on recipe variant class combinations.
| Option | Type | Required | Description |
|---|---|---|---|
recipe |
RuntimeFn |
Yes | The vanilla-extract recipe function |
selectorGenerators |
Record<string, (v: string) => string> |
Yes | Functions that generate CSS selectors. Must include "&" key. |
base |
StyleRuleWithSelectors |
No | Base styles applied to all elements (supports selectors for state-based styles) |
compoundVariants |
Array<{ variants, style }> |
No | Variant-specific styles |
The selectorGenerators object maps keys to functions that generate CSS selectors:
selectorGenerators: {
// Required: base selector
'&': (v) => `${v} svg`,
// Optional: additional state/pseudo selectors
'&:hover': (v) => `${v}:hover svg`,
'&:disabled': (v) => `${v}:disabled svg`,
}The v parameter is the variant class selector (e.g., .button_size_md).
The base applies to all elements matching the recipe's base class. It can include a selectors object:
base: {
pointerEvents: 'none',
selectors: {
'&:disabled': { opacity: 0.5 }, // Applied to all disabled states
},
}Each compound variant can include a selectors object for state-specific styles:
{
variants: { size: 'md' },
style: {
width: '20px',
selectors: {
'&:hover': { transform: 'scale(1.1)' }, // Only keys from selectorGenerators allowed
},
},
}- Variant names and values are inferred from your recipe — typos are caught at compile time
- Selector keys in
style.selectorsare constrained to keys defined inselectorGenerators
If you only need base styles without variant-specific styles, pass an empty array or omit compoundVariants:
globalRecipe({
recipe: buttonStyle,
selectorGenerators: { "&": (v) => `${v} svg` },
base: { fill: "currentColor" },
compoundVariants: [], // or just omit this property
});When specifying multiple variants, the generated selector combines all variant classes:
compoundVariants: [
{
variants: { size: "sm", variant: "primary" },
style: {
/* applies to .size_sm.variant_primary */
},
},
];Variant values set to undefined are ignored when building selectors:
compoundVariants: [
{
variants: { size: "sm", variant: undefined },
style: {
/* applies only to .size_sm */
},
},
];- Reads the recipe's internal
classNames.variantsmap - Builds CSS class selectors from variant combinations (e.g.,
.button_size_md.button_hierarchy_primary) - Passes these to your selector generators to create full selectors
- Calls vanilla-extract's
globalStylefor each selector/style pair
MIT