feat: add grouped styles mode via cssMode config option#3531
Conversation
Adds opt-in `groupedStyles: true` config that groups multiple CSS properties from a single `css()` call into one class instead of one-per-property atomic classes. Reduces class count in HTML at the cost of potential CSS duplication. - Encoder: `processGrouped()` hashes style objects into deduplicated groups - Decoder: `collectGrouped()` produces single-class CSS rules via `getGroup()` - Parser: routes `css()` and JSX style props through grouped path when enabled - Runtime: `createCss` grouped mode reproduces encoder hashes for matching class names - Generator: passes `grouped` flag to generated runtime context - Full serialization support via `toJSON`/`fromJSON`
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@gajus is attempting to deploy a commit to the Chakra UI Team on Vercel. A member of the Team first needs to authorize it. |
groupedStyles config optioncssMode config option
|
fwiw I thought about adding it at some point but figured there were too many caveats (see the PR's description) for it to be worth adding into the core package |
Tests that demonstrate where cssMode: 'grouped' breaks: - Unresolvable values: partial extraction produces a different group hash than runtime - Ternaries: parser splits branches into separate groups, none match the combined runtime object - css.raw merging: build sees individual parts, runtime sees merged result Also verifies that fully static css() and statically resolvable spreads work correctly.
Thanks for sharing. The main issues called out were ternaries and
Everything is behind cssMode: "grouped" so it's opt-in with no impact on existing behavior. If I missed something, would appreciate if you share a breaking test case. |
|
@segunadebayo any comments? |
|
Struggling to get some feedback here. Maybe @anubra266 ? |
|
Thanks for the PR @gajus. Will have a look over the weekend and let you know |
|
Thank you. For what it is worth, I've used Claude to brainstorm edge case/test pairs, and could not identify any undesirable behaviors. |
|
@segunadebayo would really like to see this land in the next version. Let me know if any further input is needed from my end. |
|
@segunadebayo any update here? |
|
@segunadebayo Are there any other maintainers I can tag here to accelerate the resolution? |
Motivation
Production Panda apps routinely produce class sequences like:
class="gGTyfw cjSpVR VxsAK kYufQm iKPHnu cPHhKG bYPztT jZzPrs hYCnIA dDifmo hDGJaH dTBYkx bjOqtc bZRhvx fPSBzf bYPznK diIxfU jTWvec jLRTbm kFYoVQ fNDbsJ fNDbsm"Atomic CSS is a good default, but at scale it has measurable costs that
hash: truedoesn't address (it shortens class names, not their count). This PR adds an opt-in flag to trade some CSS duplication for smaller, more inspectable HTML.Evidence
Style recalc scales with declaration count. Per web.dev, "the worst case cost of calculating the computed element's style is the number of elements multiplied by the selector count." A Dec 2025 benchmark compared atomic vs. grouped CSS on identical visual output (5K components, 20K DOM nodes, same CSS file size). Atomic produced 35K declarations vs. 10K grouped:
Padding class names to equalize HTML size left the gap intact – declaration count was the driver.
HTML parse cost scales with bytes, even after compression (the parser sees the decompressed string). The same benchmark generated 1.1 MB of HTML for atomic vs. 0.4 MB for grouped. Peterbe's case study measured 119KB vs. 31KB HTML on the same page: parse + layout dropped from 523ms to 126ms under throttled conditions.
Dev tools UX. Acknowledged in Panda's own docs: long atomic classNames make DOM inspection painful.
hashandgroupedStylessolve different parts of this.This matters most for SSR/SSG, where HTML can rarely be aggressively cached and lands on every initial render.
Tradeoff
cssMode: 'grouped'increases CSS bundle size – shared properties acrosscss()calls no longer dedupe to single atomic classes. The right call depends on whether the bottleneck is HTML payload + recalc time or CSS bundle size. That's why it's opt-in.What this PR does NOT change
Atomic remains the default. No breaking changes. Cascade layer ordering,
hash,cva, recipes, and patterns are unaffected.Implementation
Minimal changes localized to the extractor, behind a single config flag.