Styles encapsulation: opt-in inheritance
See original GitHub issueThis is a feature request or rather an idea – would love to know your opinion.
Like CSS Modules, CSS-in-JS solutions or even BEM and SUIT CSS, styled components generates unique class names to avoid name clashing and global disasters caused by the CSS cascade property.
People feel pretty confident when using these tools but ignore the fact that there is another enemy around the corner that is CSS inheritance i.e.
styles applied to a component can affect the subtree and even reach the leaves. Properties like color
, font-size
, font-family
are still inherited
.
This is good for many reasons but it is an unpredictable side-effect and imho inheritance should be opt-in (instead of opt-out) and predictable. Pete Hunt mentioned it at CSSConf [video] but we were already implementing this idea into SUIT CSS preprocessor and it is now available in v3 [link].
We could reset inherited and non-inherited properties at the root of the component to get styles encapsulation like in ShadowDOM. Descendants can still inherit from the component scope because they only get non-ineherited properties reset – styles won’t affect nested components.
The only difference between SUIT and styled components is that in styled components there aren’t descendants
i.e. each component is a single element without nested stuff. Therefore in order to fit components into a specific context we’d need to have a way to tell that a component is part of a widget or something (in a way a descendant of a scoped component) and reset only non-inherited properties instead.
API Ideas:
import {descendant} from 'styled-components'
// returns a styled component with inherited and non-inherited properties reset
const Button = styled.button`
border: 1px solid ${color};
`
// returns a styled component with only non-inherited properties reset
const HeaderButton = descendant(Button)
In alternative styles encapsulation could be opt-in
const Button = styled.button`
/** @encapsulate */
border: 1px solid ${color};
`
// or
import {encapsulate} from 'styled-components'
const EncapsulatedButton = encapsulate(Button)
Any of the above solutions would just inject (or remove) the following global class names:
.__styled-components-encapsulate-reset-inherited {
color: initial;
font-family: initial;
font-size: medium;
/* ... */
}
.__styled-components-encapsulate-reset-non-inherited {
margin: 0;
padding: 0;
width: auto;
/* ... */
}
These two rules should be at the top of the stylesheet so that styled components can override single properties.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:9 (3 by maintainers)
Top GitHub Comments
Hmm, this is interesting. But I don’t think the Shadow DOM does quite what you think. Shadow DOM makes a boundary that selectors can’t cross, but inheritance can. You can use
all: initial
on an element to override anything inherited, but that’s something different (at least I think that’s the case, this sort of stuff is hard to know definitively)I’m actually a big proponent of using inheritance judiciously rather than trying to reset every element all the time. I tend to think people are more worried by the idea of inheritance changing values within their components than the reality of doing so. Personally, I think each “outer component” can simply reset the properties it cares about and pass on the ones it doesn’t. That way your “descendants” are totally responsive to the properties already set on the page, which makes them much more reusable.
I gave a talk on this topic a few months back if you’re interested: https://www.youtube.com/watch?v=XR6eM_5pAb0
Since nobody has asked for this feature in almost half a year it’s unlikely we’re going to introduce it to the library. We would also break a large amount of existing users with this change, which is not something I’d be particularly comfortable with.
I’ll close this issue for now, if you still strongly feel this should be part of styled-components one way or another feel free to comment again or open a new issue!