DEV Community

Richard Haines
Richard Haines

Posted on

How to make a gatsby ecommerce theme. Part 1

We're going to go from start to finish and create a gatsby theme that you can deploy as an npm package. In the first part we will cover the base setup of the project.

This is part 1 in a series of tutorials. The format is step by step.

So where to start? You have an idea, you want to create an ecommerce theme for all the world to use. In this tutorial we are going to create a new gatsby theme from scratch, handle payments using snipcart, store our data with sanity.io and style it with theme-ui. Its going to be dope 😎

There are a few pre-requisits, Im going to assume you know how to use Gatsby and React to a certain degree, the rest we will walk through.

We are going to be building this from scratch so lets start by creating our new project

mkdir gatsby-theme-fashion
cd gatsby-theme-fashion

Cool beans, so lets now create our project. When working with gatsby themes i like to use yarn workspaces, this allows us to keep our theme and an example site in a monorepo with yarn controlling the dependencies between them. Lets create our projects root, we'll use the -y flag to auto fill the package.json stuff as we will be removing it all anyway.

yarn init -y

Now that we have a package.json in our folder we can open our code editor and remove the auto filled stuff and add our own. We want to add the workspaces key and add our project folders. There are many ways of doing this and people have different opinions about which way is best. This is my preferred method.

package.json

{
  "private": true,
  "workspaces": [
    "demo",
    "packages/*"
  ]
}

We want the project to be private as we wont publish it and also workspaces wont work if we don't set this. We have also specified that we will have one folder for our demo project and another which will hold our packages, or our theme(s).

Create the demo and packages folders

mkdir demo
mkdir packages

We'll start with our theme and hop over to the demo site a bit later on. Lets move into the packages folder and create our themes folder and initialize the project, following yarns instructions for setup as we go. I like to set the version to 0.0.1 from the start as during development we will be publish our package and when it comes time to release it in the wild its going to look strange if our version number is 54.9.7 or something. Keep it at the patches in development until release.

cd packages
mkdir gatsby-theme-fashion
cd gatsby-theme-fashion
yarn init

Now that we have our theme project installed lets add the base packages we are going to need. For us those are gatsby, react, react-dom, theme-ui, gatsby-plugin-theme-ui @emotion/core and @mdx-js/react. Lets also add prettier so that all our code looks pretty once we save it! 😇

yarn add gatsby react react-dom theme-ui gatsby-plugin-theme-ui @emotion/core @mdx-js/react gatsby-plugin-google-fonts
yarn add prettier -D

Niiiice! Cool, now lets add some scripts to our package.json for building, cleaning and making stuff pretty.

package.json

  "scripts": {
    "dev": "gatsby develop",
    "build": "gatsby build",
    "clean": "gatsby clean",
    "pretty": "prettier --write \"src/**/*js\""
  },

You can give the keys whatever name makes sense to you. We'll be mostly using dev, clean and pretty. We could set up a pre commit hook to run pretty when we commit code, you can look that up, for this example we will skip it. I quite like typing out yarn pretty and watching all my code transform. You can also set it up to run on save in vscode, again, that is something for you to google.

We want to add gatsby, react and react-dom as peer dependencies so that the end user has to install them.

package.json

  "peerDependencies": {
    "gatsby": "^2.19.8",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },

Now we need to add our gatsby-config.js file and add the plugins we have installed for our theme. Its important to add the gatsby-plugin-theme-ui plugin at the end as it will be overriden by the theme consumer if they have it installed also.

gatsby-config.js

module.exports = {
    plugins: [
        {
            resolve: 'gatsby-plugin-google-fonts',
            options: {
              fonts: [
                'Muli',
                'Open Sans',
                'source sans pro\:300,400,400i,700' 
              ]
            }
        },
          'gatsby-plugin-theme-ui'
    ]
}

We have used gatsby-plugin-google-fonts so that we can easily install some nice fonts from google. Feel free to choose your own.

I should add at this point that the versions are explicit to this tutorial, everything should work regardless, that being said it can be that some breaking changes are introduced at some point in the future so yours may differ from mine, if you have any problems check the changelog from my version to the version you are using to debug the problem.

Ok so lets add some folders and files to our project.

touch src/pages
touch src/components
touch src/pages/index.js

We also want to create an index.js file at the root of out theme as thats what gatsby is looking for

touch index.js

Add a comment to this file so it has some content and we know whats going on. Later on we can if we like export some of our theme components from this file for the end use to import.

Sweet! We've got a really basic theme setup, it doesn't do anything right now, first we want to setup our demo site and link it to our theme. The magic of yarn workspaces!

cd ..
cd demo
yarn init
yarn add gatsby react react-dom dotenv

Your demo sites package.json should look something like this:

{
  "name": "demo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "gatsby develop",
    "build": "gatsby build",
    "clean": "gatsby clean"
  },
  "dependencies": {
    "dotenv": "^8.2.0",
    "gatsby": "^2.19.8",
    "gatsby-theme-fashion": "*",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  }
}

We have added dotenv so that we can access the environment variables later on. We are also telling our demo site to use any version of our theme it finds. We have also added our scripts so that we can actually run the thing!

Next create a new file called gatsby-config.js at our demo projects root and add our theme as a plugin.

gatsby-config.js

module.exports = {
  plugins: ["gatsby-theme-fashion"]
};

Right now we don't have any options that our theme requires so this will do. Then we run yarn from the root of our project to link it all up and we can then check if its all cool

yarn
yarn workspaces info

We should see the following output indicating that our demo site is linked to our theme:

yarn workspaces v1.21.1
{
  "demo": {
    "location": "packages/demo",
    "workspaceDependencies": [
      "gatsby-theme-fashion"
    ],
    "mismatchedWorkspaceDependencies": []
  },
  "gatsby-theme-fashion": {
    "location": "packages/gatsby-theme-fashion",
    "workspaceDependencies": [],
    "mismatchedWorkspaceDependencies": []
  }
}
Done in 0.06s.

BOOM!

So now might be a good time to commit our work. Go to github and login to your account and create a new repository called gatsby-theme-fashion. Then from the root of our project....

WHOA there ✋ Lets think about this for a second, we have just installed a bunch of packages, which means we have a node_modules folder at our root and in each project, thats a hell of a lot of files that we do not want git to see. Lets create our gitignore file at our projects root.

touch .gitignore

Then add the following to it:

node_modules/
.env.*
public/
.cache/

Now i'll be honest, im not sure if you need to do this but i do it anyway, lets copy that file to our theme and demo projects.

Ok, nice! Now that thats done lets initialize our project with git and commit our work. You can get your remote repository url from the repository page you created.

git init
git add .
git commit -m "My first commit"
git remote add origin <remote repository url>
git remote -v
git push origin master

So lets recap what we have done so far:

  • Setup a project
  • Added our demo and theme projects
  • Linked our demo to our theme
  • Created a repository and committed our work

Now would be a good time to add some content and check that our theme is working. Head into your theme projects index.js file located inside src/pages folders and add a generic component to display an h1 that will render from our demo site as the imported theme.

import React from 'react'

export default () => <h1>Hello im coming at you from the theme!!<h1>

Go into your demo project and run gatsby dev. You should see a white page with the h1 text at the top left of the page, hurrah 🥳

Now that we have the base setup complete we can start thinking about what we want to have in our theme. As this is an ecommerce theme there are some basic things that we should consider before we continue. There will be a lot to add so lets get started!

  • Navbar
  • Landing/home page
    • Hero image
    • Showcase of products
    • Blog snippets
    • Instagram feed
    • Contact section
    • About section
  • Products page
  • Blog page
    • Blog posts page

These are some of the basic thing we expect an ecommerce site to have. There are of course a lot more thing we could add but lets keep it simple for our example theme. We will be keeping our layout fairly simple with a navbar at the top followed by a hero image and a section explaining about the store. Then a showcase of our top 3 most popular products followed by our Instagram feed and finishing off with our contact form in our footer.

We can begin by creating all the necessary pages:

cd packages/gatsby-theme-fashion/src/pages
touch products.js
touch blog.js

Theme-ui will enable our end user to override some of our style with ease through shadowing our theme file which will hold all of our colours, fonts and spacings. In order to get started with adding our themeable variables we need to shadow the index.js file form the gatsby-plugin-theme-ui package in our src folder. See Theme-ui for more details.

I often start with the sites basic layout. There are a couple of ways to do this. We could create a layout component in our components folder with a css reset and import that into all or our components/pages but there is an easier way utilizing gatsby-plugin-layout.

Lets add that plugin

yarn add gatsby-plugin-layout

Then we need to add it to our gatsby-config.js file:

gatsby-config.js

module.exports = {
    plugins: [
        {
            resolve: 'gatsby-plugin-google-fonts',
            options: {
              fonts: [
                'Muli',
                'Open Sans',
                'source sans pro\:300,400,400i,700' 
              ]
            }
        },
        {
            resolve: 'gatsby-plugin-layout',
            options: {
              component: require.resolve('./src/layouts/index.js'),
            },
        },
          'gatsby-plugin-theme-ui'

    ]
}

Create a layouts folder inside your src folder and add an index.js file. This will be our base layout for our site.

Layout

/** @jsx jsx */
import { jsx } from "theme-ui";
import React from "react";
import { Global, css } from "@emotion/core";

const Layout = ({ children }) => {
  return (
    <>
      <Global
        styles={css`
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
          }
          body {
            scroll-behavior: smooth;
            overflow-y: scroll;
            -webkit-overflow-scrolling: touch;
            width: 100%;
            overflow-x: hidden;
          }
        `}
      />
      <div>
        {children}
      </div>
    </>
  );
};

export default Layout;

This doesn't do much apart from reset some css and render the children within it. Lets create a basic grid layout using css grid. We are going to be using the theme-ui sx prop to add styling to our elements.

First up create some grid area strings as constants to be used in our grid. You can add these to a separate file if you wish but we will be adding them to the layout file in this example.

const PhoneTemplateAreas = `
  'nav      nav     nav     nav'
  'main     main    main    main'
  'footer   footer  footer  footer'
`;

const TabletTemplateAreas = `
  'nav      nav     nav     nav     nav     nav'
  'main     main    main    main    main    main'
  'footer   footer  footer  footer  footer  footer'
`;

const DesktopTemplateAreas = `
  '.    nav      nav     nav     nav     nav     nav    .'
  '.    main     main    main    main    main    main   .'
  '.    footer   footer  footer  footer  footer  footer .'
`;

Then we can add these to the div that wraps our returned children in the layout component.

Layout

/** @jsx jsx */
import { jsx } from "theme-ui";
import React from "react";
import { Global, css } from "@emotion/core";

const PhoneTemplateAreas = `
  'nav      nav     nav     nav'
  'main     main    main    main'
  'footer   footer  footer  footer'
`;

const TabletTemplateAreas = `
  'nav      nav     nav     nav     nav     nav'
  'main     main    main    main    main    main'
  'footer   footer  footer  footer  footer  footer'
`;

const DesktopTemplateAreas = `
  '.    nav      nav     nav     nav     nav     nav    .'
  '.    main     main    main    main    main    main   .'
  '.    footer   footer  footer  footer  footer  footer .'
`;

const Layout = ({ children }) => {
  return (
    <>
      <Global
        styles={css`
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
          }
          body {
            scroll-behavior: smooth;
            overflow-y: scroll;
            -webkit-overflow-scrolling: touch;
            width: 100%;
            overflow-x: hidden;
          }
        `}
      />
      <div
        sx={{
          display: "grid",
          gridTemplateRows: "auto",
          gridTemplateColumns: [
            "repeat(4, 1fr)",
            "repeat(6, 1fr)",
            "repeat(8, 1fr)"
          ],
          gridTemplateAreas: [
            PhoneTemplateAreas,
            TabletTemplateAreas,
            DesktopTemplateAreas
          ],
          padding: "0 1em"
        }}
      >
        {children}
      </div>
    </>
  );
};

export default Layout;

Lets breakdown whats going on here. I used to use Emotion css plus theme-ui to design and style my components, you can read about my experience here CSS grid with Theme-ui. Basically theme-ui allows us to specify breakpoints using an array syntax, so [mobile, tablet, desktop]. Pretty cool huh! Now we need to create our nav, main and footer components. All the sites amin content will live inside the main component, by using gatsby-plugin-layout we can just import main as a wrapper to tell gatsby that whatever component should be rendered inside this grid area.

Create a layout folder inside our components folder and add these three components:

Header

/** @jsx jsx */
import { jsx } from "theme-ui";

const Header = props => (
  <header
    sx={{
      gridArea: "nav",
      padding: "1em",
      backgroundColor: "background",
      color: "text",
      height: "100%",
      padding: [null, "2em", "2em"],
      paddingTop: ["2em", null, null]
    }}
  >
    {props.children}
  </header>
);

export default Header;

Main

/** @jsx jsx */
import { jsx } from "theme-ui";

const Main = props => (
  <main
    sx={{
      gridArea: "main",
      backgroundColor: "background",
      minHeight: "calc(100vh - 60px)",
      paddingTop: ["2em", "60px", "60px"]
    }}
  >
    {props.children}
  </main>
);

export default Main;

Footer

/** @jsx jsx */
import { jsx } from "theme-ui";

const Footer = () => (
  <footer
    sx={{
      gridArea: "footer",
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
      justifyContent: "center",
      backgroundColor: "background",
      color: "text"
    }}
  >
    <p
      sx={{
        color: "text",
        fontFamily: "body",
        fontSize: ["0.7em", "0.8em", "1em"],
        letterSpacing: "text",
        fontWeight: 400,
        margin: "1em auto"
      }}
    >
      This is my footer!
    </p>
  </footer>
);

export default Footer;

You may be wondering where the background colour is coming from... When using theme-ui we import all of our styles from one main index file. This file can be shadowed by the theme consumer and thus they can override whatever styles we have in there to change the look of the theme. Lets create that now.

Inside the src folder create a new folder called gatsby-plugin-theme-ui, inside that folder create a new file named index.js and add the following:

Our theme styles

export default {
  fonts: {
    body: "Open Sans",
    heading: "Muli"
  },
  fontWeights: {
    body: 300,
    heading: 400,
    bold: 700
  },
  lineHeights: {
    body: "110%",
    heading: 1.125,
    tagline: "100px"
  },
  letterSpacing: {
    body: "2px",
    text: "5px"
  },
  colors: {
    text: "#FFFfff",
    background: "#121212",
    primary: "#000010",
    secondary: "#E7E7E9",
    secondaryDarker: "#545455",
    accent: "#DE3C4B"
  },
  breakpoints: ['40em', '56em', '64em']
};

These values can be accessed via the sx prop as seen above.

Lets add them to our layout to complete this part of the tutorial.

Layout

/** @jsx jsx */
import { jsx } from "theme-ui";
import React from "react";
import { Global, css } from "@emotion/core";
import Header from "../components/layout/header";
import {
  PhoneTemplateAreas,
  TabletTemplateAreas,
  DesktopTemplateAreas
} from "./../window/breakpoints";
import Footer from "../components/layout/footer";

const Layout = ({ children }) => {
  return (
    <>
      <Global
        styles={css`
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
          }
          body {
            scroll-behavior: smooth;
            overflow-y: scroll;
            -webkit-overflow-scrolling: touch;
            width: 100%;
            overflow-x: hidden;
          }
        `}
      />
      <div
        sx={{
          display: "grid",
          gridTemplateRows: "auto",
          gridTemplateColumns: [
            "repeat(4, 1fr)",
            "repeat(6, 1fr)",
            "repeat(8, 1fr)"
          ],
          gridTemplateAreas: [
            PhoneTemplateAreas,
            TabletTemplateAreas,
            DesktopTemplateAreas
          ],
          padding: "0 1em"
        }}
      >
        <Header>
          navbar will go here
        </Header>
        {children}
        <Footer />
      </div>
    </>
  );
};

export default Layout;

So lets recap what we have done so far:

  • Setup a project
  • Added our demo and theme projects
  • Linked our demo to our theme
  • Created a repository and committed our work
  • Added a site layout
  • Added our theme styles

In the next part we will start adding our main content components and a backend to store our products. 😎

Top comments (2)

Collapse
 
okslutsiv profile image
Oksana

Thanks for sharing, looking forward to the next part.
It looks like the link to the 'CSS grid with Theme-UI' is broken. Can you fix this, pls?

Collapse
 
studio_hungry profile image
Richard Haines

Hey Oksana! Thank you for reading and your kind words! Sorry about the link being broken, i will look at fixing that. Here it is until then: richardhaines.dev/css-grid-with-th...