'How to select parent components variant in styled components?
In styled-components
we can add contextual styles using component selector pattern. But how do we select specific parents variants to contextually style the child component? For example, we have three components here, Child
, NoteWrapper
, and Note
.
const Child = styled.div<{ editing?: boolean }>`
${props => props.editing && css`some styles`}
`
const NoteWrapper = styled.div<{ compact?: boolean }>`
${props => props.compact && css`some styles`}
`
const Note = styled.input`
/* How do I style this when Child is editing and NoteWrapper is compact */
`
const App = () => {
return (
<Child editing>
<NoteWrapper compact>
<Note/>
</NoteWrapper>
</Child>
)
}
`
With plain CSS we could do something like this
.child.editing .note-wrapper.compact .note {
/* Contextual styles here */
}
I know I can easily use the editing
and compact
variables and pass it to the Note
component. However, it would be difficult to do so if components are highly nested.
My question is how do I style Note
when Child
is editing
and NoteWrapper
is compact
in styled-components
selector pattern?
I don't think we can do something like this in styled-components
in some way? Can we?
const Note = styled.input`
${Child.editingClass} ${NoteWrapper.compactClass} & {
/* The contextual styles here*/
}
`
Solution 1:[1]
As far as I know, you can't access editing
or compact
, since they're props and aren't "propagated" in CSS. But you can achieve the same result using either classes or data attributes.
Here's how I'd do it:
const App = () => {
return (
<Child data-editing={true}>
<NoteWrapper data-compact={true}>
<Note/>
</NoteWrapper>
</Child>
)
}
const Child = styled.div`
`
const NoteWrapper = styled.div`
`
const Note = styled.input`
${Child}[data-editing="true"] ${NoteWrapper}[data-compact="true"] & {
/*
Only applied when Child is editing and NoteWrapper is compact
*/
color: red;
}
`
Essentially, styled-components needs to produce valid CSS at the end of the day. ${Child}
will be replaced by the custom-generated classname, like .sc-abc123
. And so the end result is .sc-abc123[data-editing="true"]
, which is totally-valid CSS.
You could also use classes, which looks a bit tidier:
const App = () => {
return (
<Child className="editing">
<NoteWrapper className="compact">
<Note/>
</NoteWrapper>
</Child>
)
}
const Child = styled.div`
`
const NoteWrapper = styled.div`
`
const Note = styled.input`
${Child}.editing ${NoteWrapper}.compact & {
/*
Only applied when Child is editing and NoteWrapper is compact
*/
color: red;
}
`
I prefer to use data-attributes because it's clear that they're used for logical purposes, and not because editing
is a CSS class with a bunch of styles. But that's a subjective preference, they both work equally well in terms of objective functionality :)
Solution 2:[2]
You could also pass the props to the Note
component itself and do a similar logic check.
<Note
compactEdit={editing && compact}
/>
const Note = styled.div`
${props =>
props.compactEdit
? css`color: red;`
: css`color: blue;`
};
`;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Adjit |