Making an online store with Gatsby & Shopify: Part 1 - Building the Layout

August 28th, 2020

Disclaimer: this walkthrough assumes a basic familiarity with React. Some experience with GatsbyJS is also ideal.

Gatsby and Shopify are a great combination for building fast online stores. This post is going to focus primarily on code, but if you would like to find out more about why Gatsby and Shopify are such a great pairing for ecommerce, I have written about this at length here.

In this Part 1 one of the two tutorial we are going to hold off on adding the Shopify data/products for now and focus on building out the "skeleton" of our ecommerce site.

In Part 2 we will get our hands dirty with Shopify, but for now we want to set the groundwork by creating a clean, functional, relatively unopinionated ecommerce starter that includes a header/navigation, footer, overlay, and a nice cart side drawer that slides smoothly on and off the screen.

By the end, the we will have a reusable starter that fits one of the more popular and successful industry standards for ecommerce UI/UX!

Getting started

I want to build this from the ground up so I am getting started with one of the most bare-bones Gatsby starter themes - the gatsby-starter-hello-world starter is a good choice because it is pretty much a blank slate.

I am using yarn but feel free to use npm or any package manager of your choice.

Also, I am calling this project ecommerce-example but call it whatever you want.

Make sure you are inside the directory where you want the project folder to live, then run this in your terminal:

gatsby new ecommerce-example

You should now have a directory that looks like this:

Shopify Tutorial Img One

Now if we run yarn start in the terminal and go to localhost:8000 in our browser, we should see this blank page with a "Hello world!" staring back at us:

Shopify Tutorial Img 2

Adding some global CSS

In order to make sure there are no funky margin or box-sizing issues from any browser defaults, let's add some global CSS to set some presets. We can inject this CSS using the gatsby-browser.js file which gives us the ability to add things to our app at build time (among many other things).

Create this gatsby-browser.js file in the root of your project if it does not exist already. Then, add this one single line to it:

import './src/styles/global.css';

Here we imported a file that does not exist yet - let's create it now.

Create a folder inside the src directory called styles, then inside this folder add a file called global.css - inside this file, add this CSS:

src/styles/global.css html, body { height: 100%; box-sizing: border-box; margin: 0; padding: 0; }

This will ensure that there is no margin unexpectedly added to the html or body at the start, and that box-sizing defaults to border-box.

Making a basic layout

Let's make a basic layout using a Gatsby plugin that makes it easy to wrap our entire app in a layout component that will eventually include a header, footer, and sliding cart drawer: gatsby-plugin-layout

Use yarn or npm to add the plugin to your project:

yarn add gatsby-plugin-layout

The way this plugin works is we explicitly tell it the name of the file that contains the code for our layout component - it then conveniently applies this layout to every page for us.

I think it only makes sense to call this component Layout.jsx - so even though we have not created this file just yet (we will soon), in your gatsby-config.js file in the root directory, add this to the plugins array like so:


module.exports = { plugins: [ { resolve: `gatsby-plugin-layout`, options: { component: require.resolve(`./src/components/Layout/Layout.jsx`), }, }, ], }

Before we build the Layout component, let's install one more Gatsby plugin, gatsby-plugin-root-import, that will make importing components and other files easier across the project by making it so we don't have to write annoyingly long relative paths every time we want to import a local file (you will see this in action shortly):

yarn add gatsby-plugin-root-import

Now, update your gatsby-config.js file to look like this:

const path = require('path'); <-- new!

module.exports = { plugins: [ { resolve: `gatsby-plugin-layout`, options: { component: require.resolve(`./src/components/Layout/Layout.jsx`), }, }, { resolve: 'gatsby-plugin-root-import', <-- new plugin! options: { src: path.join(__dirname, 'src'), '@static': path.join(__dirname, 'static'), '@assets': path.join(__dirname, 'src/assets'), '@components': path.join(__dirname, 'src/components'), }, }, ], }

Note that we also added root imports for what will eventually be our static and assets folders while we were at it.

Now make a folder called components in your src directory - then in this src/components folder make another folder called Layout.

Then, inside this folder, we finally create our Layout.jsx file where our actual code for the layout lives.

For now I am just going to make the Layout.jsx component return some text.

In order for this plugin to work, the Layout component must take children as props, and then inside your return statement you return children according to how you want the layout to wrap around the pages on your site - this is because the children prop represents the rest of the page content that your layout component will be automatically wrapped around (don't worry too much about this yet, more on this soon).

Your project should now look something like this:

Shopify Tutorial Img Three

Note that I have also added a static folder and a src/assets folder - create these now if you do not have them already.

Now, if you run yarn start again and open the browser to localhost:8000, you can see that the "This is a layout!" text appears above the "Hello world!" text on the homepage (aka the index.js file inside src/pages).

This is because right now, our Layout.jsx component is effectively telling our app:

"Hey, for every single page of this site, place text that reads 'This is a layout!' at the very top."

That's right - for EVERY page - this is the power of gatbsy-plugin-layout.

But, of course, this is silly. We did not install this plugin just so we could show random sentences on each page - we installed it so we can easily show a header and footer on each page (among other things we might want on every page).

So what we are ultimately going to do is make a Header component and a Footer component separately and then import them into our Layout component.

But first, I am going to take a quick detour to install a package called theme-ui that I am going to use for styling this project - feel free to skip this part and style your project however you want (styled-components, css modules, etc.), but lately I prefer theme-ui for my projects because it makes global theming and media queries exceptionally easy.

Adding gatsby-plugin-google-fonts

Before we add theme-ui and start theming, let's quickly add this plugin that allows us to use Google fonts seamlessly inside our project:

yarn add gatsby-plugin-google-fonts

Then configure it by adding it to the plugins array in gatsby-config.js like so:

gatsby-config.js ... plugins: [ ... { resolve: `gatsby-plugin-google-fonts`, options: { fonts: [ `poppins\:300,400,600,700`, ], display: 'swap' } }, ... ], ...

This gives us access to the Poppins font from Google Fonts in a few different font weights - I am choosing Poppins for my project, but this works for all Google fonts so feel free to import and use whatever fonts you like!

Adding theme-ui

Gatsby conveniently has a plugin for theme-ui called gatsby-plugin-theme-ui.

Installing it is as easy as it gets - just pull up your terminal and run:

yarn add theme-ui gatsby-plugin-theme-ui

and then add just this one new line to the plugins array in your gatsby-config.js like so:


... plugins: [ 'gatsby-plugin-theme-ui', ...

In order to use this plugin, you need to create a folder called gatsby-plugin-theme-ui inside your src folder.

Then inside this new folder, add a file called index.js - this is where our global style sheet is going to live.

To read more about theming with theme ui, there is great info in the their docs - I especially recommend looking at the awesome way they handle media queries/breakpoints with arrays.

In the mean time, I am going to be using the code below as the global theme:


export default { breakpoints: ['480px', '600px', '768px', '992px', '1280px', '1440px'], space: [0, 2, 4, 6, 12, 18, 24, 30, 36, 48, 60, 72, 96, 120, 240, 480], fonts: { body: 'poppins', heading: 'poppins', }, fontSizes: [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 48, 64, 96], fontWeights: { body: 400, heading: 700, bold: 700, }, colors: { text: '#000', background: '#fff', primary: '#000', secondary: 'lightgrey', muted: '#f6f6f6', }, text: { heading: { fontFamily: 'heading', fontWeight: 'heading', }, }, styles: { root: { color: '#000', background: '#fff', fontFamily: 'poppins', m: 0, p: 0, }, }, }

Ok, we are done with all the boring setup for the theme for now - let's start making some actual components for the layout.

Making a Header component

Make a new folder in src/components called Header. In this folder make two files: Header.jsx and index.js.

Wait, what's with that index.js file?

Well, remember when we added those root imports in our gatsby-config.js? That was so we could export all of our components easily without writing out their entire relative paths. In order for us to be able to do this, we make the actual component in the .jsx file, but we have to export it as a named export inside the index.js file.

So, in this case, the index.js file inside the src/components/Header looks like this:

Shop Tutorial Img Four

From now on we will stick to this practice whenever we make a component in src/components - you will see the benefits of doing this soon, especially as the codebase grows.

Right now we are pretty much just going to use the Header.jsx as a placeholder until we make and import our slightly more specific navigation component. For now, I'm leaving it like this:

src/components/Header/Header.jsx import React from 'react'; import { Box } from 'theme-ui';

export const Header = () => { return ( <Box data-comp={Header.displayName}>This is the Header</Box> ); };

Header.displayName = 'Header';

Note #1 : we are using theme ui's "Box" component instead of the regular <div> that most are used to - hopefully this is not too jarring but I will be using these theme ui components throughout this project because this allows us to apply the theme styling to them.

Note #2: as a good practice I like to name my components using the data-comp attribute and React's displayName property - this makes life way easier later on when debugging in DevTools by allowing you to quickly identify which component you are looking at on the page. Doing this has saved me many, many times - especially when your codebases grow larger in complexity and begin to sprawl.

Now to make the Header component live inside the Layout component we already made, import the Header component into the Layout component like so:

src/components/Layout/Layout.jsx import React from 'react'; import { Box } from 'theme-ui';

import { Header } from '@components/Header'; <-- new import!

const Layout = ({ children }) => { return ( <Box data-comp={Layout.displayName}> <Header /> <-- new! {children} </Box> ); };

Layout.displayName = 'Layout'; export default Layout;


Now let's make a super simple DesktopNavigation component to go inside Header.jsx - for the record, I am going to keep the UI super simple so we can focus on ecommerce related aspects.

I am calling it DesktopNavigation in case we want to come back later and make a separate navigation for mobile, but it will work on both mobile and desktop for now.

Make a folder src/components/DesktopNavigation and inside this folder create the DesktopNavigation.jsx and index.js files. The navigation for now is just going to be a grey bar that takes up the full width of the page:

src/components/DesktopNavigation/DesktopNavigation.jsx /** @jsx jsx */ <-- this is so we can use theme-ui! import { Box, Flex, jsx } from 'theme-ui'

export const DesktopNavigation = () => { return ( <Flex sx={{ width: '100%', height: '60px', justifyContent: 'center', alignItems: 'center', color: 'white', bg: 'primary', }} data-comp={DesktopNavigation.displayName} > <Box>THE BRAND</Box> </Flex> ) }

DesktopNavigation.displayName = 'DesktopNavigation'

Note: in the interest of time and avoiding repetition, from now on I am going to stop showing the step where we add the index.js file to each new component folder we make. From here on out, the creation of this file and the export statement inside of it will be implied - so don't forget to do it!

You will notice that I have a special "jsx pragma" at the very top of the file - this is needed to use the theme-ui plugin.

Similarly, we have imported some of the most common, primitive components from theme-ui - Box is more or less a <div>, and Flex, as you can probably guess, is the equivalent of a <div> with its CSS display property set to "flex." The jsx imported from theme-ui is to ensure that jsx is in scope, which is necessary for using the theme.

Because this is already being imported into our Header component, if you run yarn start and check it out in the browser, you will hopefully see this:

Shop Tutorial Asset Five

Using the Gatsby Link component

As is best practice, we want to make "The Brand" in the middle of our navigation bar be a link to the homepage when you click on it. This means wrapping it in a link.

If I was lazy I would just wrap it in a regular link and add the necessary styling to prevent the link from being highlighted and underlined by default.

But I want to be a bit more professional here and do a solid to my future self, so I'm going to make my own <UnstyledLink /> component that is unstyled by default so I can use it throughout the project without having to repeat the same styling every time.

To accomplish this I made a folder called global inside the src directory. This is where I will store common custom components that I might want to reuse throughout the site.

Then we need to go to gatsby-config.js and make sure we add "@global" as a new root import to our root import plugin so we can import it more easily:

gatsby-config.js module.exports = { plugins: [ ... { resolve: 'gatsby-plugin-root-import', options: { src: path.join(__dirname, 'src'), '@static': path.join(__dirname, 'static'), '@assets': path.join(__dirname, 'src/assets'), '@components': path.join(__dirname, 'src/components'), '@global': path.join(__dirname, 'src/global'), <-- new! }, }, ... ], }

Inside this folder I am making an UnstyledLink.jsx file - here it is in its entirety:

src/global/UnstyledLink.jsx /** @jsx jsx */ import { jsx } from 'theme-ui' import { Link } from 'gatsby'

export const UnstyledLink = ({ ...props }) => <Link {...props} sx={{ textDecoration: 'none', color: props.color ? props.color : 'black' }} />

All we did was borrow Gatsby's Link component and add some styling to it. The default color of the link is now black (instead of that ugly default blue/purple) unless we explicitly pass it a color prop (which we will do in a second).

One important thing we need to do inside the src/global folder is add an index.js file to export this component as a named export - just like we have been doing, but with one small but important difference...

Because the purpose of the src/global folder is to store several commonly used custom components that we reuse throughout the site, eventually the idea is to have more than just the UnstyledLink in there - we might want to make a <Button /> component, for example.

Because we are going to have multiple components inside this folder and want to be able to easily export/import them all as named imports, inside the index.js file we want to export these components using this syntax:

src/global/index.js export * from './UnstyledLink'

This is basically just saying "export everything from the Unstyled.jsx file."

Now, let's say we added a new component inside src/global called SomeOtherCustomComponent.jsx - we would then export it in this same index.js file like so:


export * from './UnstyledLink' export * from './SomeOtherCustomComponent'

The reason we are doing this is because it makes it really easy to import these components into other files using the @global root import. As you will see, from now on, we can import components in this folder at the top of other files using this easy syntax:

import { UnstyledLink } from '@global' <-- no annoying relative paths required!

Alright, that's enough lecturing on the benefits of named imports and root imports, let's do it for real! Now we can import our custom UnstyledLink.jsx component into our DesktopNavigation.jsx component, and wrap it around the navbar logo like this:

... import { UnstyledLink } from '@global' <-- import custom link!


export const DesktopNavigation = () => {

return ( <Flex sx={{ width: '100%', height: '60px', justifyContent: 'center', alignItems: 'center', color: 'white', bg: 'primary', }} data-comp={DesktopNavigation.displayName} > <UnstyledLink sx={{ textAlign: 'center' }} to="/" color="white"> <-- use custom link! <Box>THE BRAND</Box> </UnstyledLink> </Flex> ) }

DesktopNavigation.displayName = 'DesktopNavigation'

All we did was import our new UnstyledLink component at the top of the file, then wrap this it around the text in our navbar. We used the pre-existing "to" prop (that comes with Gatsby Link) to set the target URL.

Then we passed it a color of "white" using the color prop we added. If we did not use the color prop, the link would default to black.

I added a collections.jsx inside src/pages to test this Link. This is because the target URL is set to the homepage, just how we want it, but right now the homepage is the only page that exists!

I can make sure the Link works by going to localhost:8000/collections, and then clicking "THE BRAND" in the navbar. If it takes you back to the homepage URL, it works!

Deciding how the Cart should work

When it comes to ecommerce sites, one decision you need to make early on is how the cart will work (aka the place where you can see what items, if any, you currently have in your cart before going to checkout).

Assuming you don't plan on radically breaking away from industry standard checkout flow models, you can either choose to have a cart that functions as a sliding side-drawer (usually on the right side of the page), have a cart that pops up as a modal, or even not have a cart at all (patagonia does this, for example).

For the record, I do not recommend trying to be a revolutionary and attempt to reinvent the wheel with a unique checkout flow - even if you come up with something cool, most people are used to the current industry standards and switching it up on them will likely hurt your conversion rates.

It is just my personal preference, but I am biased toward the side-drawer model that is used on sites like allbirds, cuts clothing, and undoubtedly thousands of others.

For example, here is what allbirds' side-drawer cart looks like:

Shop Tutorial Asset 7

I like how it is basically a preview of all the info you can expect to see on the actual checkout page without forcing you to go to checkout to see it - this makes it equally easy to either continue to checkout or stay on the current page if you want to keep shopping.

Similarly, this also makes it easy to edit/remove items in your cart without having to leave the page you're currently on - I think this makes for a fluid and enjoyable user experience!

More cart functionality considerations

In terms of functionality, the cart is essentially just a white block that slides on and off the page. Also, when the cart is open/visible, we want there to be an overlay that slightly fades out the page in the background behind the cart. This isn't totally necessary, but a nice touch.

The cart component should be able to appear on any page on the site, as we want the user to be able to click the little cart icon (an industry standard that we will add to our navbar shortly) and see their cart and its contents, regardless of what page they are on.

We also want to trigger the cart to slide into view whenever a user adds an item to their cart. Obviously we will also have an icon at the top of the cart that lets the user easily slide it back out of view.

Sounds like the Cart component needs to have a global state the triggers it open or closed, something we can do easily with React Hooks - more on this shortly!

But enough talk, let's get coding and start by adding the little cart icon to our navbar.

Adding the Cart icon to our navbar

Go out and find a cart icon you like (I found one here and changed the color to be white).

Download the icon as an SVG (not PNG, etc.) - I will spare you the rant, but pretty much all of your icons on your sites should be SVGs (you can read more about why here).

Inside your src/assets folder, add a folder called images - now put your cart icon SVG file in this folder.

The SVG should look something like this:

Shop Tutorial Asset 8

Unlike other file formats, we can't just import SVGs into components and start using them with <img> tags because SVGs actually don't "just work" in React.

In a non-Gatsby React project we would actually need to manually add support for them in our bundler (webpack, etc.).

Fortunately for us, Gatsby has a plugin for this (surprise, surprise) that does all this work for us: gatsby-plugin-react-svg

As usual, run this in your terminal to install the plugin:

yarn add gatsby-plugin-react-svg

Then it is as easy as it gets with just a one line addition to our gatsby-config.js plugins array:

gatsby-config.js module.exports = { plugins: [ 'gatsby-plugin-theme-ui', 'gatsby-plugin-react-svg', <-- new! ... ], }

I love this plugin because it allows us to import SVGs like they're their own React components (you will see what I mean in a sec) - pretty seamless.

As a teaser, this is what the cart icon now looks like in my navbar:

Shop Tutorial Asset 9

And here is the updated DesktopNavigation.jsx component:

src/components/DesktopNavigation/DesktopNavigation.jsx /** @jsx jsx */ import { Box, Flex, jsx } from 'theme-ui' import { UnstyledLink } from '@global'

import CartIconSvg from '@assets/images/white-cart-icon.svg' <-- new import!

export const DesktopNavigation = () => { return ( <Box sx={{ px: 5, display: 'grid', <-- changed the Navbar from "flex" to "grid" gridTemplateColumns: 'repeat(3, 1fr)', <-- Navbar split into 3 columns height: '60px', alignItems: 'center', color: 'white', bg: 'primary', }} data-comp={DesktopNavigation.displayName} > <Box>NOTHING YET</Box> <UnstyledLink sx={{ textAlign: 'center' }} to="/" color="white"> <Box>THE BRAND</Box> </UnstyledLink> <Box sx={{ textAlign: 'right' }}> <CartIconSvg sx={{ '&:hover': { <-- changing cursor on hover! cursor: 'pointer', }, }} /> </Box> </Box> ) }

DesktopNavigation.displayName = 'DesktopNavigation'

You will notice a few changes...

First, we imported the Cart Icon toward the top of the file. As you can see where we use it below, we use the same bracket syntax that we use for all components to display it - pretty cool. We also added a mouse hover effect using theme-ui's sx prop.

Second, I updated the navbar so that it is now using CSS Grid instead of Flexbox - this will make life easier if we want to add and group items inside the navbar in the future. For example, adding margin or padding to one icon in the navbar won't move all the other items inside the navbar using CSS Grid like it does when using Flexbox (at least not without extra lines of CSS).

From here on out, our desktop navbar is going to follow a common ecommerce industry standard where it is essentially broken up into thirds (some icons/links on the left, brand/logo in the center, cart and other icons on the right).

For example:

Shop Tutorial Asset 10

Setting things up for the Cart component

As discussed above, the cart is very much a global feature in that we want to be able to open and close the cart side drawer from many places on the site, and we want the user to be able to view their cart regardless of the page they're on.

This means we need a way to globally share the cart's state because many components are eventually going to need to know whether the cart is open or closed.

For this reason, I'm going to include the cart as one of the components inside our Layout, and to achieve a global state for the cart I will use React's Context API.

A quick word on the Context API

In its purest sense, React's Context API lets us avoid prop-drilling.

Let's say we have a parent component with some state, and we want to share this state with a child component, and furthermore we want to pass this state to a child of that child component (grandchild?), etc. - it would be pretty annoying and tedious to have to manually pass the props down at every single level.

I've done this before, and in the past I have even found myself passing props down to components that don't even need the prop just because I have to pass through it to get to its child component that does need the prop.

The Context API saves us from this prop-drilling by allowing us to simply wrap a component with some state or value we want to share with "downstream" components, and then all the child components (and their siblings, children, grandchildren) get access to it - magic!

By way of analogy, imagine you are standing in a line with ten people, and you want to share a message (ex. "I love ice cream!") with these ten people. Prop-drilling is like writing this message down on a note, then handing that note to the next person in line, who then reads it and passes it to the next person, and so on, until the tenth person has finally read the message... pretty inefficient.

On the other hand, the Context API allows you to simply grab a megaphone and shout "I love ice cream!" just one time, and all ten people in the line hear you at once, no note-passing required.

Enough with the analogies, though - let's create the cart context and share it at the Layout level so it effectively becomes "global" state.

Creating the cart Context

Inside the src/components/Layout folder, add a file called CartContext.jsx.

All we are doing in this file is creating the Cart Context, which only requires two lines of code to both create the context and export it:

src/components/Layout/CartContext.jsx import React from 'react'

export const CartContext = React.createContext();

Yup, that's it.

But wait? I thought the context was supposed to contain the information we want to share with other components - what's with the empty parentheses?

Yes, it will eventually contain the cart state! But right now we are just initializing it so that it exists in the first place - a necessary step, or formality, if you will.

The plan is to wrap this context's Provider around the Layout component, so we are actually going to create the cart state inside our Layout component - then we will package this state up inside the context and use the context's Provider to "ship" it from the Layout component to the rest of the components in our application.

Let's do this now - here is the updated Layout component:

src/components/Layout/Layout.jsx import React, { useState } from 'react'; <-- import 'useState' for the cart state! import { Box } from 'theme-ui';

import { Header } from '@components/Header'; import { Cart } from '@components/Cart'; <-- doesn't exist yet, but will soon! import { Overlay } from '@components/Overlay'; <-- also does not exist yet!

import { CartContext } from './CartContext'; <-- import the context we just created!

const Layout = ({ children }) => { const [cartVisible, setCartVisible] = useState(false); <-- our cart state!

const toggleCart = () => { <-- function children use to toggle cart! setCartVisible(!cartVisible) };

return ( <CartContext.Provider value={{ cartVisible, toggleCart }}> <-- "shipping" our cart state and cart toggle function to Layout's children! <Box sx={{position: 'relative' }} data-comp={Layout.displayName}> <-- position: 'relative' so that we can set Cart component to be 'absolute' in the future! <Overlay /> <-- we will make this component soon! <Cart /> <-- we will make this component soon! <Header /> {children} </Box> </CartContext.Provider> ); };

Layout.displayName = 'Layout'; export default Layout;

As you can see, we imported the CartContext we just created, then we created some state (cartVisible) for the cart using useState that is a boolean that will be true when the cart is visible, and false when the cart is not visible.

We also created a function called toggleCart whose only purpose in life is to toggle the cart open and closed by toggling the cartVisible state (by way of setCartVisible).

Finally, we grabbed the cartVisible state and our newly created toggleCart function and passed them both inside as the value for the CartContext.Provider - this is what ultimately gives all our child components not only access to the cart state (aka the ability to know whether the cart is open or closed), but also the ability to change this state globally via the toggleCart function. Oh, the power!

Don't worry, we are about to make the Cart component so you will see how this works shortly.

Building the Cart component

Inside src/components make a folder called Cart and inside this folder add a Cart.jsx file and index.js file. Even though it doesn't exist yet, export the Cart.jsx component as a named export inside the index.js file like we have been doing for our other components.

Let's move quickly here - here is the Cart.jsx file in its entirety:

src/components/Cart/Cart.jsx import React, { useContext } from 'react' <-- import the useContext hook! import { Box, Heading, Flex } from 'theme-ui'

import { CartContext } from '../Layout/CartContext' <-- import the cart context file!

export const Cart = () => { const { cartVisible, toggleCart } = useContext(CartContext) <-- we use useContext to "grab" the cart state and toggle function so we can use them below!

return ( <Box sx={{ p: 3, <-- (theme-ui's "padding" syntax) width: '100vw', maxWidth: '440px', minHeight: '100vh', position: 'absolute', zIndex: 100, right: 0, bg: 'white', boxShadow: '0px 0px 4px grey', transform: cartVisible ? 'translateX(0%)' : 'translateX(100%)', <-- shows/hides cart depending on cart state! transition: 'transform 0.3s ease-out', <-- transition effect to make cart slide nice 'n' smooth! }} data-comp={Cart.displayName} > <Flex sx={{ justifyContent: 'space-between' }}> <Heading>CART</Heading> <Box sx={{ '&:hover': { cursor: 'pointer' } }} onClick={toggleCart}> <-- allows user to close out of/hide the cart! X </Box> </Flex> </Box> ) }

Cart.displayName = 'Cart'

So as you can see, all we did here was create our physical cart component which is essentially just a big white block that can slide on and off the right side of the page.

We imported React's useContext hook which we use to "pull out" the cartVisible state and toggleCart from the cart context we provided back in our Layout.jsx component not too long ago.

We use the cartVisible state's value (true or false) to determine whether the cart should be on or off the page using a ternary operator inside the cart's transform CSS property.

Inside the cart, we also added an "X" so the user can close the cart, causing it to slide off the page - we gave it an onClick property and put the toggleCart inside it so that when the user clicks it, the cart state changes from true to false, triggering the cart to close.

This is all well and good, but right now when we go to our homepage, we still have no way to trigger the cart to open. Obviously we want the cart icon in our navbar to open the cart when clicked, so let's quickly add this now!

Connecting the cart icon in the navbar to the cart state

Thanks to the useContext hook, this should be quick and painless.

In case you've already forgotten, the cart icon lives inside our DesktopNavigation.jsx component.

Here is this updated component in its entirety:

src/components/DesktopNavigation/DesktopNavigation.jsx /** @jsx jsx */ import React, { useContext } from 'react'; <-- import useContext hook! import { Box, Flex, jsx } from 'theme-ui' import { UnstyledLink } from '@global'

import CartIconSvg from '@assets/images/white-cart-icon.svg'

import { CartContext } from '../Layout/CartContext'; <-- import cart context file!

export const DesktopNavigation = () => { const {toggleCart} = useContext(CartContext); <-- grab the toggleCart function from the context!

return ( <Box sx={{ px: 5, display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', height: '60px', alignItems: 'center', color: 'white', bg: 'primary', }} data-comp={DesktopNavigation.displayName} > <Box>NOTHING YET</Box> <UnstyledLink sx={{ textAlign: 'center' }} to="/" color="white"> <Box>THE BRAND</Box> </UnstyledLink> <Box sx={{ textAlign: 'right' }}> <Box> <CartIconSvg onClick={toggleCart} <-- toggle cart when user clicks cart icon! sx={{ '&:hover': { cursor: 'pointer', }, }} /> </Box> </Box> </Box> ) }

DesktopNavigation.displayName = 'DesktopNavigation'

At this point, you should be able to go to the homepage and use the cart icon to toggle the cart open and closed - awesome!

But one last thing we need to do to really tie a bow on this is create the <Overlay /> component that we are currently importing inside our Layout.jsx - this component does not exist because we have not made it yet! Let's change this.

Adding the Overlay component

As you can probably guess, this component's job is to appear and fade out the background of the entire website behind the cart whenever the cart is open.

Inside src/components make a folder called Overlay and inside this folder add an Overlay.jsx file and index.js file.

Export the component inside the index.js file as we have been doing.

Now, here is the Overlay.jsx component in its entirety:

src/components/Overlay/Overlay.jsx import React, { useContext } from 'react' import { Box } from 'theme-ui'

import { CartContext } from '../Layout/CartContext' <-- import the cart context file!

export const Overlay = () => { const { cartVisible, toggleCart } = useContext(CartContext) <-- grab the cart state & toggler from the context!

return ( <Box data-comp={Overlay.displayName} onClick={toggleCart} <-- "toggle cart closed if user clicks anywhere outside the cart while it is open" sx={{ visibility: cartVisible ? 'visible' : 'hidden', <-- show overlay if cart is open position: 'absolute', zIndex: 99, <-- the cart component has a zIndex of 100 so this makes sure overlay stays behind the cart side drawer! height: '100vh', width: '100%', bg: 'black', transition: 'visibility 0.4s linear, opacity 0.4s linear', <-- make overlay enter/disappear smoothly opacity: cartVisible ? '0.3' : '0', <-- for "fade" effect above }} ></Box> ) }

Overlay.displayName = 'Overlay'

Hopefully that is all pretty much self-explanatory. Now the basic functionality of the Cart side drawer is done - nice!

Make sure you have imported this Overlay component into Layout.jsx and placed it among the other other components.

src/components/Layout/Layout.jsx ... import { Overlay } from '@components/Overlay'; <-- imported! ... const Layout = ({ children }) => { ...

return ( <CartContext.Provider value={{ cartVisible, toggleCart }}> <Box sx={{position: 'relative' }} data-comp={Layout.displayName}> <Overlay /> <-- added! <Cart /> <Header /> {children} </Box> </CartContext.Provider> ); }; ...

Then run yarn start (or npm start or whatever your local development command is) and pop open localhost:8000 and click the cart icon and hopefully you see our beautiful cart drawer slide on out!

Shop Tutorial Asset 9

Adding the Footer to our Layout

Ok this last bit should be pretty quick.

I have seen many opinions on how to add a footer to a website and there is no "one way." Footers can be tricky because you need to get them to stick to the bottom of the page at all times, and sometimes they can be stubborn.

Also, my personal preference is to never have the footer be inside the viewport on first page load. If, for example, there is a page with barely any content on it, I still want the user to have to scroll a bit to get to the footer.

At least to me, it seems a little trashy/unprofessional when the footer can be seen without scrolling down to it - just my personal opinion though.

Lucky for me, there is a way to achieve this affect that has the added bonus of being super clean and simple, without any CSS depending on the pixel height of the footer in multiple places, for example.

Go ahead and make a Footer component inside src/components and export it as a named import as we have been doing. Set the width to 100%, and style it to be whatever height you want - I made mine 300 pixels. I also made the background color the same 'primary' color of the navbar.

No need to set the position as 'sticky' or 'absolute' - just import it into our Layout.jsx component and add the following styling:

import React, { useState } from 'react' import { Box } from 'theme-ui'

import { Header } from '@components/Header' import { Cart } from '@components/Cart' import { Overlay } from '@components/Overlay' import { Footer } from '@components/Footer' <-- import Footer!

import { CartContext } from './CartContext'

const Layout = ({ children }) => { const [cartVisible, setCartVisible] = useState(false)

const toggleCart = () => { setCartVisible(!cartVisible) }

return ( <CartContext.Provider value={{ cartVisible, toggleCart }}> <Box sx={{ minHeight: '100vh', <-- set minHeight to '100vh' so site content is always at least the height of the viewport! position: 'relative', }} data-comp={Layout.displayName} > <Overlay /> <Cart /> <Header /> {children} </Box> <Footer /> <-- add the Footer and keep it outside the <Box /> that contains the other layout components </CartContext.Provider> ) }

Layout.displayName = 'Layout' export default Layout

And we're done!

Shop Tutorial Asset 10

As you can see, the Footer is now always at the bottom of the page and always lies beyond the initial viewport height. It also sticks to the bottom without being asked - how obedient!

Last word

And we're done - if you made it all the way to the end, congrats! It was definitely a lot of work and a lot of boring configuration/setup, but the beauty of nailing down all the not-so-fun setup stuff is once you do it once, you don't need to do it again!

I kept the site minimal for a reason - now I can clone and reuse it any time I want to make an ecommerce site with Gatsby.

The header, footer, and even that swanky sliding cart/overlay are already there and ready to go, which means instead of building a new ecommerce site from the ground up every time I make one, from now on I get to focus on adding the fun stuff - products, photos, logos, and all the content!

Granted, adding Shopify products, add-to-cart functionality, and checkout can definitely get a bit more complicated and require some serious thought.

However, if you enjoyed this, stick around for Part 2 (coming soon) where I will use the starter we just built to begin a basic integration with Shopify's Storefront API!

© 2021 All Rights Reserved