[Feature] Generate sub-classes to reduce duplication of CSS from dynamic prop combinations
See original GitHub issueHave some way to split styles into sub-classes for dynamic props, rather than duplicate the entire CSS/style for each new variant from combining different dynamic prop values.
A user on the spectrum group recently posted about using CSS Grid to build a type of Table component.
They had dynamic props for the table cells based on row and column which made every cell unique. 25 rows resulted in 200+ classes, and they claimed it could go into the thousands of classes in some cases. If classes were split by dynamic props(perhaps with some helper to mark/enable), it’d be considerably less. Their workaround was similar to what I’ve shown below.
What will it allow you to do that you can’t do today?
Reduced class generation from unique dynamic prop combinations.
Each unique class variant duplicates all static CSS rules(as well as all vendor prefixed ones?).
Perhaps it’s considered a non-issue for SSR with gzip, or on client-side as it’s generated on demand and may use little memory or cause little performance hit from having hundreds of variants for what could be only a few classes instead?
How will it make current work-arounds straightforward?
I wouldn’t need to handle it externally/manually? Or bother with coming up with classnames? (One of the pros styled-components already promotes).
What potential bugs and edge cases does it help to avoid?
Should reduce memory from massive CSS duplication(static) from single prop changes introducing new dynamic variants/combinations. Possibly translates into better perf for the CSS parser in the browser?
No real-world bugs/edge cases come to mind.
DX wise, you’re forced into all styles being flattened/merged, despite any use of extension(styled(component)
). In certain components, during development at least it’d be nice to maintain style/class separation(eg in dev tools, a component extends a button
which has had it’s styles “reset”, followed by the extended button applying actual CSS the dev is interested in working with.
Example
The style string / CSS pre-generated::
const ICON_WIDTH = 80;
const ICON_HEIGHT = 80;
const backgroundImage = (output, [key, filepath]) => output + `
&.${key}{
background: url(${filepath}) no-repeat;
}
`
const backgroundPosition = (output, [key, data]) => output + `
&.${key}{
background-position: -${data.x}px -${data.y}px;
}
`
const generateCSSClasses = (dataObj, reducer) => Object.entries(dataObj).reduce(reducer, "")
const SkillStyles = `
// common, base CSS (static)
width: ${ICON_WIDTH}px;
height: ${ICON_HEIGHT}px;
background:red;
position: absolute;
// spritesheet classes - static after generation
${generateCSSClasses(spritesheets, backgroundImage)}
// skill classes - static after generation (high amount)
${generateCSSClasses(IMG_SKILL_DATA, backgroundPosition)}
// Toggle opacity - dynamic toggle based on component prop, effectively static
// prop is only boolean, not a range value
opacity: 0.2;
&.isActive {
opacity: 1;
}
`
My styled component (or you can use regular div and assign the style to css prop and handle className prop there too):
```JS
const Icon = styled.div.attrs(props => ({
className: `${props.spritesheet} ${props.icon}${props.isActive ? ' isActive' : ''}`
})
)`${SkillStyles}`
Usage:
<Styled.Icon
icon={icon}
spritesheet={spritesheet}
isActive={isActive}
/>
For 5 spritesheets with 20 icons each, that’s 100 unique classes(when only the background-position
prop is unique per icon), and the opacity toggle state can double that to 200 unique classes, rather than be it’s own toggle class(scoped to the component), it doubles the variants with all that CSS duplicated. Above snippets don’t have that issue.
background-position
could be made inline via attrs() instead, true. Then with the current styles, you’d only have the 5 spritesheets * 2(opacity states) = 10 unique classes(along with all the duplicated static CSS). Above snippet (which is obviously not a great solution/workaround, or even an option in some cases), without the background-position
classes is only 7 unique classes (base + spritesheets(5) + opacity toggle(2)), no duplicated CSS(besides opacity) and clear separation in dev tools.
Problem
Have to create own css classes and add them manually via classNames
, they also need to be prefixed with &
. Would be great to use without attempting to generate all variants and their classes up front statically, and benefit from opacity: ${props => (props.isActive ? 1 : 0.2)};
usage, but having the scoped class generated like the main styled components one is.
Related Issues(?):
https://github.com/styled-components/styled-components/issues/220 https://github.com/styled-components/styled-components/issues/2069 https://github.com/styled-components/styled-components/issues/986 https://github.com/styled-components/styled-components/issues/937 https://github.com/styled-components/styled-components/issues/779
https://github.com/styled-components/styled-components/issues/1859 They express that they want to adjust a property for a component, but because variants duplicate all CSS for that component due to a different dynamic prop combination, you’re unable to adjust static CSS in dev tools. My above example has base CSS that’s common across all components, I ran into a similar issue.
https://github.com/styled-components/styled-components/issues/1209 I think this is pretty much covered via the css helper, but shows two approaches(one in the issue and another in related issue for compose method). If there was a method or way to define the class separation via array of tagged template literals(or via css helper or similar if necessary?), that’d be nice. Not only are single dynamic props declared for their own class but groups of props/rules could belong to their own class(afaik, a hash for a styled-component classname is derived from the given CSS output?, should be applicable to these groups/subclasses?)
https://github.com/styled-components/styled-components/issues/948 Could technically apply to this issue, by splitting the vendor prefixes added into their own class maybe(though with upcoming toggling on/off for that, I guess that’s less relevant)
https://github.com/styled-components/styled-components/issues/263
The amount of CSS you inject is pretty irrelevant, while there is a “maximum” it’s so huge that you should never be able to reach that point. Well that’s probably much easier to reach with highly dynamic styles when a single prop introducing a variant in a style full of other dynamic props duplicates all CSS including the static CSS for that component?
https://github.com/styled-components/styled-components/issues/134 Sort of related, shows perf issues from generating many classes, but is mostly due to high frequency updates to props, such as the colour picker example in the issue which is better suited for inline styles.
https://github.com/styled-components/styled-components/issues/1056 Suggested using prop types for static generation of variants upfront where possible.
https://github.com/styled-components/styled-components/issues/1018
this inline styles actually an issue for us especially when we running the web site in cloud and they charge by egress network data size. we have a unzipped 1.5MB html page and 1.3MB styles inline, it is gzipped as 200KB but it is still too large.
https://github.com/styled-components/styled-components/issues/940 https://github.com/styled-components/styled-components/issues/1530
However, we also support
styled(Text)
which creates two styledcomponents and two classes. Maybe so? It seems that this was a way for users in v3 to get the type of class separation, but in v4, a component ignores the separation and merges/flattens the classes into one.
https://github.com/styled-components/styled-components/issues/2104
Yes, this is intentional. Internally we now “fold” styled-components into each other when extended with
styled(Comp)
so that there’s less components in your hierarchy and less styles duplicated.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:4
- Comments:5 (1 by maintainers)
Top GitHub Comments
My use case is sightly related to this issue but it’s more about reducing CSS duplication than Class duplication.
I’m using the following component:
Currently, Styled Components generates two class names where each has the same properties but differ in the color. I would expect one class with all the static properties, and one for each dynamic property:
I’ve added another issue regarding this with explanation of my problem: https://github.com/styled-components/styled-components/issues/3240