Mars Theme: A Deep Look at Frontity’s Headless WordPress Theme

0
36

This post was in progress before Automattic acquired Frontity and its entire team. According to Frontity’s founders, the framework will be transitioned into a community-led project and leave the project in “a stable, bug-free position” with documentation and features. Like other open-source community projects, Frontity will remain free as it has been, with opportunities to contribute to the project and make it an even better framework for decoupled WordPress. More detail is found in this FAQ page.

In my previous article, we created a headless WordPress site with Frontity and briefly looked at its file structure. In this companion article, we will go into a deep dive of the @frontity/mars-theme package, or Mars Theme, with a step-by-step walkthrough on how to customize it to make our own. Not only is the Mars Theme a great starter, it’s Frontity’s default theme — sort of like WordPress Twenty Twenty-One or the like. That makes it a perfect starting point for us to get hands-on experience with Frontity and its features.

Specifically, we will look at the fundamental parts of Frontity’s Mars Theme, including what they call “building blocks” as well as the different components that come with the package. We’ll cover what those components do, how they work, and finally, how styling works with examples.

Ready? Let’s go!

Frontity’s building blocks

Let’s revisit the file structure of the Frontity project we made in the last article as that shows us exactly where to find Frontity’s building blocks, the frontity.settings.js, and package.json and packages/mars-theme folder. We covered these is great detail before but, in particular, the package.json file gives us a lot of information about the project, like the name, description, author, dependencies, etc. Here’s what that file includes:

frontity: this is the main package that includes all the methods used in Frontity app development. It’s also where the CLI lives.@frontity/core: This is the most important package because it takes care of all the bundling, rendering, merging, transpiling, serving, etc. We don’t need to access to it in order to develop a Frontity app. The full list is captured in the Frontity docs.@frontity/wp-source: This package connects to the WordPress REST API of our site and fetches all the data needed in the Mars Theme.@frontity/tiny-router: This package handles window.history and helps us with routing.@frontity/htmal2react: This package converts HTML to React, working with processors that match HTML portions while replacing them with React components.

Frontity core, or @frontity/package (also referred as Frontity’s building block), is composed of useful React component libraries in its @frontity/components package, which exports helpful things like Link, Auto Prefetch, Image, Props, Iframe, Switch, and other functions, objects, etc., that can be directly imported into Frontity project components. A more detailed description of these components—including syntax info use cases—is in this package reference API.

The Frontity docs provide a little more information on what happens when a Frontity project is started:

When starting frontity, all the packages defined in frontity.settings.js are imported by @frontity/file-settings and the settings and exports from each package are merged by @frontity/core into a single store where you can access the state and actions of the different packages during development using @frontity/connect, the frontity state manager.

Next up, we’re familiarizing ourselves with how these building blocks, utilities and exports are used in the Mars Theme package to create a functioning Frontity project with a headless WordPress endpoint.

Section 1: Digging into the Mars Theme

Before discussing styling and customizing let’s briefly familiarize ourselves with the Mars Theme (@frontity/mars-theme) file structure and how it is put together.

#! frontity/mars-theme file structure
packages/mars-theme/
|__ src/
|__ index.js
|__ components/
|__ list/
|__ index.js
|__ list-item.js
|__ list.js
|__ pagination.js
|__ featured-media.js
|__ header.js
|__ index.js
|__ link.js
|__ loading.js
|__ menu-icon.js
|__ menu-model.js
|__ menu.js
|__ nav.js
|__ page-error.js
|__ post.js
|__ title.js

The Mars Theme has three important component files: /src/index.js file, src/list/index.js and src/components/index.js. Frontity’s documentation is a great resource for understanding the Mars Theme, with especially great detail on how different Mars Theme components are defined and connected together in a Frontity site. Let’s start familiarizing ourselves with the theme’s three most important components: Root, Theme and List.

Theme Root component (/src/index.js)

The src/index.js file, also known as the theme’s Root, is one of the most important Mars Theme components. The Root serves as an entry point that targets

in the site markup to inject the roots of all the installed packages required to run a Frontity project. A Frontity theme exports a root and other required packages in the DOM as shown in the following use case example from the Frontity documentation:







This Frontity doc explains how Frontity extends its theme using extensibility patterns called Slot and Fill. An example of the Root component (/src/index.js) is taken from its Mars Theme package (@frontity/mars-theme).

This is everything the package pulls in when initializing the Root component:

// mars-theme/src/components/index.js
import Theme from “./components”;
// import processor libraries
import image from “@frontity/html2react/processors/image”;
import iframe from “@frontity/html2react/processors/iframe”;
import link from “@frontity/html2react/processors/link”;

const marsTheme = {
// The name of the extension
name: “@frontity/mars-theme”,
// The React components that will be rendered
roots: {
/** In Frontity, any package can add React components to the site.
* We use roots for that, scoped to the `theme` namespace. */
theme: Theme,
},
state: {
/** State is where the packages store their default settings and other
* relevant state. It is scoped to the `theme` namespace. */
theme: {
autoPrefetch: “in-view”,
menu: [],
isMobileMenuOpen: false,
featured: {
showOnList: false,
showOnPost: false,
},
},
},

/** Actions are functions that modify the state or deal with other parts of
* Frontity-like libraries. */
actions: {
theme: {
toggleMobileMenu: ({ state }) => {
state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;
},
closeMobileMenu: ({ state }) => {
state.theme.isMobileMenuOpen = false;
},
},
},
/** The libraries that the extension needs to create in order to work */
libraries: {
html2react: {
/** Add a processor to `html2react` so it processes the `` tags
* and internal link inside the content HTML.
* You can add your own processors too. */
processors: [image, iframe, link],
},
},
};

export default marsTheme;

The Mars Theme root component exports packages that includes any of the roots, fills, state, actions and libraries elements. More detailed information on Root can be found in this Frontity doc.

Theme component (/src/components/index.js)

The Frontity Theme component is its main root level component that is exported by the Theme namespace (lines 12-16, highlighted in the previous example. The Theme component is wrapped with the @frontity/connect function (line 51, highlighted below) which provides access to its state, actions and libraries props from the Root component instance and allows Theme component to read the state, manipulate through actions, or use code from other features packages in the libraries.

// mars-theme/src/components/index.js
import React from “react”
// Modules from @emotion/core, @emotion/styled, css, @frontity/connect, react-helmet
import { Global, css, connect, styled, Head } from “frontity”;
import Switch from “@frontity/components/switch”;
import Header from “./header”;
import List from “./list”;
import Post from “./post”;
import Loading from “./loading”;
import Title from “./title”;
import PageError from “./page-error”;

/** Theme is the root React component of our theme. The one we will export
* in roots. */
const Theme = ({ state }) => {
// Get information about the current URL.
const data = state.source.get(state.router.link);

return (
<>
{/* Add some metatags to the of the HTML with react-helmet */}
<br /> <Head><br /> <meta name="description" content={state.frontity.description} /><br /> <html lang="en" /><br /> </Head></p> <p> {/* Add some global styles for the whole site, like body or a’s.<br /> Not classes here because we use CSS-in-JS. Only global HTML tags. */}<br /> <Global styles={globalStyles} /></p> <p> {/* Render Header component. Add the header of the site. */}<br /> <HeadContainer><br /> <Header /><br /> </HeadContainer></p> <p> {/* Add the main section. It renders a different component depending<br /> on the type of URL we are in. */}<br /> <Main><br /> <Switch><br /> <Loading when={data.isFetching} /><br /> <List when={data.isArchive} /><br /> <Post when={data.isPostType} /><br /> <PageError when={data.isError} /><br /> </Switch><br /> </Main><br /> </><br /> );<br /> };</p> <p>export default connect(Theme);</p> <p>{/* define Global styles and styled components used Theme component here */}<br /> const globalStyles = css`<br /> body {<br /> margin: 0;<br /> font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto,<br /> “Droid Sans”, “Helvetica Neue”, Helvetica, Arial, sans-serif;<br /> }<br /> a,<br /> a:visited {<br /> color: inherit;<br /> text-decoration: none;<br /> }<br /> `;<br /> const HeadContainer = styled.div`<br /> // …<br /> `;</p> <p>const Main = styled.div`<br /> // …<br /> `;</p> <p>This example is pulled directly from the Mars Theme’s /src/components/index.js component, which we imported with connect from frontity (line 4, above). We are using state.source.get() to retrieve data to be rendered from the current path (lines 39-46, highlighted above); for example, List, Post and other components.</p> <h3 id="h-section-2-working-with-the-list-component">Section 2: Working with the List component</h3> <p>What we just looked at are the theme-level components in Frontity’s Mars Theme. You may have noticed that those components import additional components. Let’s look at a specific one of those, the List component.</p> <p>The List component is exported by src/components/list/index.js which uses @loadable/components to split the List component code in such a way that the component only loads when a user clicks a List view; otherwise it won’t render at all, like when a Post view is clicked instead.</p> <p>// src/components/list/index.js<br /> import { loadable } from “frontity”;</p> <p>// Codesplit the list component so it’s not included if the users<br /> // load a post directly.<br /> export default loadable(() => import(“./list”));</p> <p>In this example, Frontity utilizes loadble functions (integrated from Loadable components) for code splitting which loads a component asynchronously and separates code into different bundles that are dynamically loaded at run time. Frontity’s core package API reference goes into much more detail.</p> <h4 id="h-displaying-lists-of-posts">Displaying lists of posts</h4> <p>To display a list of posts in an archive page, we first have to look Frontity src/components/list/list.js component. As the name suggests, the List component renders lists of posts using state.source.get(link) and its items field (lines 22-25, highlighted below).</p> <p>// src/components/list/list.js<br /> import { connect, styled, decode } from “frontity”;<br /> import Item from “./list-item”;<br /> import Pagination from “./pagination”;</p> <p>const List = ({ state }) => {<br /> // Get the data of the current list.<br /> const data = state.source.get(state.router.link);<br /> return (<br /> <Container><br /> {/* If the list is a taxonomy, we render a title. */}<br /> {data.isTaxonomy && (<br /> <Header><br /> {data.taxonomy}: {state.source[data.taxonomy][data.id].name}<br /> </Header><br /> )}<br /> {/* If the list is an author, we render a title. */}<br /> {data.isAuthor && (<br /> <Header>Author: {state.source.author[data.id].name}</Header><br /> )}<br /> {/* Iterate over the items of the list. */}<br /> {data.items.map(({ type, id }) => {<br /> const item = state.source[type][id];<br /> // Render one Item component for each one.<br /> return <Item key={item.id} item={item} />;<br /> })}<br /> <Pagination /><br /> </Container><br /> );<br /> };<br /> export default connect(List);</p> <p>In the code example above, the connect function is imported by frontity in line 2 and is wrapped around the exported connect(List) component in line 31 (the last line). Two other components, list-item.js and pagination.js are also imported. Let’s look at those next!</p> <p>Here’s what we have for list-item.js:</p> <p>// src/components/list/list-item.js<br /> import { connect, styled } from “frontity”;<br /> import Link from “../link”;<br /> import FeaturedMedia from “../featured-media”;</p> <p>const Item = ({ state, item }) => {<br /> const author = state.source.author[item.author];<br /> const date = new Date(item.date);<br /> return (</p> <article> {/* Rendering clickable post Title */}<br /> <Link link={item.link}><br /> <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} /><br /> </Link></p> <div> {/* If the post has an author, we render a clickable author text. */}<br /> {author && (<br /> <StyledLink link={author.link}><br /> <AuthorName><br /> By <b>{author.name}</b><br /> </AuthorName><br /> </StyledLink><br /> )}<br /> {/* Rendering post date */}<br /> <PublishDate><br /> {” “}<br /> on <b>{date.toDateString()}</b><br /> </PublishDate> </div> <p> {/* If the want to show featured media in the<br /> * list of featured posts, we render the media. */}<br /> {state.theme.featured.showOnList && (<br /> <FeaturedMedia id={item.featured_media} /><br /> )}<br /> {/* If the post has an excerpt (short summary text), we render it */}<br /> {item.excerpt && (<br /> <Excerpt dangerouslySetInnerHTML={{ __html: item.excerpt.rendered }} /><br /> )}<br /> </article> <p> );<br /> };<br /> // Connect the Item to gain access to `state` as a prop<br /> export default connect(Item);</p> <p>The Item component renders the preview of a blog post with clickable post title (lines, 12-14, highlighted above), author name (lines 19-21, highlighted above) and published date (lines: 25-28, highlighted above) along with <FeaturedMedia /> which serves as a post’s optional featured image.</p> <h4 id="h-paginating-a-list-of-posts">Paginating a list of posts</h4> <p>Let’s look at the Pagination component that was rendered earlier in the List component by the src/components/list/pagination/js that follows:</p> <p>// src/components/list/pagination.js<br /> import { useEffect } from “react”;<br /> import { connect, styled } from “frontity”;<br /> import Link from “../link”;</p> <p>const Pagination = ({ state, actions }) => {<br /> // Get the total posts to be displayed based for the current link<br /> const { next, previous } = state.source.get(state.router.link);<br /> // Pre-fetch the the next page if it hasn’t been fetched yet.<br /> useEffect(() => {<br /> if (next) actions.source.fetch(next);<br /> }, []);<br /> return (</p> <div> {/* If there’s a next page, render this link */}<br /> {next && (<br /> <Link link={next}><br /> <Text>← Older posts</Text><br /> </Link><br /> )}<br /> {previous && next && ” – “}<br /> {/* If there’s a previous page, render this link */}<br /> {previous && (<br /> <Link link={previous}><br /> <Text>Newer posts →</Text><br /> </Link><br /> )} </div> <p> );<br /> };<br /> /**<br /> * Connect Pagination to global context to give it access to<br /> * `state`, `actions`, `libraries` via props<br /> */<br /> export default connect(Pagination);</p> <p>The Pagination component is used so that users can paginate between lists of posts — you know, like navigating forward from Page 1 to Page 2, or backward from Page 2 to Page 1. The state, actions, libraries props are provided by the global context that wraps and exports them with connect(Pagination).</p> <h4 id="h-displaying-single-posts">Displaying single posts</h4> <p>The Post component displays both single posts and pages. Indeed, structurally both are the same except, in posts, we usually display meta data (author, date, categories etc). Meta data isn’t usually used in pages.</p> <p>In this Post component, conditional statements are rendered only if the post object contains data (i.e. data.isPost) and a featured image is selected in sate.theme.featured in the theme’s root component:</p> <p>// src/components/post.js<br /> import { useEffect } from “react”;<br /> import { connect, styled } from “frontity”;<br /> import Link from “./link”;<br /> import List from “./list”;<br /> import FeaturedMedia from “./featured-media”;</p> <p>const Post = ({ state, actions, libraries }) => {<br /> // Get information about the current URL.<br /> const data = state.source.get(state.router.link);<br /> // Get the data of the post.<br /> const post = state.source[data.type][data.id];<br /> // Get the data of the author.<br /> const author = state.source.author[post.author];<br /> // Get a human readable date.<br /> const date = new Date(post.date);<br /> // Get the html2react component.<br /> const Html2React = libraries.html2react.Component;</p> <p> useEffect(() => {<br /> actions.source.fetch(“http://www.webdesignernews.com/”);<br /> {/* Preloading the list component which runs only on mount */}<br /> List.preload();<br /> }, []);</p> <p> // Load the post, but only if the data is ready.<br /> return data.isReady ? (<br /> <Container></p> <div> <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} /><br /> {/* Only display author and date on posts */}<br /> {data.isPost && (</p> <div> {author && (<br /> <StyledLink link={author.link}><br /> <Author><br /> By <b>{author.name}</b><br /> </Author><br /> </StyledLink><br /> )}<br /> <DateWrapper><br /> {” “}<br /> on <b>{date.toDateString()}</b><br /> </DateWrapper> </div> <p> )} </p></div> <p> {/* Look at the settings to see if we should include the featured image */}<br /> {state.theme.featured.showOnPost && (<br /> <FeaturedMedia id={post.featured_media} /><br /> )}<br /> {/* Render the content using the Html2React component so the HTML is processed<br /> by the processors we included in the libraries.html2react.processors array. */}<br /> <Content><br /> <Html2React html={post.content.rendered} /><br /> </Content><br /> </Container><br /> ) : null;<br /> };<br /> {/* Connect Post to global context to gain access to `state` as a prop. */}<br /> export default connect(Post);</p> <h3 id="h-section-3-links-menus-and-featured-images">Section 3: Links, menus, and featured images</h3> <p>We just saw how important the List component is when it comes to displaying a group of posts. It’s what we might correlate to the markup we generally use when working with the WordPress loop for archive pages, latest posts feeds, and other post lists.</p> <p>There are a few more components worth looking at before we get into Mars Theme styling.</p> <h4 id="h-the-link-component-src-components-link-js">The Link component (src/components/link.js)</h4> <p>The following MarsLink component comes from src/components/link.js, which is a wrapper on top of the {@link Link} component. It accepts the same props as the {@link Link} component.</p> <p>// src/components/link.js<br /> import { connect, useConnect } from “frontity”;<br /> import Link from “@frontity/components/link”;</p> <p>const MarsLink = ({ children, …props }) => {<br /> const { state, actions } = useConnect();</p> <p> /** A handler that closes the mobile menu when a link is clicked. */<br /> const onClick = () => {<br /> if (state.theme.isMobileMenuOpen) {<br /> actions.theme.closeMobileMenu();<br /> }<br /> };</p> <p> return (<br /> <Link {...props} onClick={onClick} className={className}><br /> {children}<br /> </Link><br /> );<br /> };<br /> // Connect the Item to gain access to `state` as a prop<br /> export default connect(MarsLink, { injectProps: false });</p> <p>As explained in this tutorial, the Link component provides a link attribute that takes a target URL as its value. Quoting from the doc: it outputs an <a> element into the resulting HTML, but without forcing a page reload which is what would occur if you simply added an <a> element instead of using the Link component.</p> <p>Earlier, we defined values for menu items in the frontity.settings.js file. In the Nav component (located in src/components/nav/js) those menu item values are iterated over, match their page url, and display the component inside the Header component.</p> <p>// src/components/nav.js<br /> import { connect, styled } from “frontity”;<br /> import Link from “./link”;</p> <p>const Nav = ({ state }) => (<br /> <NavContainer><br /> // Iterate over the menu exported from state.theme and menu items value set in frontity.setting.js<br /> {state.theme.menu.map(([name, link]) => {<br /> // Check if the link matched the current page url<br /> const isCurrentPage = state.router.link === link;<br /> return (<br /> <NavItem key={name}><br /> {/* If link URL is the current page, add `aria-current` for a11y */}<br /> <Link link={link} aria-current={isCurrentPage ? "page" : undefined}><br /> {name}<br /> </Link><br /> </NavItem><br /> );<br /> })}<br /> </NavContainer><br /> );<br /> // Connect the Item to gain access to `state` as a prop<br /> export default connect(Nav);</p> <p>The Mars Theme provides two additional menu components — menu.js and menu-modal.js — for mobile device views which, like nav.js, are available from the Mars Theme GitHub repository.</p> <h4 id="h-featured-image-component-src-components-featured-media-js">Featured Image component (/src/components/featured-media.js)</h4> <p>In Frontity, featured media items values are defined in the Root component ‘s theme.state.featured line that we discussed earlier. Its full code is available in the /src/components/featured-media.js component file.</p> <p>Now that we’re more familiar with the Mars Theme, as well as its building blocks, components, and functions, we can move into the different approaches that are available for styling the Mars Theme front-end.</p> <p>As we move along, you may find this Frontity doc a good reference for the various styling approaches we cover.</p> <h3 id="h-section-4-how-to-style-a-frontity-project">Section 4: How to style a Frontity project</h3> <p>For those of us coming from WordPress, styling in Frontity looks and feels different than the various approaches for overriding styles in a typical WordPress theme.</p> <p>First off, Frontity provides us with reusable components made with with styled-components, and Emotion, a CSS library for styling components in JavaScript, right out of the box. Emotion is popular with React and JavaScript developers, but not so much in the WordPress community based on what I’ve seen. CSS-Tricks has covered CSS-in-JS in great detail including how it compares with other styling, and this video provides background information about the library. So, knowing that both styled-components and Emotion are available and ready to use is nice context as we get started.</p> <p class="is-style-explanation">Frontity’s documentation has great learning resources for styling frontity components as well as set-by-step guidance for customizing Frontity theme styles.</p> <p>I am new to the CSS-in-JS world, except for some general reading on it here and there. I was exposed to CSS-in-JS styling in a Gatsby project, but Gatsby provides a bunch of other styling options that aren’t readily available in Frontity or the Mars Theme. That said, I feel I was able to get around that lack of experience, and what I learned from my discovery work is how I’m going to frame things.</p> <p>So, with that, we are going to visit a few styling examples, referencing Frontity’s styling documentation as we go in order to familiarize ourselves with even more information.</p> <h4 id="h-using-styled-components">Using styled-components</h4> <p>As the name suggests, we need a component in order to style it. So, first, let’s create a styled-component using Emotion’s styled function.</p> <p>Let’s say we want to style a reusable <Button /> component that’s used throughout our Frontity project. First, we should create a <Button /> component (where its div tag is appended with a dot) and then call the component with a template literal for string styles.</p> <p>// Creating Button styled component<br /> import { styled } from “frontity”</p> <p>const Button = styled.div`<br /> background: lightblue;<br /> width: 100%;<br /> text-align: center;<br /> color: white;<br /> `</p> <p>Now this <Button /> component is available to import in other components. Let’s look specifically at the Mars Theme <Header /> component to see how the styled-component is used in practice.</p> <p>// mars-theme/src/components/header.js<br /> import { connect, styled } from “frontity”;<br /> import Link from “./link”;<br /> import MobileMenu from “./menu”;</p> <p>const Header = ({ state }) => {<br /> return (<br /> <><br /> <Container> // This component is defined later<br /> <StyledLink link="http://www.webdesignernews.com/"> // This component is defined later<br /> <Title>{state.frontity.title} // This component is defined later

// …


);
};

// Connect the Header component to get access to the `state` in its `props`
export default connect(Header);

// Defining the Container component that is a div with these styles
const Container = styled.div`
width: 848px;
max-width: 100%;
box-sizing: border-box;
padding: 24px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: space-around;
`;
// Defining Title component that is h2 with these styles
const Title = styled.h2`
margin: 0;
margin-bottom: 16px;
`;
// Defining StyledLink component that is a third-party Link component
const StyledLink = styled(Link)`
text-decoration: none;
`;

In the above code example, the component (lines 39-41, highlighted above) is used to style another component, . Similarly. the and styled-components are used to style the site title and the site’s main container width.</p> <p>The Emotion docs describe how a styled component can be used as long as it accepts className props. This is a useful styling tool that can be extended using a variable as shown in the following example below from Frontity’s documentation:</p> <p>// mars-theme/src/components/header.js<br /> // …<br /> // We create a variable to use later as an example<br /> Const LinkColor = “green”;</p> <p>// … </p> <p>// Defining StyledLink component that is a third-party Link component<br /> const StyledLink = styled(Link)`<br /> text-decoration: none;<br /> Background-color: ${linkColor};<br /> `;</p> <p>The styled component above is used extensively in the Mars Theme. But before we go further, let’s look at using a CSS prop to style components.</p> <h4 id="h-using-a-css-prop">Using a CSS prop</h4> <p>The css prop is available as a template literal for inline styling from the Frontity core package. It is similar to styled-components, except css does not return a React component but rather a special object that can be passed to a component through the css prop.</p> <p>/* Using as CSS prop */<br /> import { css } from “frontity”;</p> <p>const PinkButton = () => (</p> <div css={css`background: pink`}> My Pink Button </div> <p>);</p> <p>See that? We can style a component inline using the css prop on a component. Additional use case examples are available in the Emotion docs.</p> <h4 id="h-using-the-global-component">Using the <Global /> component</h4> <p><Global /> is a React component that allows to us create site-wide general styles, though Frontity does not optimize it for performance. Global styles should be added to the <Theme /> root component.</p> <p>// packages/mars-theme/src/components/index.js<br /> // …</p> <p>import { Global, css, styled } from “frontity”;<br /> import Title from “./title”;<br /> import Header from “./header”;<br /> // …</p> <p>// Theme root<br /> const Theme = ({ state }) => {<br /> // Get information about the current URL.<br /> const data = state.source.get(state.router.link);</p> <p> return (<br /> <><br /> {/* Add some metatags to the <head> of the HTML. */}<br /> <Title /><br /> // …<br /> {/* Add global styles */}<br /> <Global styles={globalStyles} /><br /> {/* Add the header of the site. */}<br /> <HeadContainer><br /> <Header /><br /> </HeadContainer><br /> // …<br /> </><br /> );<br /> };</p> <p>export default connect(Theme);</p> <p>const globalStyles = css`<br /> body {<br /> margin: 0;<br /> font-family: -apple-system, “Helvetica Neue”, Helvetica, sans-serif;<br /> }<br /> a,<br /> a:visited {<br /> color: inherit;<br /> text-decoration: none;<br /> }<br /> `;</p> <p>const HeadContainer = styled.div`<br /> // …<br /> `;</p> <p>The <Global /> component has a style attribute that takes a css function as its value and consists of standard CSS inside back ticks (lines 35-45, highlighted above) as template literals. Frontity recommends using global styles for globally-used HTML tags, like <html>, <body data-rsssl=1>, <a>, and <img>.</p> <p>Additional CSS styling options — including a dynamic CSS prop and React style props — are described in this Frontity guide to styling.</p> <h4 id="h-resources-for-customizing-a-frontity-theme">Resources for customizing a Frontity theme</h4> <p>I did a lot of research heading into my Mars Theme project and thought I’d share some of the more useful resources I found for styling Frontity themes:</p> <p>Official Frontity themes. In addition to the default Mars Theme, Frontity has a ready-to-use package that ports the default WordPress Twenty Twenty theme in its entirety to a Frontity project. You will notice in the next section that my style customizations were inspired by this great learning resource.Community themes. At this time of this writing, there are a grand total of nine Frontity community members who contributed fully functional theme packages. Those themes can be cloned into your own project and customized according to your needs. Likewise, many of the sites included in the Frontity showcase have GitHub repository links, and just as we can copy or pick up design tips from WordPress themes, we can use these resources to customize our own Frontity theme by referencing these packages.Creating your own theme from scratch. The Frontity tutorial site has an excellent step-by-step guide to create your own fully working and functional theme package from scratch. Although it’s a little time consuming to go through it all, it is the best approach to fully understand a Frontity site project.</p> <p>Now that we have covered the more commonly used Frontity styling techniques, let’s apply what we’ve learned to start customizing our Mars Theme project.</p> <h3 id="h-section-5-customizing-the-frontity-mars-theme">Section 5: Customizing the Frontity Mars Theme</h3> <p>I’m going to share one of my working Frontity projects, where I took the Mars Theme as a base and modified it with the resources we’ve covered so far. Because this is my learning playground, I took time to learn from Frontity default themes, community themes and Frontity showcase sites.</p> <p>So here are examples of how I customized Frontity’s Mars Theme for my headless WordPress site project.</p> <h4 id="h-changing-the-theme-package-name">Changing the theme package name</h4> <p>First, I wanted to change the @frontity/mars-theme package name to something different. It’s a good idea to change the package name and make sure all of the dependencies in the package file are up to date. Luis Herrera outlines the required steps for renaming the Mars Theme package in this frontity community forum, which I used as a reference to go from @fontity/mars-theme package to @frontity/labre-theme.</p> <p>So, open up the package.json file and change the name property on line 2. This is the name of the package that gets used throughout the project.</p> <p>I renamed my project from mars-theme to labre-theme in my package.json file,.</p> <p>We should also update the name of the project folder while we’re at it. We can do that on line 25. I changed mine from ./package/mars-theme to ./package/labre-theme. Now, the theme package is properly listed as a dependency and will be imported to the project.</p> <p>Our frontity-settings.js file needs to reflect the name change. So, let’s open that up and:</p> <p>rename the package name on line 13 (I changed mine from @frontity/mars-theme to @frontity/labre-theme), andrename the name on line 3 (I changed mine from mars-demo to labre-demo).<br /> // @frontity-settings.js<br /> const settings = {<br /> “name”: “labre-demo”,<br /> “state”: {<br /> “frontity”: {<br /> “url”: “http://frontitytest.local”,<br /> “title”: “Frontity Demo Blog”,<br /> “description”: “Exploring Frontity as Headless WordPress”<br /> }<br /> },<br /> “packages”: [<br /> {<br /> “name”: “@frontity/labre-theme”,<br /> “state”: {<br /> “theme”: {<br /> “menu”: [<br /> [“Home”, “http://www.webdesignernews.com/”],<br /> [“Block”, “/category/block/”],<br /> [“Classic”, “/category/classic/”],<br /> [“Alignments”, “/tag/alignment-2/”],<br /> [“About”, “/about/”]<br /> ],<br /> // …</p> <p>Next up, we want to re-initialize the project with these changes. We should delete the node_modules folder with rm -rf node_modules in a terminal and reinstall the npm package with yarn install. Once the npm package is reinstalled, everything gets properly linked internally and our Frontity project runs just fine without any errors.</p> <p>As we discussed earlier, Frontity menu items are either hard-coded in the frontity.setting.js file or in index.js component that’s stored in the Frontity state. However, WordPress can dynamically fetch the Frontity menu. In fact, Frontity just so happens to have a YouTube video on the subject. Let me break down the key steps here.</p> <p>The first step is to install the WP-REST-API V2 Menus plugin in WordPress. The plugin is freely available in the WordPress Plugin Directory, which means you can find it and activate it directly from the WordPress admin.</p> <p>Why do we need this plugin? It extends the new routes to all the registered WordPress menus to the REST API (e.g. /menus/v1/menus/<slug>).</p> <p><img loading="lazy" width="1000" height="424" alt="" class="wp-image-350088 jetpack-lazy-image" data-recalc-dims="1" data-lazy- src="https://www.teknoreset.com/wp-content/uploads/2021/09/1631300007_853_Mars-Theme-A-Deep-Look-at-Frontitys-Headless-WordPress-Theme.png"/>If we check our project site at /wp-json/menu/v1/menus, it should display our selected menu items in the JSON. We can get the menu items with the menu item’s slug property.</p> <p>Next, let’s use the menuHandler function from the tutorial. Create a new menu-handler.js file at src/components/handler/menu-handler.js and paste in the following code:</p> <p>// src/components/handler/menu-handler.js<br /> const menuHandler = {<br /> name: “menus”,<br /> priority: 10,<br /> pattern: “/menu/:slug”,<br /> func: async ({ link, params, state, libraries }) => {<br /> console.log(“PARAMS:”, params);<br /> const { slug } = params;</p> <p> // Fetch the menu data from the endpoint<br /> const response = await libraries.source.api.get({<br /> endpoint: `/menus/v1/menus/${slug}`,<br /> });</p> <p> // Parse the JSON to get the object<br /> const menuData = await response.json();</p> <p> // Add the menu items to source.data<br /> const menu = state.source.datahttp://www.webdesignernews.com/external/mars-theme-a-deep-look-at-frontity-s-headless-wordpress-theme;<br /> console.log(link);<br /> Object.assign(menu, {<br /> items: menuData.items,<br /> isMenu: true,<br /> });<br /> },<br /> };</p> <p>export default menuHandler;</p> <p>This menuHandler function is only executed if the pattern value (i.e. /menu/:slug) matches. Now let’s update our /src/index.js root component so it imports the handler:</p> <p>// src/index.js<br /> import Theme from “./components”;<br /> import image from “@frontity/html2react/processors/image”;<br /> import iframe from “@frontity/html2react/processors/iframe”;<br /> import link from “@frontity/html2react/processors/link”;<br /> import menuHandler from “./components/handlers/menu-handler”;</p> <p>const labreTheme = {<br /> // …<br /> state: {<br /> theme: {<br /> autoPrefetch: “in-view”,<br /> menu: [],<br /> {/* Add menuURL property with menu slug as its value */}<br /> menuUrl: “primary-menu”,<br /> isMobileMenuOpen: false,<br /> // …<br /> },<br /> },</p> <p> /** Actions are functions that modify the state or deal with other parts of<br /> * Frontity-like libraries */<br /> actions: {<br /> theme: {<br /> toggleMobileMenu: ({ state }) => {<br /> state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;<br /> },<br /> closeMobileMenu: ({ state }) => {<br /> state.theme.isMobileMenuOpen = false;<br /> },<br /> {/* Added before SSR action */}<br /> beforeSSR: async ({ state, actions }) => {<br /> await actions.source.fetch(`/menu/${state.theme.menuUrl}/`);<br /> },<br /> },<br /> },<br /> libraries: {<br /> // …<br /> {/* Added menuHandler source */}<br /> source: {<br /> handlers: [menuHandler],<br /> },<br /> },<br /> };</p> <p>export default labreTheme;</p> <p>Add an array of handlers under the source property and fetch data before the beforeSSR function. It does not fetch but does match the menu-handler slug, which means menuHandler() is executed. That puts the menu items into state and they become available to manipulate.</p> <p class="is-style-explanation">Please note that we have added a new menuUrl property here (line 15 above) which can be used as a variable at our endpoint in handlers, as well as the nav.js component. Then, changing the value of menuUrl in the index.js root component, we could display another menu.</p> <p>Let’s get this data into our theme through state and map with menu-items to display on the site.</p> <p>// src/components/nav.js<br /> import { connect, styled } from “frontity”;<br /> import Link from “./link”;</p> <p>/** Navigation Component. It renders the navigation links */<br /> const Nav = ({ state }) => {<br /> {/* Define menu-items constants here */}<br /> const items = state.source.get(`/menu/${state.theme.menuUrl}/`).items;</p> <p> return (<br /> <NavContainer><br /> {items.map((item) => {<br /> return (<br /> <NavItem key={item.ID}><br /> <Link link={item.url}>{item.title}</Link><br /> </NavItem><br /> );<br /> })}<br /> </NavContainer><br /> );<br /> };</p> <p>export default connect(Nav);</p> <p>const NavContainer = styled.nav`<br /> list-style: none;<br /> // …</p> <p>If we change our menu slug here and in index.js, then we get a different menu. To view dynamic menu items in mobile view, we should similarly update menu-modal.js components as well.</p> <p>Additionally, the tutorial describes how to fetch nested menus as well, which you can learn from the tutorial video, starting at about 18:09.</p> <h4 id="h-modifying-the-file-structure">Modifying the file structure</h4> <p>I decided to restructure my Labre (formerly known as Mars) theme folder. Here’s how it looks after the changes:</p> <p>#! modified Frontity labre-theme structure<br /> packages/labre-theme/<br /> |__ src/<br /> |__ index.js<br /> |__ components/<br /> |__image/<br /> |__assets/<br /> |__ list/<br /> |__ footer/<br /> |__footer.js<br /> |__ widget.js<br /> |__ header/<br /> |__ header.js<br /> |__ menu-icon.js<br /> |__ menu-model.js<br /> |__ nav.js<br /> |__ pages/<br /> |__ index.js<br /> |__ page.js<br /> |__ posts/<br /> |__ index.js<br /> |__ post.js<br /> |__ styles/<br /> // …</p> <p>As you can see, I added separate folders for pages, styles, headers, posts, and images. Please take a note that we have to update file paths in index.js and other related components anytime we change the way files and folders are organized. Otherwise, they’ll be pointing to nothing!</p> <p>You may have noticed that the original Mars Theme folder structure includes neither a footer component, nor a separate page component. Let’s make those components to demonstrate how our new folder structure works.</p> <p>We can start with the page component. The Mars Theme generates both pages and posts with the posts.js component by default — that’s because pages and posts are essentially the same except that posts have meta data (e.g. authors, date, etc.) and they can get away with it. But we can separate them for our own needs by copying the code in posts.js and pasting it into a new pages.js file in our /pages folder.</p> <p>// src/components/pages/page.js<br /> import React, { useEffect } from “react”;<br /> import { connect, styled } from “frontity”;<br /> import List from “../list”;</p> <p>const Page = ({ state, actions, libraries }) => {<br /> // Get information about the current URL.<br /> const data = state.source.get(state.router.link);<br /> // Get the data of the post.<br /> const page = state.source[data.type][data.id];<br /> // …<br /> // Load the page, but only if the data is ready.<br /> return data.isReady ? (<br /> <Container></p> <div className="post-title"> <Title dangerouslySetInnerHTML={{ __html: page.title.rendered }} /> </div> <p> {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */}<br /> <Content><br /> <Html2React html={page.content.rendered} /><br /> </Content><br /> </Container><br /> ) : null;<br /> };<br /> // Connect the Page component to get access to the `state` in its `props`<br /> export default connect(Page);</p> <p>// Copy styled components from post.js except, DateWrapper<br /> const Container = styled.div`<br /> width: 90vw;<br /> width: clamp(16rem, 93vw, 58rem);<br /> margin: 0;<br /> padding: 24px;<br /> `<br /> // ..</p> <p>All we did here was remove the meta data from post.js (lines 31-34 and 55-76) and the corresponding styled components. Just as we did with the Mars Theme /list folder, we should export the loadable function in both the pages and posts folders to code split the <List /> component. This way, the <List /> component isn’t displayed if a user is on a single post.</p> <p>// src/components/pages/index.js<br /> import { loadable } from “frontity”;</p> <p>/** Codesplit the list component so it’s not included<br /> * if the users load a post directly. */<br /> export default loadable(() => import(“./page”));</p> <p>Next, we should update path url of /src/components/index.js component as shown below:</p> <p>// src/components/index.js<br /> import { Global, css, connect, styled, Head } from “frontity”;<br /> import Switch from “@frontity/components/switch”;<br /> import Header from “./header/header”;<br /> import List from “./list”;<br /> import Page from “./pages/page”;<br /> import Post from “./posts/post”;<br /> import Loading from “./loading”;<br /> import Title from “./title”;<br /> import PageError from “./page-error”;</p> <p>/** Theme is the root React component of our theme. The one we will export<br /> * in roots. */<br /> const Theme = ({ state }) => {<br /> // Get information about the current URL.<br /> const data = state.source.get(state.router.link);</p> <p> return (<br /> <><br /> // …</p> <p> {/* Add some global styles for the whole site */}<br /> <Global styles={globalStyles} /><br /> {/* Add the header of the site. */}<br /> <HeadContainer><br /> <Header /><br /> </HeadContainer><br /> {/* Add the main section */}<br /> <Main><br /> <Switch><br /> <Loading when={data.isFetching} /><br /> <List when={data.isArchive} /><br /> <Page when={data.isPage} /> {/* Added Page component */}<br /> <Post when={data.isPostType} /><br /> <PageError when={data.isError} /><br /> </Switch><br /> </Main><br /> </><br /> );<br /> };</p> <p>export default connect(Theme);</p> <p>// styled components</p> <p>Now we’re importing the <Page / component and have added our <Main /> styled component.</p> <p>Let’s move on to our custom footer component. You probably know what to do by now: create a new footer.js component file and drop it into the /src/components/footer/ folder. We can add some widgets to our footer that display the sitemap and some sort of “Powered by” blurb:</p> <p>// src/components/footer/footer.js<br /> import React from “react”;<br /> import { connect, styled } from “frontity”;<br /> import Widget from “./widget”</p> <p>const Footer = () => {<br /> return (<br /> <><br /> <Widget /></p> <footer> <SiteInfo><br /> Frontity LABRE Theme 2021 | {” “} Proudly Powered by {” “}<br /> <FooterLinks href="https://wordpress.org/" target="_blank" rel="noopener">WordPress</FooterLinks><br /> {” “} and<br /> <FooterLinks href="https://frontity.org/" target="_blank" rel="noopener"> Frontity</FooterLinks><br /> </SiteInfo><br /> </footer> <p> </><br /> );<br /> };</p> <p>export default connect(Footer);<br /> // …</p> <p>This is a super simple example. Please note that I have imported a <Widget /> component (line 4, highlighted above) and called the component (line 9, highlighted above). We don’t actually have a <Widget /> component yet, so let’s make that while we’re at it. That can be a widget.js file in the same directory as the footer, /src/components/footer/.</p> <p><img loading="lazy" width="1024" height="784" alt="Screen shot of VS code editor open to a widget.js file that shows the syntax highlighted markup for a component." class="wp-image-350103 jetpack-lazy-image" data-recalc-dims="1" data-lazy- src="https://www.teknoreset.com/wp-content/uploads/2021/09/Mars-Theme-A-Deep-Look-at-Frontitys-Headless-WordPress-Theme.jpg"/>This widget.js component was inspired by Aamodt Group‘s footer component, available in a GitHub repository.<br /> <img loading="lazy" width="2600" height="684" alt="Four columns of links, each with a heading. The text is dark against a light gray background." class="wp-image-350104 jetpack-lazy-image" data-recalc-dims="1" data-lazy- src="https://www.teknoreset.com/wp-content/uploads/2021/09/1631300008_547_Mars-Theme-A-Deep-Look-at-Frontitys-Headless-WordPress-Theme.png"/>The widget is hard-coded but works.</p> <p>The default header.js component in Mars Theme is very basic with a site title and site description and navigation items underneath. I wanted to refactor the header component with a site logo and title on the left and the nav.js component (top navigation) on the right.</p> <p>// src/components/header.js<br /> import { connect, styled } from “frontity”;<br /> import Link from “./link”;<br /> import Nav from “./nav”;<br /> import MobileMenu from “./menu”;<br /> import logo from “./images/frontity.png”</p> <p>const Header = ({ state }) => {<br /> return (<br /> <><br /> <Container><br /> <StyledLink link="http://www.webdesignernews.com/"><br /> {/* Add header logo*/}<br /> <Logo src={logo} /><br /> <Title>{state.frontity.title}

{/*{state.frontity.description} */}