-
Hi, I'm building a design system based on vanilla-extract. CSS-in-TS is an amazing concept that keeps your types safe and makes the development experience more comfortable. Thank you very much for this. I’d like to share my experience with a specific use case, as I’m curious whether the library would recommend it or advise against it. The key issue I encountered is the number of sprinkles combinations exceeding expectations and the resulting impact on bundle size. Setting pseudo selectors in sprinkles conditionsI tried to deliver design tokens through pseudo-selectors in sprinkles. e.g., const pseudoClassesConditions = {
default: { selector: "&" },
// Pseudo-classes
hover: { selector: "&:hover" },
focus: { selector: "&:focus" },
focusWithin: { selector: "&:focus-within" },
active: { selector: "&:active" },
disabled: { selector: "&:disabled" },
enabled: { selector: "&:enabled" },
checked: { selector: "&:checked" },
}; I defined properties under these conditions, where each property and condition combination creates dozens of variations. In this code, color properties are combined with conditions like lightMode, darkMode, and several pseudo-classes. export const colorAtomicProps = defineProperties({
properties: colorProps, // ✅ color properties
shorthands: {
bgColor: ["backgroundColor"],
},
defaultCondition: "lightMode",
conditions: {
lightMode: ...
darkMode: ...
...pseudoClassesConditions, // ✅ conditions
},
responsiveArray: ["lightMode", "darkMode"],
}); I can use these pseudo selectors via the sx prop, which passes them to sprinkles, and it works as expected. For example, the background color changes as expected when hovering over this component: // It means that `sprinkles({ backgroundColor: { hover: "green100" }})`
<HStack
sx={{
gap: "xs",
backgroundColor: { hover: "green100" }, // ✅ here
}}
></HStack> Combinatorial explosions 💥The problem is the combinatorial explosion!
After transpiling .ts to .js with rollup, @rollup/plugin-typescript, and @vanilla-extract/rollup-plugin, the resulting spinkles.css.js code looks like this: h = (function () {
var g = {
conditions: {
defaultCondition: "lightMode",
conditionNames: [
"lightMode",
"darkMode",
// ✅ Pseudo classes that I set
"default",
"hover",
"focus",
"focusWithin",
"active",
"disabled",
"enabled",
"checked",
// -----------------------------
],
responsiveArray: ["lightMode", "darkMode"],
},
styles: {
bgColor: { mappings: ["backgroundColor"] },
background: {
values: {
green50: {
conditions: {
lightMode: hash key string.....,
darkMode: ...,
default: ...,
hover: ...,
focus: ...,
focusWithin: ...,
active: ...,
disabled: ...,
enabled: ...,
checked: ...,
},
defaultClass: ...,
},
green100: { ... },
green200: { ... },
green300: { ... },
green400: { ... },
green500: { ... },
green600: { ... }, Here’s the issue:
The bundle sizeI tested one of 4 properties in my sprinkles. export const sprinkles = createSprinkles(
unresponsiveAtomicProps,
colorAtomicProps, // ✅ test properties
responsiveAtomicProps,
basicAtomicProps,
); �For
After removing the pseudo-classes from the conditions:
This saved about 21.54% (148K) on the CSS file and 11.70% (49K) on the JS file. Since responsiveAtomicProps and basicAtomicProps have even more properties and variables than colorAtomicProps, including all of them results in a 962K JS file and a 2.1M CSS file. ☠️💥 For consistent usability, we eventually decided not to support pseudo-selectors across all properties. I’m curious if this is simply a trade-off for supporting numerous conditions, or if there are any optimization strategies to reduce the bundle size? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
i would say this is an anti-pattern and vanilla extract is not the right tool for this job with sprinkles you spit out a pre-compiled CSS with every possible combination and in runtime the decision is made when to use which combination. but if you need more flexibility here then something like panda css might be better suited. in that tool there is build time logic that determines which combinations are actually used and only spits out the css for that - which i guess is a more truer CSS-in-JS solution. in our design system we used sprinkles just to include tokenized values of our styles (spacing, colors, border radius, box shadow, etc.) and combined it with responsive breakpoint/screen conditions only for a selective list of props (like padding, display, layout related stuff basically). this will cover 90% of use cases and for pseudo states like hover/active we expect devs to fallback to writing regular css with tokens using css custom properties and kind of abandoning sprinkles at that point. for light/dark mode for colors we didn't even use sprinkles and made sure to select the token values based on the current light/dark mode. so styles just refer to the token and whether the light/dark version of that color is used depends on the light/dark mode. |
Beta Was this translation helpful? Give feedback.
i would say this is an anti-pattern and vanilla extract is not the right tool for this job
with sprinkles you spit out a pre-compiled CSS with every possible combination and in runtime the decision is made when to use which combination.
but if you need more flexibility here then something like panda css might be better suited. in that tool there is build time logic that determines which combinations are actually used and only spits out the css for that - which i guess is a more truer CSS-in-JS solution.
in our design system we used sprinkles just to include tokenized values of our styles (spacing, colors, border radius, box shadow, etc.) and combined it with responsive breakpoint/screen …