Migrating to styled-components cheatsheet
Choosing a styling library for a React project is hard.
And when you already have an existing code base you’re considering to migrate, things get only trickier.
Will you be able to convert everything?
Is there a way to get a feel for how the code will look before you actually start changing your entire project?
I’ve recently migrated a project from vanilla CSS to styled-components to get myself more familar with the library and made a list of the things I bumped into while converting the code.
I hope you’ll find this useful to see what to expect from the transition or even help you when doing the switch!
Word of caution
If you've never seen styled component syntax before, it might look awkard at first, but it's just native ES6!
// Styled component syntax ..
const Button = styled.button`
border: 1px solid blue;
`;
// .. made out of template literals
// "smarter" strings available in ES6
// with multiline and interpolation support
const color = "blue";
const test = `my color is ${blue}`;
// .. and tagged template literals
// which are functions that can receive
// a template literal as input
funtion myTag(strings, ...expressions) {}
const test = myTag`here is a ${color}`;
Read more about:
- template literals
- tagged template literals
- a more in depth explanation of how this works from the author of the library, Max Stoiber.
HTML tags with classes
- pick the HTML element you have (div, a, button - let's call it X)
- create a component with "styled.X"
/* BEM */
.App__header {
color: #00d8ff;
background-color: #222;
}
<div className="App__header"></div>
/* Styled components */
const AppHeader = styled.div`
color: #00d8ff;
background-color: #222;
`;
<AppHeader></AppHeader>
Custom React component with classes
- pick the React component you have (any custom component, let's call it X)
- create a styled component wrapper with "styled(X)"
/* BEM */
.App__homelink {
text-decoration: none;
font-weight: bold;
margin-right: .75em;
}
<Link to="/news" className="App__homelink">
Hello!
</Link>
/* Styled components */
const HomeLink = styled(Link)`
text-decoration: none;
font-weight: bold;
margin-right: .75em;
`;
<HomeLink to="/news">
Hello!
</HomeLink>
Nested styles
- just use SCSS styled nested syntax (yes, it's supported)
/* BEM */
.App__header {
color: #00d8ff;
background-color: #222;
}
.App__header a {
color: inherit;
text-decoration: none;
}
.App__header a.active {
color: #fff;
}
<div className="App__header">...</div>
/* Styled components */
var AppHeader = styled.div`
color: #00d8ff;
background-color: #222;
a {
color: inherit;
text-decoration: none;
&.active {
color: #fff;
}
}
`;
<AppHeader>...</AppHeader>
Vendor prefixes
You don’t need to migrate vendor prefixes, these are already supported!
/* BEM */
.Spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
<div className="bounce1"></div>
/* Styled components */
var Bounce1 = styled.div`
animation-delay: -0.32s;
`;
<Bounce1/>
Extending classes
There are two ways to extend classes in styled components:
- styled(X) (e.g. styled(Button)) - very similar to SCSS @extend in that it creates a new class and copies the styles over to it
- X.extend - very similar to SCSS @mixin in that it creates a new class with just the extra rule, and applies both the initial and the new class to the element
/* BEM */
.Spinner {}
.Spinner > div {
background-color: #666;
border-radius: 100%;
display: inline-block;
/* ... other styles */
}
.Spinner .bounce1 {
animation-delay: -0.32s;
}
.Spinner .bounce2 {
animation-delay: -0.16s;
}
<div className="Spinner">
<div className="bounce1"/>
<div className="bounce2"/>
<div className="bounce3"/>
</div>
/* Styled components */
var StyledSpinner = styled.div``;
var Bounce = styled.div`
background-color: #666;
border-radius: 100%;
display: inline-block;
/* ... other styles */
`;
var Bounce1 = styled(Bounce)`
animation-delay: -0.32s;
`;
var Bounce2 = styled(Bounce)`
animation-delay: -0.16s;
`;
var Bounce3 = styled(Bounce)``;
<StyledSpinner>
<Bounce1/>
<Bounce2/>
<Bounce3/>
</StyledSpinner>
Keyframes
- use the "keyframes" helper function to get a generated unique name for each keyframe in your app
/* BEM */
@keyframes bouncedelay {
0%, 80%, 100% { transform: scale(0.0); }
40% { transform: scale(1.0); }
}
.bounce {
animation: bouncedelay 1.4s infinite ease-in-out;
}
<div className='bounce'></div>
/* Styled components */
const bouncedelay = keyframes`
0%, 80%, 100% { transform: scale(0.0); }
40% { transform: scale(1.0); }
`;
var Bounce = styled.div`
animation: ${bouncedelay} 1.4s infinite ease-in-out;
`;
<Bounce/>
Dynamic styles
- have the styles adapt to props out of the box
/* Style attribute */
var bounceSize = this.props.size + 'px';
var bounceStyle = {
height: bounceSize,
width: bounceSize,
marginRight: this.props.spacing + 'px'
};
<div className="Spinner" style={width: ((Number(this.props.size) + Number(this.props.spacing)) * 3) + 'px'}>
<div className="bounce1" style={bounceStyle}/>
<div className="bounce2" style={bounceStyle}/>
<div className="bounce3" style={bounceStyle}/>
</div>
/* Styled components */
var StyledSpinner = styled.div`
width: ${props => ((Number(props.size) + Number(props.spacing)) * 3) || 0 }px;
`;
var Bounce = styled.div`
height: ${props => props.size || 0}px;
width: ${props => props.size || 0}px;
margin-right: ${props => props.spacing || 0}px;
`;
<StyledSpinner {...this.props}>
<Bounce {...this.props}/>
<Bounce {...this.props}/>
<Bounce {...this.props}/>
</StyledSpinner>
Refs
React does not allow forwarding refs, so you will need to use the styled components innerRef attribute that will be forwarded to the inner DOM node (see official docs); note: in the example here, the left side uses string refs, which have now been deprecated.
// Accessing the ref:
// this.refs.container.focus()
<div ref="container"
className="Settings"
tabIndex="-1"
onClick={this.onClick}>
</div>
// Accessing the ref:
// this.settingsContainer.focus()
<SettingsContainer
innerRef={(container) =>
(this.settingsContainer = container)}
tabIndex="-1"
onClick={this.onClick} />
Conditional class names
You no longer need to compute class name lists - just pass in the needed props.
<div className={cx(
'Item',
{'Item--dead': item.dead}
)}></div>
<StyledItem dead={item.dead}></StyledItem>
Change styles based on parent
Notice how with styled components we can use the React component name as a selector.
/* BEM */
.Comment--new > .Comment__content {
background-color: #ffffde;
}
/* Styled components */
const CommentContent = styled.div``;
const Comment = styled.div`
> ${CommentContent} {
background-color: ${props => props.new ? '#ffffde' : ''}
}
`;
Change child styles based on parent hover
Same as above, use the component selector to capture the child.
/* BEM */
.Comment:hover > .Comment__content {
background-color: #fff;
}
/* Styled components */
const CommentContent = styled.div``;
const Comment = styled.div`
&:hover {
> ${CommentContent} {
background-color: #fff;
}
}
`;
Media queries
- you can define meaningful names for each media rule and export them as constants
- reference them as needed in each component
/* BEM */
/* line 109 */
.App__homelink {
text-decoration: none;
font-weight: bold;
color: #00d8ff !important;
margin-right: .75em;
}
.App__homelink.active {
color: #fff !important;
}
/* ... line 319 */
@media only screen and (max-width: 750px) and (min-width: 300px) {
/* Hide the App title homelink on narrow viewports */
.App__homelink {
display: none;
}
}
/* Styled components */
/* Define this in separate file, like media.js */
const narrowScreen = 'only screen and (max-width: 750px) and (min-width: 300px)';
/* Include the file in each component that needs to adapt based on a media query */
var HomeLink = styled(Link)`
text-decoration: none;
font-weight: bold;
color: #00d8ff !important;
margin-right: .75em;
&.active {
color: #fff !important;
}
@media ${narrowScreen} {
display: none;
}
`;
The end
Which side do you like better?
Is it the left (vanilla CSS) or the right (styled-components)?
Let me know in the comments below!