styled-components 💅 getting started
We're going to style the basic create react app with styled-components to look something like this:
But first, preamble✨: I have always struggled with styling sites, it seems to be an aspect of starting web development that is either an afterthought or glossed over. Up until December last year I didn't really like styling anything at all with CSS, it was a chore rather than something I enjoyed doing.
This was until I started using styled-components, when I joined a
build to learn project for a Chingu voyage (grad.then()
if
you're interested) we decided to use a CSS-in-JS package, Marina who
was on my team was such an inspiration for me watching how components
were styled and really gave me the confidence to start using
styled-components.
me with css before
I want to share what I have learned so far by going through styling a basic react application.
There's some basic CSS concepts in this post that I was not aware of before starting out with styled-components that I presume are assumed in styling web pages.
Styling the body element of a site is assumed, so for when you are
starting out with a blank canvas there are some defaults for all
modern web browsers you add to your site, like leaving font size at
16px (or 1rem) or box-sizing:
border-box;
there's some packages
out there to take care of this for you as well.
Install styled-components
Ok let's bootstrap the basic react application you get when using
Create React App with npx
, if you already have Create React App
installed globally then you can use the command without npx
.
1npx create-react-app style-with-styled-components2cd style-with-styled-components/3npm i styled-components
Ok, now we have the basic app we can style, thankfully Dan has kindly provided the starting styles for us so let's begin my using them with styled-components.
The way the CRA CSS is laid out, assumes that you will have a corresponding CSS file for each component, which can help with maintaining the CSS and lends to the React idea of having all your files separated into their component parts.
We can start with the App.js
file and it's accompanying App.css
file. Let's take a look at the App.js
first:
1import React, { Component } from 'react'2import logo from './logo.svg'3import './App.css'4class App extends Component {5 render() {6 return (7 <div className="App">8 <header className="App-header">9 <img src={logo} className="App-logo" alt="logo" />10 <h1 className="App-title">Welcome to React</h1>11 </header>12 <p className="App-intro">13 To get started, edit <code>src/App.js</code> and save to14 reload.15 </p>16 </div>17 )18 }19}20export default App
In styled-components we'd create components for each of these elements
that replace the aforementioned className
's. Ok we can start by
migrating our styles into components, let's do one component first to
get an idea of where we're going with this.
First, import styled
into the App.js
module:
1import styled from 'styled-components'
Now let's look at <div className="App">
, it's the top level div for
this component and is what I like to call the wrapper for the
component. So let's give it an imaginative name AppWrapper.
Referring to the App.css there is text-align: center;
which belongs
to this, so:
1const AppWrapper = styled.div`2 text-align: center;3`
So here we have defined the AppWrapper
const as a styled.div
followed by back ticks inside of the back ticks we can write any
regular CSS with the exact same CSS syntax you wold in a normal .css
file.
Now that we have our AppWrapper
we can replace the top level div on
the App.js
component.
1import React, { Component } from 'react'2import styled from 'styled-components'3import logo from './logo.svg'4import './App.css'5class App extends Component {6 render() {7 return (8 <AppWrapper>9 <header className="App-header">10 <img src={logo} className="App-logo" alt="logo" />11 <h1 className="App-title">Welcome to React</h1>12 </header>13 <p className="App-intro">14 To get started, edit <code>src/App.js</code> and save to15 reload.16 </p>17 </AppWrapper>18 )19 }20}21export default App
styled-components all the things
So let's do that for the remaining four CSS classes, and take a look,
I'll define them underneath the AppWrapper
here:
1const rotate360 = keyframes`2 from {3 transform: rotate(0deg);4 }5 to {6 transform: rotate(360deg);7 }8`9const AppLogo = styled.img`10 animation: ${rotate360} infinite 120s linear;11 height: 80px;12`13const AppHeader = styled.div`14 background-color: #222;15 height: 150px;16 padding: 20px;17 color: white;18`19const AppTitle = styled.h1`20 font-size: 1.3em;21`22const AppIntro = styled.p`23 font-size: large;24`
So first off we've created a variable for the React svg animation,
you'll need to import the keyframes
helper from styled-components
like so:
1import styled, { keyframes } from 'styled-components'
this can now be used throughout the App.js
component and we can add
an on hover
selector to any of our styled-components within this
module. Here we're going to add it to the AppLogo
to keep the super
sweet rotating React logo.
1const AppLogo = styled.img`2 animation: ${rotate360} infinite 120s linear;3 height: 80px;4 &:hover {5 animation: ${rotate360} infinite 1.5s linear;6 }7`
Ok, our app shouldn't look any different as we haven't added in our
styled-components to the app render()
method, so let's do that now.
Let's also change the intro text. You can add a wrapper for the
<code>
tags something like:
1const CodeWrapper = styled.code`2 font-size: 1.3rem;3`
But if you prefer you can nest selectors within the component, like:
1const AppIntro = styled.p`2 color: ${props => props.theme.dark};3 font-size: large;4 code {5 font-size: 1.3rem;6 }7`
Let's have a look at the render()
method now…
1render() {2 return (3 <AppWrapper>4 <AppHeader>5 <AppLogo src={logo} alt="logo" />6 <AppTitle>Welcome to React</AppTitle>7 </AppHeader>8 <AppIntro>9 Bootstrapped with <code>create-react-app</code>.10 </AppIntro>11 <AppIntro>12 Components styled with <code>styled-components</code>{' '}13 <EmojiWrapper aria-label="nail polish"></EmojiWrapper>14 </AppIntro>15 </AppWrapper>16 )17}
Now all the classes originally used in App.js
have been replaced so
there's no need for the import './App.css'
mapping, remove that
aaaaand! Still no change!! Which is a good thing because at the moment
we're swapping out the .css
files for styled-components.
Cool, we have now replaced all the css with styled-components, now we
can take a look at injectGlobal
.
Let's take a look at how the App.js
file should look before we move
on:
1import React, { Component } from 'react'2import styled, { keyframes } from 'styled-components'3import logo from './logo.svg'45const AppWrapper = styled.div`6 text-align: center;7`89const rotate360 = keyframes`10 from {11 transform: rotate(0deg);12 }13 to {14 transform: rotate(360deg);15 }16`1718const AppLogo = styled.img`19 animation: ${rotate360} infinite 120s linear;20 height: 80px;21 &:hover {22 animation: ${rotate360} infinite 1.5s linear;23 }24`2526const AppHeader = styled.div`27 background-color: #222;28 height: 12rem;29 padding: 1rem;30 color: white;31`3233const AppTitle = styled.h1`34 font-weight: 900;35`3637const AppIntro = styled.p`38 font-size: large;39 code {40 font-size: 1.3rem;41 }42`4344const EmojiWrapper = styled.span.attrs({45 role: 'img',46})``4748class App extends Component {49 render() {50 return (51 <AppWrapper>52 <AppHeader>53 <AppLogo src={logo} alt="logo" />54 <AppTitle>Welcome to React</AppTitle>55 </AppHeader>56 <AppIntro>57 Bootstrapped with <code>create-react-app</code>.58 </AppIntro>59 <AppIntro>60 Components styled with <code>styled-components</code>{' '}61 <EmojiWrapper aria-label="nail polish" />62 </AppIntro>63 </AppWrapper>64 )65 }66}6768export default App
Style the body with injectGlobal
For styling the body of our react app we currently have the
index.css
file that is being imported into the mounting point of our
app in the index.js
file.
To style the body we can use injectGlobal
from styled-components
which adds the styles directly to the stylesheet.
To do this you bring in the injectGolabl
named export from
styled-components and add your styles between the back ticks.
The current index.css
looks like this:
1body {2 padding: 0;3 margin: 0;4 font-family: sans-serif;5}
Let's add a theme
folder in the src
directory and add a
globalStyle.js
file where we can keep all our styles we want to use
throughout the app, keeping the styles on one place will make changes
simpler.
In src/theme/globalStyle.js
we'll need to import the injectGlobal
named export from styled-components and add the index.css
styles
into it:
1import { injectGlobal } from 'styled-components'23injectGlobal`4 body {5 padding: 0;6 margin: 0;7 font-family: sans-serif;8 }9`
Ok, now we're adding the body style to the stylesheet directly so
there is no need for the index.css
file mapping that is in
index.js
it should look like this now:
1import React from 'react' import ReactDOM from 'react-dom'23import App from './App'45import registerServiceWorker from './registerServiceWorker'67ReactDOM.render(<App />, document.getElementById('root'))89registerServiceWorker()
We should still have our nice sans-serif
font going on, but let's
add in some nice Roboto for the body and Montserrat for the heading in
our globalStyle.js
module. We can import Google fonts with an
@import
in injectGlobal
and apply Roboto to the body:
1injectGlobal`2 @import url(‘https://fonts.googleapis.com/css?family=Montserrat|Roboto');3 4 body {5 padding: 0;6 margin: 0;7 font-family: Roboto, sans-serif;8 }9`
Cool now we can add our imported font for or app header, and there's
the option if we want all our <h1>
's to use the same font we can add
that to the injectGlobal in our globalStyle.js
module.
1injectGlobal`2 @import url(‘https://fonts.googleapis.com/css?family=Montserrat:400,900|Roboto');3 body {4 padding: 0;5 margin: 0;6 font-family: Roboto, sans-serif;7 }8 h1 {9 font-family: Montserrat;10 }11`
Then we can adjust the weight on the AppTitle
component:
1const AppTitle = styled.h1`2 font-weight: 900;3`
To add the additional styles for fonts like Montserrat and Roboto you
can specify them in the @import url()
you'll notice that Montserrat
has :400,900
after it that is specifying the styles regular (400)
and black (900), you can import as many as you like from Google fonts
(CDN) but the more you import the longer it will take to load them, if
you have a lot of fonts and styles you want in your app then consider
adding them to a folder in the project, like:
1import Montserrat from './fonts/Montserrat-Regular.ttf'23injectGlobal`@font-face { font-family: Montserrat; src: url(${Montserrat}); }`
Theming
Themes are often used to change the look and feel of a wide range of things at once. For example, you may have a night and day mode like in Twitter. You can create your own themes in styled-components too.
Use the styled-components ThemeProvider
Now say we want to have several components in our app that use a CSS
colour property color: #6e27c5
instead of hard coding it through the
app for every component that uses it we can use the styled-components
ThemeProvider
.
For this we will need to import the ThemeProvider
named export from
styled-components, then define a theme
object where our colour is
going to live:
1export const theme = {2 primary: '#6e27c5',3}
Let's add the newly created theme
to the globalStyle
module we
created previously.
To make the theme object available throughout the app component we'll
wrap our app component in the ThemeProvider
and import our awesome
theme for use in the ThemeProvider
:
1import React, { Component } from 'react'2import styled, { keyframes, ThemeProvider } from 'styled-components'3import logo from './logo.svg'4import { theme } from './theme/globalStyle'56// our styled-components78class App extends Component {9 render() {10 return (11 <ThemeProvider theme={theme}>12 {/* all children can access the theme object */}13 </ThemeProvider>14 )15 }16}17export default App
Now the theme
properties can be used as props in our
styled-components, let's change the background-color:
in the
AppHeader
component, whilst we're at it let's add a dark: #222
property to our theme
object and use that for the color
property:
1const AppHeader = styled.div`2 height: 12rem;3 padding: 1rem;4 color: ${props => props.theme.dark};5 background-color: ${props => props.theme.primary};6`
Now we can change our app theme globally
Ok cool, can you change theme?
This is what I was thinking and it turns out you can, there's a great Stack Overflow answer from Max on it.
It got me thinking if you can switch between themes rather than define them for different sections like in the SO answer.
I started off by defining two themes (with imaginative names) in the
globalStyle.js
module:
1export const theme1 = {2 primary: '#ff0198',3 secondary: '#01c1d6',4 danger: '#eb238e',5 light: '#f4f4f4',6 dark: '#222',7}89export const theme2 = {10 primary: '#6e27c5',11 secondary: '#ffb617',12 danger: '#f16623',13 light: '#f4f4f4',14 dark: '#222',15}
Now we need a way to switch between the two theme
objects, let's use
a select box for them, let's create a components folder and in there
make a ThemeSelect.js
component, we can worry about refactoring the
App.js
component when I'm not here :
ThemeSelect.js
1import React from 'react'2import styled from 'styled-components'34const Select = styled.select`5 margin: 2rem 0.5rem;6 padding: 0rem 0.5rem;7 font-family: Roboto;8 font-size: 1rem;9 border: 1px solid ${props => props.theme.light};10 box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);11 background: ${props => props.theme.light};12 border-radius: 2px;13`1415export const SelectOpt = styled.option`16 font-family: Roboto;17 font-size: 1rem;18`1920class ThemeSelect extends React.Component {21 render() {22 return (23 <div>24 <Select onChange={e => this.props.handleThemeChange(e)}>25 <SelectOpt value="theme1">Theme 1</SelectOpt>26 <SelectOpt value="theme2">Theme 2</SelectOpt>27 </Select>28 </div>29 )30 }31}3233export default ThemeSelect
You've probably noticed the
onChange={e => this.props.handleThemeChange(e)
event, we're going to
add that method to the App.js
component along with some state to
manage what theme is selected.
App.js
1import React, { Component } from 'react'2import styled, { keyframes, ThemeProvider } from 'styled-components'34import logo from './logo.svg'56import { theme1, theme2 } from './theme/globalStyle'7import ThemeSelect from './components/ThemeSelect'89// our lovely styled-components here1011class App extends Component {12 state = {13 theme: theme1,14 }15 handleThemeChange = e => {16 let theme = e.target.value17 theme === 'theme1' ? (theme = theme1) : (theme = theme2)18 this.setState({ theme })19 }20 render() {21 return (22 <ThemeProvider theme={this.state.theme}>23 <AppWrapper>24 <AppHeader>25 <AppLogo src={logo} alt="logo" />26 <AppTitle>Welcome to React</AppTitle>27 </AppHeader>28 <AppIntro>29 Bootstrapped with <code>create-react-app</code>.30 </AppIntro>31 <AppIntro>32 Components styled with <code>styled-components</code>{' '}33 <EmojiWrapper aria-label="nail polish" />34 </AppIntro>35 <ThemeSelect handleThemeChange={this.handleThemeChange} />36 </AppWrapper>37 </ThemeProvider>38 )39 }40}4142export default App
To summarise what we have done with App.js
here is, add some state
to default to theme1 where the two themes are imported as named
exports of the globalStyle.js
module.
Add a method to handle the change of the ThemeSelect.js
component
handleThemeChange
this is where we can switch between the two
theme
objects.
Let's try it out, we should be able to switch between the two themes we've defined now.
Extending styled-components
So far our app hasn't got many styled-components that are similar but what if we were to add some buttons…
1export const Button = styled.button`2 font-size: 1rem;3 border-radius: 5px;4 padding: 0.25rem 1rem;5 margin: 0 1rem;6 background: transparent;7 color: ${props => props.theme.primary};8 border: 2px solid ${props => props.theme.primary};9 ${props =>10 props.primary &&11 css`12 background: ${props => props.theme.primary};13 color: white;14 `};15`
Here I've added a Button
component to the globalStyle.js
for us to
use in the App.js
component. For the sake of convenience we're going
to add it here, you may find if you have a lot of similar components
that you are reusing throughout your app that it may be a good idea to
add them all to a components
folder.
We can import the Button
as you would any other component and use it
in the module, as we're extending it this means we only need to apply
the specific styles we want for that button. But first in the App.js
component we can specify a normal and a primary button:
1<button>Normal Button</button> <button primary>Primary Button</button>
Now to specify another button with the same css as the imported button we can extend it, like in this example we'll make the button take up 40% of the screen width and make the corners more rounded:
1const BigButt = Button.extend`2 height: 3rem;3 font-size: 2rem;4 width: 40vw;5 border-radius: 30px;6`
Let's also apply the theme for an underline on create-react-app
and
styled-components
by adding in an Underline
styled-component:
1const Underline = styled.span`2 border-bottom: 4px solid ${props => props.theme.secondary};3`
Now we can switch the theme and have it applied to our components using the theme, pretty neat, right?
I have put all of the examples we have gone over here in a working example for you to play around with the theming and styled-components, enjoy.
https://codesandbox.io/s/x26q7l9vyq?from-embed
Want to know more?
A great resource for getting started with styled-components which really helped me is Simon Vrachliotis's egghead.io styled-components playlist which is a great foundation for starting out with styled-components 👌 the first lesson is for pro members but the rest are currently available to watch for free.
There's also the spectrum.chat community and of course Stack Overflow.
Thanks for reading 🙏
If there is anything I have missed, or if you have a better way to do something then please let me know.
Find me on Twitter or Ask Me Anything on GitHub.