gatsby-theme-terminal
This is my second attempt at creating a Gatsby theme and my approach to this build was very different to the first time round.
If you want to dive right in you can see a demo here https://gatsby-theme-terminal.netlify.com/
In my first theme gatsby-theme-gatstats i was treating the theme like it was a fully complete UI toolkit / Component Library and data provider all rolled in to oneā¦
ā¦this feels wrong ā
āWrongā sounds a bit harsh but Gatsby Themes are different to Wordpress Themes.
If youāve worked with Wordpress Themes youāll know the score, they are as i mentioned above āfully completeā and after you install and add your content you have a website or blog looking and working perfectly.
The problem iāve found with Wordpress is, itās fine if you want your site or blog to look and function in exactly the same way as the theme, but the troubles begin when you need something to be slightly different.
Your only options are to hack over the top of the theme until you arrive at something both you and your client are happy with.
This, in the āpastā was an acceptable approach to web development but with Gatsby Themes the team have ushered in a new way of thinking, you can of course create a theme that does as iāve mentioned above, but Gatsby themes are and can be so much more!
I learned a lot from building my first theme but i learned a lot more when i started to use it ā¦ this blog used to use gatsby-theme-gatstats and whilst it does serve a purpose there were one or two areas where iāve found it to be restrictive.
My new theme gatsby-theme-terminal (the theme this blog is now built on) tries to remove those restrictions and aims to separate two very important and different parts of a theme.
- The data
- The presentation
The data
The data is somewhat a fixed set of rules. You canāt for a number of reasons provide users of your theme the ability to
create new frontmatter
fields because your themeās GraphQL queries needs to know what theyāre called.
That said, by providing a decent set of frontmatter
options for your users they should be able to get the right data
to and from their file nodes.
gatsby-theme-terminal exposes the following frontmatter fields:
title: Some Post
date: 2020-01-01
dateModified: 20-20-2020
status: draft // => means it won't be rendered
isPrivate: // -> it will be rendered but you can use this prop as a filter
author: Paul Scanlon
tags: ["JavaScript", "React", "GatsbyJs", "HTML", "CSS", "theme-ui"]
featuredImage: markus-spiske-466ENaLuhLY-unsplash.jpg
embeddedImages:
- markus-spiske-FXFz-sW0uwo-unsplash.jpg
The presentation
This is where we use the data, but how we use the data shouldnāt be a job for the theme, whats to say that my requirements for creating a āposts listā will be the same for every user of the theme, and why restrict this to a single component that renders a list in a particular way, or even renders a list at all.
What we really want is to provide the theme users with both a set of UI components that they can compose any which way they like and the data.
This abstraction was fundamental to my approach with gatsby-theme-terminal
Letās start with data and by looking at the
<SourceList />
component provided by the theme.
<SourceList>
{(source) => (
<ul>
{source.map((edge, index) => {
const {
frontmatter: { title },
} = edge.node;
return <li key={index}>{title}</li>;
})}
</ul>
)}
</SourceList>
The <SourceList />
components finds and returns all data for all the .mdx
files that have been defined in the
theme options and gives you back an array of
objects a bit like this.
{
"node": {
"id": "24b1d743-83cb-5014-a5b1-4094974e3a4d",
"body": "(removed for brevity)",
"excerpt": "Lorem ipsum dolor sit amet...",
"frontmatter": {
"title": "gatsby-theme-terminal",
"tags": [
"React", "JavaScript", "Gatsby.js"
],
"date": "2020-02-27T00:00:00.000Z",
"dateModified": 2020-02-28T00:00:00.000Z,
"author": Paul Scanlon,
"status": published,
"featuredImage": "gatsby-theme-terminal-featured-image.jpg",
"embeddedImages": gatsby-theme-terminal-image-1.jpg
},
"fields": {
"slug": "/posts/2020/02/gatsby-theme-terminal/"
}
}
}
These are pretty much what GraphQL returns and in this component iāve left the data untouched so theme users can map over the array and return any DOM node(s) they like.
Now letās look at the presentation.
Instead of a ul
/ li
it might be nice to return some cards which have the featured image, the date, an excerpt, any
tags used in the post and a āView Postā call to action, and then link it via the slug to the actual page.
To do this we can de-structure a bit more of the data returned from the <SourceList />
component
const {
frontmatter: { title, featuredImage, tags, date },
fields: { slug },
excerpt,
} = edge.node;
ā¦and then use some of the theme-ui/components which are provided by the theme to
create something a bit more eye catching, like one of <Card />
components used on the posts page, like this š
ā¦ and hereās the source code
<SourceList filter='posts'>
{(posts) => {
const {
frontmatter: { title, featuredImage, tags, date },
excerpt,
fields: { slug },
} = posts[3].node;
return <Card title={title} featuredImage={featuredImage} tags={tags} date={date} excerpt={excerpt} slug={slug} />;
}}
</SourceList>
To understand whatās going on here you do need to be a little familiar with theme-ui/components but in short they are UI components that can be re-styled by adding object keys to your theme-ui theme.
A little segway here but with gatsby-plugin-theme-ui you can write all your styles in one place and by providing design tokens all of your HTML can be styled very easily.
for example
// gatsby-plugin-theme-ui/index.js
export default {
...
colors: {
text: "#ffffff",
primary: "#ff79c6",
},
styles: {
h1: {
color: "primary",
},
p: {
color: "text",
},
},
}
ā¦ and if you wanted to provide styles for any or all of the theme-ui/components components you can do something like this
// gatsby-plugin-theme-ui/index.js
export default {
...
colors: {
text: "#ffffff",
primary: "#ff79c6",
},
shadows: [
`0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)`,
],
styles: {
h1: {
color: "primary",
},
p: {
color: "text",
},
},
// here are the styles for the Card component
card : {
primary: {
boxShadow: 0,
},
}
}
Using this method means that it doesnāt matter how your theme users want to render their data because youāve already
provided them with a set of UI components that when used already feel like part of the same theme. Furthermore if what
your theme users want isnāt part of this components list they can use the sx={{}}
prop to add theme specific styles to
any HTML element.
Thereās one more step that i feel is pretty essential in theme development and thatās how to provide the components.
In order to use the <Card />
component in .mdx
you would either have to import it as you normally would in .js
like this
import { Card } from '@theme-ui/components';
Or continue on with the āpowerā approach and provide all of these components via the MDXProvider
like this
// some-layout.js
import * as themeUiComponents from '@theme-ui/components';
<MDXProvider components={themeUiComponents}>{children}</MDXProvider>;
Iām pretty confident in this approach and have just used this theme to update my commercial portfolio pauliescanlon.io and found it to be as flexible as iād intended.
Iām always looking to improve my work so if youāre using this theme and have any questions or problems please do let me know, iād honestly love to hear from you @pauliescanlon
Boom š„ Just launched my second @gatsbyjs theme!
— Paul Scanlon (@PaulieScanlon) February 27, 2020
Introducing... Gatsby Theme Terminal!
If you need a completely customisable lightweight theme have a look š
Full #markdown #mdx and theme-ui support as well as some custom "data" components.
š https://t.co/YqHukiTFAV