Scott Spence

Scott's Digital Garden.

Globally Style the Gatsby Default Starter with styled-components v5

I'm going to go over globally styling the Gatsby Default Starter with styled-components v5, I've done this in the past with styled-components v4 but I've changed my approach and want to document it.

I'll be swapping out the styles included with a CSS reset and adding in global style with the styled-components createGlobalStyle helper function and also adding in the styled-components theme provider.

To start I'll make a new Gatsby project using npx:

1npx gatsby new gatsby-starter-styled-components

Install styled-components dependencies

I'm using yarn to install my dependencies, the backslash is to have the packages on multiple lines instead of one long line:

1yarn add gatsby-plugin-styled-components \
2 styled-components \
3 babel-plugin-styled-components \
4 styled-reset

Configure gatsby-plugin-styled-components and createGlobalStyle

Pop gatsby-plugin-styled-components into the gatsby-config.js file plugins array:

1plugins: [
2 `gatsby-plugin-styled-components`,
3 `gatsby-plugin-react-helmet`,
4 {

Now I'm going to create a global-style.js file in a new directory src/theme then import the styled-components helper function createGlobalStyle into that, this is where the styles for the site are going to live now.

Create the dir and file with the terminal command:

1mkdir src/theme && touch src/theme/global-style.js

The base styles go in here, along with the styled-reset.

To start with I'll create the GlobalStyle object and add in the the reset.

1import { createGlobalStyle } from 'styled-components'
2import reset from 'styled-reset'
3
4export const GlobalStyle = createGlobalStyle`
5 ${reset}
6`

Remove current styling

Remove the current styling that is used in the <Layout> component, it's the import './layout.css' line, I'll also delete the layout.css file as I'm going to be adding in my styles.

1import { graphql, useStaticQuery } from 'gatsby'
2import PropTypes from 'prop-types'
3import React from 'react'
4import Header from './header'
5import './layout.css'

Now the site has the base browser default styles, time to add in my own styles. Before that I'm going to confirm the reset is doing it thing.

Confirm CSS reset

Now I have the base browser styles I'm going to confirm the CSS reset in the <Layout> component. This is where I've removed the previous global styles (layout.css) from.

1import { graphql, useStaticQuery } from "gatsby"
2import PropTypes from "prop-types"
3import React from "react"
4import { GlobalStyle } from "../theme/global-style"
5import Header from "./header"
6
7const Layout = ({ children }) => {
8 // static query for the data here
9 return (
10 <>
11 <Header siteTitle={data.site.siteMetadata.title} />
12 <div
13 style={{
14 margin: `0 auto`,
15 maxWidth: 960,
16 padding: `0 1.0875rem 1.45rem`,
17 }}
18 >
19 <GlobalStyle />
20 <main>{children}</main>
21 <footer>

In the code example here 👆I've I removed the useStaticQuery hook for readability.

reset page

Ok, cool, looks pretty reset to me!

Create the new browser base styles

Time to add in some more styles to the global style. First, the box-sizing reset, take a look at the CSS Tricks post on Box Sizing for a great explanation of why we do this.

1import { createGlobalStyle } from 'styled-components'
2import reset from 'styled-reset'
3
4export const GlobalStyle = createGlobalStyle`
5 ${reset}
6
7 *, *:before, *:after {
8 box-sizing: border-box;
9 }
10 html {
11 box-sizing: border-box;
12 }
13`

Then I'm adding in the smooth scroll html property and some additional styles for base font size and colour along with base line height letter spacing and background colour.

1import { createGlobalStyle } from 'styled-components'
2import reset from 'styled-reset'
3
4export const GlobalStyle = createGlobalStyle`
5 ${reset}
6
7 *, *:before, *:after {
8 box-sizing: border-box;
9 }
10 html {
11 box-sizing: border-box;
12 scroll-behavior: smooth;
13 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
14 font-size: 16px;
15 color: '#1a202c';
16 }
17 body {
18 line-height: 1.5;
19 letter-spacing: 0;
20 background-color: '#f7fafc';
21 }
22`

Place GlobalStyle at the top of the React tree 🌳

I'm adding this as high up the component tree as possible so that the global styles will affect everything that is 'underneath' it.

In the case of the Gatsby Default Starter you'll notice that the <Layout> component wraps a the index.js page, page-2.js and the 404.js page so adding the <GlobalStyle /> component here is a sound option.

There is an alternative to adding it to the layout and that is to use the Gatsby Browser and Gatsby SSR API wrapRootElement.

If I add in the following code to gatsby-browser.js the styles are applied.

1import React from 'react'
2import Layout from './src/components/layout'
3import { GlobalStyle } from './src/theme/global-style'
4
5export const wrapRootElement = ({ element }) => (
6 <>
7 <GlobalStyle />
8 <Layout>{element}</Layout>
9 </>
10)

I also have a double header, that's because the layout component is still wrapping the index page, page 2 and the 404 page. I'll remove the layout component from those locations so I have it in one place to manage.

Make a Root Wrapper to keep things DRY 🌵

I also need to add the same code into gatsby-ssr.js so so that the styles are rendered on the server when the site is built.

Rather than have the code duplicated in the two files I'll create a root-wrapper.js file (you can call it what you like!) and add it to the root of the project. I'll import that into both the gatsby-browser.js and gatsby-ssr.js files:

1import { wrapRootElement as wrap } from './root-wrapper'
2
3export const wrapRootElement = wrap

Global fonts with gatsby-plugin-google-fonts

Onto the main reason for this post, with the v5 release of styled-components the use of @imports in createGlobalStyle isn't working, (that approach is detailed here) it's recommended that you embed these into your HTML index file, etc.

NOTE: At this time we recommend not using @import inside of createGlobalStyle. We're working on better behavior for this functionality but it just doesn't really work at the moment and it's better if you just embed these imports in your HTML index file, etc.

But! As I'm using Gatsby, of course, "There's a Plugin For That™️" so I'm going to use gatsby-plugin-google-fonts for this, I'm using this in place of gatsby-plugin-web-font-loader because it uses display=swap.

1yarn add gatsby-plugin-google-fonts

Configure, I'll add three fonts, sans, sans serif and monospace to the Gatsby plugin array in gatsby-config.js:

1{
2 resolve: `gatsby-plugin-google-fonts`,
3 options: {
4 fonts: [
5 `cambay\:400,700`,
6 `arvo\:400,700`,
7 `ubuntu mono\:400,700`,
8 ],
9 display: 'swap',
10 },
11},

I can now use these fonts throughout my site.

styled-components Theme provider

The styled-components ThemeProvider is a great solution for managing your styles throughout a project.

Part of the inspiration for my approach came from Sid's talk at React Advanced which I wrote about and part from watching the Tailwind CSS courses from Adam Wathan on Egghead.io check out the playlist here: Introduction to Tailwind and the Utility first workflow

With the ThemeProvider I can have things like colours, sizes, font weights in one place so that there is a consistent set of presets to choose from when styling.

In the global-style.js file I'm creating a theme object to hold all the values for the theme.

For the font I'll add in the types I defined in the Gatsby config, for serif, sans serif and monospace.

1import { createGlobalStyle } from 'styled-components'
2import reset from 'styled-reset'
3
4export const theme = {
5 font: {
6 sans: 'Cambay, sans-serif',
7 serif: 'Arvo, sans',
8 monospace: '"Ubuntu Mono", monospace',
9 },
10}
11
12export const GlobalStyle = createGlobalStyle`
13 ${reset}
14
15 *, *:before, *:after {
16 box-sizing: border-box;
17 }
18 html {
19 box-sizing: border-box;
20 scroll-behavior: smooth;
21 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22 font-size: 16px;
23 color: '#1a202c';
24 }
25 body {
26 line-height: 1.5;
27 letter-spacing: 0;
28 background-color: '#f7fafc';
29 }
30`

Now I'll need to add the <ThemeProvider> high up in the React render tree, same as with the global style, I'll add it to the root-wrapper.js file.

1import React from 'react'
2import { ThemeProvider } from 'styled-components'
3import Layout from './src/components/layout'
4import { GlobalStyle, theme } from './src/theme/global-style'
5
6export const wrapRootElement = ({ element }) => (
7 <ThemeProvider theme={theme}>
8 <GlobalStyle />
9 <Layout>{element}</Layout>
10 </ThemeProvider>
11)

When I want to pick a font type to use in the project I can use the theme object and pick out the desired type.

Like setting the HTML font-family to sans serif:

1export const GlobalStyle = createGlobalStyle`
2 ${reset}
3
4 *, *:before, *:after {
5 box-sizing: border-box;
6 }
7 html {
8 box-sizing: border-box;
9 scroll-behavior: smooth;
10 font-family: ${({ theme }) => theme.font.sans};
11 font-size: 16px;
12 color: '#1a202c';
13 }
14 body {
15 line-height: 1.5;
16 letter-spacing: 0;
17 background-color: '#f7fafc';
18 }
19`

The base font is now set to Cambay, why stop there though, I'll bring in some fonts sizes and font weights from the Tailwind full config and add them to the theme object.

1import { createGlobalStyle } from 'styled-components'
2import reset from 'styled-reset'
3
4export const theme = {
5 font: {
6 sans: 'Cambay, sans-serif',
7 serif: 'Arvo, sans',
8 monospace: '"Ubuntu Mono", monospace',
9 },
10 fontSize: {
11 xs: '0.75rem',
12 sm: '0.875rem',
13 base: '1rem',
14 lg: '1.125rem',
15 xl: '1.25rem',
16 '2xl': '1.5rem',
17 '3xl': '1.875rem',
18 '4xl': '2.25rem',
19 '5xl': '3rem',
20 '6xl': '4rem',
21 },
22 fontWeight: {
23 hairline: '100',
24 thin: '200',
25 light: '300',
26 normal: '400',
27 medium: '500',
28 semibold: '600',
29 bold: '700',
30 extrabold: '800',
31 black: '900',
32 },
33}
34
35export const GlobalStyle = createGlobalStyle`
36 ${reset}
37
38 *, *:before, *:after {
39 box-sizing: border-box;
40 }
41 html {
42 box-sizing: border-box;
43 scroll-behavior: smooth;
44 font-family: ${({ theme }) => theme.font.sans};
45 font-size: ${({ theme }) => theme.fontSize.lg};
46 color: '#1a202c';
47 }
48 body {
49 line-height: 1.5;
50 letter-spacing: 0;
51 background-color: '#f7fafc';
52 }
53`

I'll add the base font at .lg (1.125rem), I'll also add in line height and line spacing defaults but I'll save adding the whole config here to save you a codewall, you get the idea though, right?

Here's the rest of the GlobalStyle with defaults applied.

1export const GlobalStyle = createGlobalStyle`
2 ${reset}
3
4 *, *:before, *:after {
5 box-sizing: border-box;
6 }
7 html {
8 box-sizing: border-box;
9 scroll-behavior: smooth;
10 font-family: ${({ theme }) => theme.font.sans};
11 font-size: ${({ theme }) => theme.fontSize.lg};
12 color: ${({ theme }) => theme.colours.grey[900]};
13 }
14 body {
15 line-height: ${({ theme }) => theme.lineHeight.relaxed};
16 letter-spacing: ${({ theme }) => theme.letterSpacing.wide};
17 background-color: ${({ theme }) => theme.colours.white};
18 }
19`

Shared Page Elements

The current page is still missing basic styles for h1 and p so I'm going to create those in a new directory src/components/page-elements

1mkdir src/components/page-elements
2touch src/components/page-elements/h1.js
3touch src/components/page-elements/p.js

And add some base styles to those for h1:

1import styled from 'styled-components'
2
3export const H1 = styled.h1`
4 font-size: ${({ theme }) => theme.fontSize['4xl']};
5 font-family: ${({ theme }) => theme.font.serif};
6 margin-top: ${({ theme }) => theme.spacing[8]};
7 line-height: ${({ theme }) => theme.lineHeight.none};
8`

And the same sort of thing for the p:

1import styled from 'styled-components'
2
3export const P = styled.p`
4 font-size: ${({ theme }) => theme.fontSize.base};
5 margin-top: ${({ theme }) => theme.spacing[3]};
6 strong {
7 font-weight: bold;
8 }
9 em {
10 font-style: italic;
11 }
12`

Then it's a case of replacing the h1's and p's in the project to use the styled components.

Here's the index.js file as an example:

1import { Link } from 'gatsby'
2import React from 'react'
3import Image from '../components/image'
4import { H1 } from '../components/page-elements/h1'
5import { P } from '../components/page-elements/p'
6import SEO from '../components/seo'
7
8const IndexPage = () => (
9 <>
10 <SEO title="Home" />
11 <H1>Hi people</H1>
12 <P>Welcome to your new Gatsby site.</P>
13 <P>Now go build something great.</P>
14 <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
15 <Image />
16 </div>
17 <Link to="/page-2/">Go to page 2</Link>
18 </>
19)
20
21export default IndexPage

Export all your styled-components from an index file

As the amount of page elements grows you may want to consider using an index.js file instead of having an import for each individual component you can import from one file.

Let's take a quick look at that, so let's say I import the h1 and p into a file, it'll looks something like this:

1import { H1 } from '../components/page-elements/h1'
2import { P } from '../components/page-elements/p'

If you have several elements your using in the file the imports could get a bit cluttered.

I've taken to creating an index.js file that will export all the components, like this:

1export * from './h1'
2export * from './p'

Then when importing the components it will look like this:

1import { H1, P } from '../components/page-elements'

That's it for this one!

Here's a video detailing the process 📺

Thanks for reading 🙏

Please take a look at my other content if you enjoyed this.

Follow me on Twitter or Ask Me Anything on GitHub.

Resources