Globally Style the Gatsby Default Starter with styled-components v5

Scott Spence - Feb 7 '20 - - Dev Community
Photo by Jeremy Bishop on Unsplash

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:

npx gatsby new gatsby-starter-styled-components
Enter fullscreen mode Exit fullscreen mode

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:

yarn add gatsby-plugin-styled-components \
  styled-components \
  babel-plugin-styled-components \
  styled-reset
Enter fullscreen mode Exit fullscreen mode

Configure gatsby-plugin-styled-components and createGlobalStyle

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

  plugins: [
    `gatsby-plugin-styled-components`,
    `gatsby-plugin-react-helmet`,
    {
Enter fullscreen mode Exit fullscreen mode

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:

mkdir src/theme && touch src/theme/global-style.js
Enter fullscreen mode Exit fullscreen mode

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.

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

export const GlobalStyle = createGlobalStyle`
  ${reset}
`;
Enter fullscreen mode Exit fullscreen mode

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.

import { graphql, useStaticQuery } from 'gatsby';
import PropTypes from 'prop-types';
import React from 'react';
import Header from './header';
import './layout.css';
Enter fullscreen mode Exit fullscreen mode

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.

import { graphql, useStaticQuery } from "gatsby"
import PropTypes from "prop-types"
import React from "react"
import { GlobalStyle } from "../theme/global-style"
import Header from "./header"

const Layout = ({ children }) => {
  // static query for the data here
  return (
    <>
      <Header siteTitle={data.site.siteMetadata.title} />
      <div
        style={{
          margin: `0 auto`,
          maxWidth: 960,
          padding: `0 1.0875rem 1.45rem`,
        }}
      >
        <GlobalStyle />
        <main>{children}</main>
        <footer>
Enter fullscreen mode Exit fullscreen mode

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.

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

export const GlobalStyle = createGlobalStyle`
  ${reset}

  *, *:before, *:after {
    box-sizing: border-box;
  }
  html {
    box-sizing: border-box;
  }
`;
Enter fullscreen mode Exit fullscreen mode

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.

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

export const GlobalStyle = createGlobalStyle`
  ${reset}

  *, *:before, *:after {
    box-sizing: border-box;
  }
  html {
    box-sizing: border-box;
    scroll-behavior: smooth;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 16px;
    color: '#1a202c';
  }
  body {
    line-height: 1.5;
    letter-spacing: 0;
    background-color: '#f7fafc';
  }
`;
Enter fullscreen mode Exit fullscreen mode

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.

import React from 'react';
import Layout from './src/components/layout';
import { GlobalStyle } from './src/theme/global-style';

export const wrapRootElement = ({ element }) => (
  <>
    <GlobalStyle />
    <Layout>{element}</Layout>
  </>
);
Enter fullscreen mode Exit fullscreen mode

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:

import { wrapRootElement as wrap } from './root-wrapper';

export const wrapRootElement = wrap;
Enter fullscreen mode Exit fullscreen mode

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.

yarn add gatsby-plugin-google-fonts
Enter fullscreen mode Exit fullscreen mode

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

{
  resolve: `gatsby-plugin-google-fonts`,
  options: {
    fonts: [
      `cambay\:400,700`,
      `arvo\:400,700`,
      `ubuntu mono\:400,700`,
    ],
    display: 'swap',
  },
},
Enter fullscreen mode Exit fullscreen mode

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.

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

export const theme = {
  font: {
    sans: 'Cambay, sans-serif',
    serif: 'Arvo, sans',
    monospace: '"Ubuntu Mono", monospace',
  },
};

export const GlobalStyle = createGlobalStyle`
  ${reset}

  *, *:before, *:after {
    box-sizing: border-box;
  }
  html {
    box-sizing: border-box;
    scroll-behavior: smooth;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 16px;
    color: '#1a202c';
  }
  body {
    line-height: 1.5;
    letter-spacing: 0;
    background-color: '#f7fafc';
  }
`;
Enter fullscreen mode Exit fullscreen mode

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.

import React from 'react';
import { ThemeProvider } from 'styled-components';
import Layout from './src/components/layout';
import { GlobalStyle, theme } from './src/theme/global-style';

export const wrapRootElement = ({ element }) => (
  <ThemeProvider theme={theme}>
    <GlobalStyle />
    <Layout>{element}</Layout>
  </ThemeProvider>
);
Enter fullscreen mode Exit fullscreen mode

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:

export const GlobalStyle = createGlobalStyle`
  ${reset}

  *, *:before, *:after {
    box-sizing: border-box;
  }
  html {
    box-sizing: border-box;
    scroll-behavior: smooth;
    font-family: ${({ theme }) => theme.font.sans};
    font-size: 16px;
    color: '#1a202c';
  }
  body {
    line-height: 1.5;
    letter-spacing: 0;
    background-color: '#f7fafc';
  }
`;
Enter fullscreen mode Exit fullscreen mode

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.

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';

export const theme = {
  font: {
    sans: 'Cambay, sans-serif',
    serif: 'Arvo, sans',
    monospace: '"Ubuntu Mono", monospace',
  },
  fontSize: {
    xs: '0.75rem',
    sm: '0.875rem',
    base: '1rem',
    lg: '1.125rem',
    xl: '1.25rem',
    '2xl': '1.5rem',
    '3xl': '1.875rem',
    '4xl': '2.25rem',
    '5xl': '3rem',
    '6xl': '4rem',
  },
  fontWeight: {
    hairline: '100',
    thin: '200',
    light: '300',
    normal: '400',
    medium: '500',
    semibold: '600',
    bold: '700',
    extrabold: '800',
    black: '900',
  },
};

export const GlobalStyle = createGlobalStyle`
  ${reset}

  *, *:before, *:after {
    box-sizing: border-box;
  }
  html {
    box-sizing: border-box;
    scroll-behavior: smooth;
    font-family: ${({ theme }) => theme.font.sans};
    font-size: ${({ theme }) => theme.fontSize.lg};
    color: '#1a202c';
  }
  body {
    line-height: 1.5;
    letter-spacing: 0;
    background-color: '#f7fafc';
  }
`;
Enter fullscreen mode Exit fullscreen mode

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.

export const GlobalStyle = createGlobalStyle`
  ${reset}

  *, *:before, *:after {
    box-sizing: border-box;
  }
  html {
    box-sizing: border-box;
    scroll-behavior: smooth;
    font-family: ${({ theme }) => theme.font.sans};
    font-size: ${({ theme }) => theme.fontSize.lg};
    color: ${({ theme }) => theme.colours.grey[900]};
  }
  body {
    line-height: ${({ theme }) => theme.lineHeight.relaxed};
    letter-spacing: ${({ theme }) => theme.letterSpacing.wide};
    background-color: ${({ theme }) => theme.colours.white};
  }
`;
Enter fullscreen mode Exit fullscreen mode

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

mkdir src/components/page-elements
touch src/components/page-elements/h1.js
touch src/components/page-elements/p.js
Enter fullscreen mode Exit fullscreen mode

And add some base styles to those for h1:

import styled from 'styled-components';

export const H1 = styled.h1`
  font-size: ${({ theme }) => theme.fontSize['4xl']};
  font-family: ${({ theme }) => theme.font.serif};
  margin-top: ${({ theme }) => theme.spacing[8]};
  line-height: ${({ theme }) => theme.lineHeight.none};
`;
Enter fullscreen mode Exit fullscreen mode

And the same sort of thing for the p:

import styled from 'styled-components';

export const P = styled.p`
  font-size: ${({ theme }) => theme.fontSize.base};
  margin-top: ${({ theme }) => theme.spacing[3]};
  strong {
    font-weight: bold;
  }
  em {
    font-style: italic;
  }
`;
Enter fullscreen mode Exit fullscreen mode

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:

import { Link } from 'gatsby';
import React from 'react';
import Image from '../components/image';
import { H1 } from '../components/page-elements/h1';
import { P } from '../components/page-elements/p';
import SEO from '../components/seo';

const IndexPage = () => (
  <>
    <SEO title="Home" />
    <H1>Hi people</H1>
    <P>Welcome to your new Gatsby site.</P>
    <P>Now go build something great.</P>
    <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
      <Image />
    </div>
    <Link to="/page-2/">Go to page 2</Link>
  </>
);

export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

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:

import { H1 } from '../components/page-elements/h1';
import { P } from '../components/page-elements/p';
Enter fullscreen mode Exit fullscreen mode

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:

export * from './h1';
export * from './p';
Enter fullscreen mode Exit fullscreen mode

Then when importing the components it will look like this:

import { H1, P } from '../components/page-elements';
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .