Tag: Theme

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

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 <div id="root"> 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:

<!-- /index.HTML (rendered by Frontity) --> <html>   <head>...</head>   <body>     <div id="root">       <MyAwesomeTheme />       <ShareModal />       <YetAnotherPackage />     </div>   </body> </html>

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 `<img>` 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 <head> of the HTML with react-helmet */}       <Title />       <Head>         <meta name="description" content={state.frontity.description} />         <html lang="en" />       </Head>        {/* Add some global styles for the whole site, like body or a's.        Not classes here because we use CSS-in-JS. Only global HTML tags. */}       <Global styles={globalStyles} />        {/* Render Header component. Add the header of the site. */}       <HeadContainer>         <Header />       </HeadContainer>        {/* Add the main section. It renders a different component depending       on the type of URL we are in. */}       <Main>         <Switch>           <Loading when={data.isFetching} />           <List when={data.isArchive} />           <Post when={data.isPostType} />           <PageError when={data.isError} />         </Switch>       </Main>     </>   ); };  export default connect(Theme);  {/* define Global styles and styled components used Theme component here */} const globalStyles = css`   body {     margin: 0;     font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,       "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;   }   a,   a:visited {     color: inherit;     text-decoration: none;   } `; const HeadContainer = styled.div`   // ... `;  const Main = styled.div`   // ... `;

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.

Section 2: Working with the List component

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.

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.

// src/components/list/index.js import { loadable } from "frontity";  // Codesplit the list component so it's not included if the users // load a post directly. export default loadable(() => import("./list"));

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.

Displaying lists of posts

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).

// src/components/list/list.js import { connect, styled, decode } from "frontity"; import Item from "./list-item"; import Pagination from "./pagination";  const List = ({ state }) => {   // Get the data of the current list.   const data = state.source.get(state.router.link);   return (     <Container>       {/* If the list is a taxonomy, we render a title. */}       {data.isTaxonomy && (         <Header>           {data.taxonomy}: {state.source[data.taxonomy][data.id].name}         </Header>       )}       {/* If the list is an author, we render a title. */}       {data.isAuthor && (         <Header>Author: {state.source.author[data.id].name}</Header>       )}       {/* Iterate over the items of the list. */}       {data.items.map(({ type, id }) => {         const item = state.source[type][id];         // Render one Item component for each one.         return <Item key={item.id} item={item} />;       })}       <Pagination />     </Container>   ); }; export default connect(List);

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!

Here’s what we have for list-item.js:

// src/components/list/list-item.js import { connect, styled } from "frontity"; import Link from "../link"; import FeaturedMedia from "../featured-media";  const Item = ({ state, item }) => {   const author = state.source.author[item.author];   const date = new Date(item.date);   return (     <article>      {/* Rendering clickable post Title */}       <Link link={item.link}>         <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} />       </Link>       <div>         {/* If the post has an author, we render a clickable author text. */}         {author && (           <StyledLink link={author.link}>             <AuthorName>               By <b>{author.name}</b>             </AuthorName>           </StyledLink>         )}         {/* Rendering post date */}         <PublishDate>           {" "}           on <b>{date.toDateString()}</b>         </PublishDate>       </div>       {/* If the want to show featured media in the        * list of featured posts, we render the media. */}       {state.theme.featured.showOnList && (         <FeaturedMedia id={item.featured_media} />       )}       {/* If the post has an excerpt (short summary text), we render it */}       {item.excerpt && (         <Excerpt dangerouslySetInnerHTML={{ __html: item.excerpt.rendered }} />       )}     </article>   ); }; // Connect the Item to gain access to `state` as a prop export default connect(Item);

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.

Paginating a list of posts

Let’s look at the Pagination component that was rendered earlier in the List component by the src/components/list/pagination/js that follows:

// src/components/list/pagination.js import { useEffect } from "react"; import { connect, styled } from "frontity"; import Link from "../link";  const Pagination = ({ state, actions }) => {   // Get the total posts to be displayed based for the current link   const { next, previous } = state.source.get(state.router.link);   // Pre-fetch the the next page if it hasn't been fetched yet.   useEffect(() => {     if (next) actions.source.fetch(next);   }, []);   return (     <div>       {/* If there's a next page, render this link */}       {next && (         <Link link={next}>           <Text>← Older posts</Text>         </Link>       )}       {previous && next && " - "}       {/* If there's a previous page, render this link */}       {previous && (         <Link link={previous}>           <Text>Newer posts →</Text>         </Link>       )}     </div>   ); }; /**  * Connect Pagination to global context to give it access to  * `state`, `actions`, `libraries` via props  */ export default connect(Pagination);

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).

Displaying single posts

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.

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:

// src/components/post.js import { useEffect } from "react"; import { connect, styled } from "frontity"; import Link from "./link"; import List from "./list"; import FeaturedMedia from "./featured-media";  const Post = ({ state, actions, libraries }) => {   // Get information about the current URL.   const data = state.source.get(state.router.link);   // Get the data of the post.   const post = state.source[data.type][data.id];   // Get the data of the author.   const author = state.source.author[post.author];   // Get a human readable date.   const date = new Date(post.date);   // Get the html2react component.   const Html2React = libraries.html2react.Component;    useEffect(() => {     actions.source.fetch("/");     {/* Preloading the list component which runs only on mount */}     List.preload();   }, []);    // Load the post, but only if the data is ready.   return data.isReady ? (     <Container>       <div>         <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} />         {/* Only display author and date on posts */}         {data.isPost && (           <div>             {author && (               <StyledLink link={author.link}>                 <Author>                   By <b>{author.name}</b>                 </Author>               </StyledLink>             )}             <DateWrapper>               {" "}               on <b>{date.toDateString()}</b>             </DateWrapper>           </div>         )}       </div>       {/* Look at the settings to see if we should include the featured image */}       {state.theme.featured.showOnPost && (         <FeaturedMedia id={post.featured_media} />       )}       {/* Render the content using the Html2React component so the HTML is processed        by the processors we included in the libraries.html2react.processors array. */}       <Content>         <Html2React html={post.content.rendered} />       </Content>     </Container>   ) : null; }; {/* Connect Post to global context to gain access to `state` as a prop. */}  export default connect(Post);

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.

There are a few more components worth looking at before we get into Mars Theme styling.

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.

// src/components/link.js import { connect, useConnect } from "frontity"; import Link from "@frontity/components/link";  const MarsLink = ({ children, ...props }) => {   const { state, actions } = useConnect();    /** A handler that closes the mobile menu when a link is clicked. */   const onClick = () => {     if (state.theme.isMobileMenuOpen) {       actions.theme.closeMobileMenu();     }   };    return (     <Link {...props} onClick={onClick} className={className}>       {children}     </Link>   ); }; // Connect the Item to gain access to `state` as a prop export default connect(MarsLink, { injectProps: false });

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.

Frontity menu (src/components/nav.js)

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.

// src/components/nav.js import { connect, styled } from "frontity"; import Link from "./link";  const Nav = ({ state }) => (   <NavContainer>     // Iterate over the menu exported from state.theme and menu items value set in frontity.setting.js     {state.theme.menu.map(([name, link]) => {       // Check if the link matched the current page url       const isCurrentPage = state.router.link === link;       return (         <NavItem key={name}>           {/* If link URL is the current page, add `aria-current` for a11y */}           <Link link={link} aria-current={isCurrentPage ? "page" : undefined}>             {name}           </Link>         </NavItem>       );     })}   </NavContainer> ); // Connect the Item to gain access to `state` as a prop export default connect(Nav);

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.

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.

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.

As we move along, you may find this Frontity doc a good reference for the various styling approaches we cover.

Section 4: How to style a Frontity project

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.

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.

Frontity’s documentation has great learning resources for styling frontity components as well as set-by-step guidance for customizing Frontity theme styles.

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.

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.

Using styled-components

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.

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.

// Creating Button styled component import { styled } from "frontity"  const Button = styled.div`   background: lightblue;   width: 100%;   text-align: center;   color: white; `

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.

// mars-theme/src/components/header.js import { connect, styled } from "frontity"; import Link from "./link"; import MobileMenu from "./menu";  const Header = ({ state }) => {   return (     <>       <Container> // This component is defined later         <StyledLink link="/"> // This component is defined later           <Title>{state.frontity.title}</Title> // This component is defined later         </StyledLink>         // ...       </Container>     </>   ); };  // 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 <StyledLink /> component (lines 39-41, highlighted above) is used to style another component, <Link />. Similarly. the <Container /> and <Title /> styled-components are used to style the site title and the site’s main container width.

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:

// mars-theme/src/components/header.js  // ... // We create a variable to use later as an example Const LinkColor = "green";  // ...   // Defining StyledLink component that is a third-party Link component const StyledLink = styled(Link)`   text-decoration: none;   Background-color: $ {linkColor}; `;

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.

Using a CSS prop

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.

/* Using as CSS prop */ import { css } from "frontity";  const PinkButton = () => (   <div css={css`background: pink`}>     My Pink Button   </div> );

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.

Using the <Global /> component

<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.

// packages/mars-theme/src/components/index.js // ...  import { Global, css, styled } from "frontity"; import Title from "./title"; import Header from "./header"; // ...  // Theme root const Theme = ({ state }) => {   // Get information about the current URL.   const data = state.source.get(state.router.link);    return (    <>      {/* Add some metatags to the <head> of the HTML. */}       <Title />         // ...       {/* Add global styles */}       <Global styles={globalStyles} />       {/* Add the header of the site. */}       <HeadContainer>         <Header />       </HeadContainer>         // ...    </>   );  };  export default connect(Theme);  const globalStyles = css`   body {     margin: 0;     font-family: -apple-system, "Helvetica Neue", Helvetica, sans-serif;   }   a,   a:visited {     color: inherit;     text-decoration: none;   } `;  const HeadContainer = styled.div`   // ... `;

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>, <a>, and <img>.

Additional CSS styling options — including a dynamic CSS prop and React style props — are described in this Frontity guide to styling.

Resources for customizing a Frontity theme

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:

  • 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.

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.

Section 5: Customizing the Frontity Mars Theme

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.

So here are examples of how I customized Frontity’s Mars Theme for my headless WordPress site project.

Changing the theme package name

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.

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.

Screenshot of the package.json file open in VS Code. The left panel shows the files and the right panel displays the code.
I renamed my project from mars-theme to labre-theme in my package.json file,.

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.

Our frontity-settings.js file needs to reflect the name change. So, let’s open that up and:

  • rename the package name on line 13 (I changed mine from @frontity/mars-theme to @frontity/labre-theme), and
  • rename the name on line 3 (I changed mine from mars-demo to labre-demo).
// @frontity-settings.js const settings = {   "name": "labre-demo",   "state": {     "frontity": {       "url": "http://frontitytest.local",       "title": "Frontity Demo Blog",       "description": "Exploring Frontity as Headless WordPress"     }   },   "packages": [     {       "name": "@frontity/labre-theme",       "state": {         "theme": {           "menu": [             ["Home", "/"],             ["Block", "/category/block/"],             ["Classic", "/category/classic/"],             ["Alignments", "/tag/alignment-2/"],             ["About", "/about/"]           ],  // ...

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.

Refactoring navigation with dynamic menu fetching

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.

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.

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>).

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.

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:

// src/components/handler/menu-handler.js const menuHandler = {   name: "menus",   priority: 10,   pattern: "/menu/:slug",   func: async ({ link, params, state, libraries }) => {     console.log("PARAMS:", params);     const { slug } = params;      // Fetch the menu data from the endpoint     const response = await libraries.source.api.get({       endpoint: `/menus/v1/menus/$ {slug}`,     });      // Parse the JSON to get the object     const menuData = await response.json();      // Add the menu items to source.data     const menu = state.source.data[link];     console.log(link);     Object.assign(menu, {       items: menuData.items,       isMenu: true,     });   }, };  export default menuHandler;

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:

// src/index.js import Theme from "./components"; import image from "@frontity/html2react/processors/image"; import iframe from "@frontity/html2react/processors/iframe"; import link from "@frontity/html2react/processors/link"; import menuHandler from "./components/handlers/menu-handler";  const labreTheme = {   // ...   state: {     theme: {       autoPrefetch: "in-view",       menu: [],       {/* Add menuURL property with menu slug as its value */}       menuUrl: "primary-menu",       isMobileMenuOpen: 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;       },       {/* Added before SSR action */}       beforeSSR: async ({ state, actions }) => {         await actions.source.fetch(`/menu/$ {state.theme.menuUrl}/`);       },     },   },   libraries: {     // ...     {/* Added menuHandler source */}     source: {       handlers: [menuHandler],     },   }, };  export default labreTheme;

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.

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.

Let’s get this data into our theme through state and map with menu-items to display on the site.

// src/components/nav.js import { connect, styled } from "frontity"; import Link from "./link";  /** Navigation Component. It renders the navigation links */ const Nav = ({ state }) => {   {/* Define menu-items constants here */}   const items = state.source.get(`/menu/$ {state.theme.menuUrl}/`).items;    return (   <NavContainer>     {items.map((item) => {        return (         <NavItem key={item.ID}>            <Link link={item.url}>{item.title}</Link>          </NavItem>       );     })}   </NavContainer>   ); };  export default connect(Nav);  const NavContainer = styled.nav`   list-style: none;   // ...

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.

Additionally, the tutorial describes how to fetch nested menus as well, which you can learn from the tutorial video, starting at about 18:09.

Modifying the file structure

I decided to restructure my Labre (formerly known as Mars) theme folder. Here’s how it looks after the changes:

#! modified Frontity labre-theme structure packages/labre-theme/ |__ src/   |__ index.js   |__ components/      |__image/      |__assets/      |__ list/      |__ footer/        |__footer.js        |__ widget.js      |__ header/        |__ header.js        |__ menu-icon.js        |__ menu-model.js        |__ nav.js      |__ pages/        |__ index.js        |__ page.js      |__ posts/        |__ index.js        |__ post.js      |__ styles/      // ...

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!

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.

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.

// src/components/pages/page.js import React, { useEffect } from "react"; import { connect, styled } from "frontity"; import List from "../list";  const Page = ({ state, actions, libraries }) => {   // Get information about the current URL.   const data = state.source.get(state.router.link);   // Get the data of the post.   const page = state.source[data.type][data.id];   //  ...   // Load the page, but only if the data is ready.   return data.isReady ? (     <Container>       <div className="post-title">         <Title dangerouslySetInnerHTML={{ __html: page.title.rendered }} />       </div>        {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */}       <Content>         <Html2React html={page.content.rendered} />       </Content>     </Container>   ) : null; }; // Connect the Page component to get access to the `state` in its `props` export default connect(Page);  // Copy styled components from post.js except, DateWrapper const Container = styled.div`     width: 90vw;     width: clamp(16rem, 93vw, 58rem);     margin: 0;     padding: 24px; ` // ..

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.

// src/components/pages/index.js import { loadable } from "frontity";  /** Codesplit the list component so it's not included *   if the users load a post directly. */ export default loadable(() => import("./page"));

Next, we should update path url of /src/components/index.js component as shown below:

// src/components/index.js import { Global, css, connect, styled, Head } from "frontity"; import Switch from "@frontity/components/switch"; import Header from "./header/header"; import List from "./list"; import Page from "./pages/page"; import Post from "./posts/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 global styles for the whole site */}        <Global styles={globalStyles} />       {/* Add the header of the site. */}       <HeadContainer>         <Header />       </HeadContainer>       {/* Add the main section */}       <Main>         <Switch>           <Loading when={data.isFetching} />           <List when={data.isArchive} />           <Page when={data.isPage} /> {/* Added Page component */}           <Post when={data.isPostType} />           <PageError when={data.isError} />         </Switch>       </Main>     </>   ); };  export default connect(Theme);  // styled components

Now we’re importing the <Page / component and have added our <Main /> styled component.

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:

// src/components/footer/footer.js import React from "react"; import { connect, styled } from "frontity"; import Widget from "./widget"  const Footer = () => {   return (   <>     <Widget />     <footer>       <SiteInfo>         Frontity LABRE Theme 2021 | {" "} Proudly Powered by {"  "}         <FooterLinks href="https://wordpress.org/" target="_blank" rel="noopener">WordPress</FooterLinks>         {"  "} and         <FooterLinks href="https://frontity.org/" target="_blank" rel="noopener"> Frontity</FooterLinks>       </SiteInfo>     </footer>     </>   ); };  export default connect(Footer); // ...

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/.

Screen shot of VS code editor open to a widget.js file that shows the syntax highlighted markup for a component.
This widget.js component was inspired by Aamodt Group‘s footer component, available in a GitHub repository.
Four columns of links, each with a heading. The text is dark against a light gray background.
The widget is hard-coded but works.

Customizing the theme header

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.

// src/components/header.js import { connect, styled } from "frontity"; import Link from "./link"; import Nav from "./nav"; import MobileMenu from "./menu"; import logo from "./images/frontity.png"  const Header = ({ state }) => {   return (     <>       <Container>         <StyledLink link="/">          {/* Add header logo*/}           <Logo src={logo} />           <Title>{state.frontity.title}</Title>         </StyledLink>           {/*<Description>{state.frontity.description}</Description> */}           <Nav />       </Container>         <MobileMenu />     </>   ); }; // Connect the Header component to get access to the `state` in its `props` export default connect(Header);  const Container = styled.div`   width: 1000px;   // ...   `} {/* Logo styled component */} const Logo = styled.img`   max-width: 30px;   display: inline-block;   border-radius: 15px;   margin-right: 15px; `;  // ...

My refactored header.js component imports a logo image (line 6, highlighted above) and uses in line 14. The nav.js component shown below is basically the same, only with some minor styling modifications.

Screenshot showing refactored site header with site logo and site title (left) and top navigation (right)

Adding the <Global> style component

We have already covered the <Global> component and how it’s used for site-wide CSS. There are only a few global styles in the default Mars Theme root component, and I wanted to add more.

I did that with a separate globalStyles file at /src/components/styles/globalStyles.js — similar to Frontity’s Twenty Twenty theme — and added root variables, a CSS reset, and common site-wide element styles, found in the GitHub repo.

Implementing fluid typography

Even though it’s not really in scope, I really wanted to use fluid typography in my custom theme as part of my overall learning journey. So, I added it to the global styles.

CSS-Tricks has extensively covered fluid typography and how the clamp() function is used to set target font sizes. Following those CSS-Tricks posts and this Picalilli one as my guide, I defined two custom properties with clamped font size ranges on the :root element in the globalStyles.js component.

// src/components/styles/globalStyles.js :root {   --wide-container: clamp(16rem, 90vw, 70rem);   --normal-container: clamp(16rem, 90vw, 58rem); }

The wide-container wrapper is used for header and footer components whereas the normal-container will be used for displaying posts and pages.

I also clamped the headings under elementBase in the globalStyles.js component as shown in this GitHub repo.

It was a fun working with the clamp() function because it meant I could set a range of sizes without any media queries at all!

Adding webfonts to the theme

I also wanted to use a different webfont in my theme. Importing webfonts in CSS using @font-face is covered here on CSS-Tricks. Frontity’s Twenty Twenty Theme uses it, so that’s a good place to reference as well.

I wanted three Google fonts:

We can use the fonts with either with a <link>in the HTML head or with @import in CSS. But Chris covered how to use @font-face with Google Fonts, which allows us to optimize the number of HTTP requests we make since we can download the fonts to our own server.

I use the Google webfonts helper to host the downloaded font files. Here’s what I got:

/* source: google webfonts helper */ /* source-sans-pro-regular - latin */ @font-face {   font-family: 'Source Sans Pro';   font-style: normal;   font-weight: 400;   src: url('../fonts/source-sans-pro-v14-latin-regular.eot'); /* IE9 Compat Modes */   src: local(''),     url('../fonts/source-sans-pro-v14-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */     url('../fonts/source-sans-pro-v14-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */     url('../fonts/source-sans-pro-v14-latin-regular.woff') format('woff'), /* Modern Browsers */     url('../fonts/source-sans-pro-v14-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */     url('../fonts/source-sans-pro-v14-latin-regular.svg#SourceSansPro') format('svg'); /* Legacy iOS */ }

Looking at the Twenty Twenty Theme as a reference for how it’s done there, I created a font-face.js file and dropped it into the /src/components/styles folder as shown in this GitHub repository.

Those fonts point to a /fonts folder that doesn’t exist. So, let’s make one there and make sure all of the correct font files are in it so the fonts load properly.

Importing globalStyles and @face-font components to the root <Theme /> component

Let’s open our theme root component, /src/components.index.js, and add our globalStyles.js and font-face.js components in there. As shown below, we should import both components into index.js and call the components later.

// src/components/index.js  // ... import FontFace from "./styles/font-face"; import globalStyles from "./styles/globalStyles";  /** 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 global styles for the whole site, like body or a's.      *  Not classes here because we use CSS-in-JS. Only global HTML tags. */}       <Global styles={globalStyles} />       <FontFace />       {/* Add the header of the site. */}       // ...  export default connect(Theme);   {/* delete original globalStyles css component */}   // ...

Finally, we should remove mars-theme globalStyles component from index.js. Now our new fonts are applied throughout our project.

Styling pages and posts

Our posts and pages are pretty much styled already, except for some Gutenberg block contents, like buttons, quotes, etc.

To style our post entry meta data, let’s add icons for the author, date, categories, and tags. Frontity’s port of the WordPress Twenty Nineteen theme uses SVG icons and components for author.js, categories.js, posted-on.js and tags.js components, which we can totally copy and use in our own project. I literally copied the top-level entry-meta folder and everything in it from the frontity-twentynineteen theme and added it all to the /components/posts/ project folder.

Next we should update our src/components/list/list-item.js component so we can use the new assets:

// src/components/list/list-item.js  import { connect, styled } from "frontity"; import Link from "../link"; import FeaturedMedia from "../featured-media";  // import entry-meta import Author from "../entry-meta/author"; import PostedOn from "../entry-meta/posted-on";  const Item = ({ state, item }) => {    return (     <article>       <div>         {/* If the post has an author, we render a clickable author text. */}         <EntryMeta>           <Author authorId={item.author} /> {"|  "}           <PostedOn post={item} />         </EntryMeta>       </div>        <Link link={item.link}>         <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} />       </Link>       // ...     </article>   ); };  // Connect the Item to gain access to `state` as a prop export default connect(Item);

The styled component for the <EntryMeta /> component can be something like as shown in the GitHub repository.

With these styles in place, our archive page entry meta looks good with icons displayed before entry-meta taxonomy (authors, posted-on).

Here we will modify archives taxonomy page styling with more descriptive header. Let’s update list.js component of our /src/components/list/list.js as shown below.

// src/components/list/list.js  import React from "react"; import { connect, styled, decode } from "frontity"; import Item from "./list-item"; import Pagination from "./pagination";  const List = ({ state }) => {   // Get the data of the current list.   const data = state.source.get(state.router.link);    return (     <Container className="entry-content">       {/* If the list is a taxonomy, we render a title. */}       {data.isAuthor ? (         <Header>           Author Archives:{" "}           <PageDescription>           {decode(state.source.author[data.id].name)}           </PageDescription>         </Header>         ) : null}          {/* If the list is a taxonomy or category, we render a title. */}         {data.isTaxonomy || data.isCategory ? (           <Header>             {data.taxonomy.charAt(0).toUpperCase() + data.taxonomy.slice(1)}{" "}             Archives:{" "}             <PageDescription>             {decode(state.source[data.taxonomy][data.id].name)}             </PageDescription>           </Header>         ) : null}       // ...        <Pagination />     </Container>   ); }; export default connect(List);  const PageDescription = styled.span`   font-weight: bold;   font-family: var(--body-family);     color: var(--color-text); `; // ...

In the example above, we wrapped taxonomy.id data with PageDesctiption styled component applied some styling rules.

The post pagination in the default Mars Theme is very basic with almost no styling. Let’s borrow from the Frontity Twenty Nineteen theme again and add the pagination component and styling from the theme by copying the pagination.js component file in its entirety, and paste it to /src/components/list/pagination.js in our theme.

Showing two example posts in a post list, one with comments enabled, and the other with comments disabled. The post content is black against a light gray background.
I added some minor CSS and it works perfectly in our project.

To customize the actual individual posts and pages, let’s make bold header title that’s centered and displays the entry meta:

// src/components/posts/post.js  // ... // Import entry-meta import Author from "../entry-meta/author"; import PostedOn from "../entry-meta/posted-on"; import Categories from "../entry-meta/categories"; import Tags from "../entry-meta/tags";  const Post = ({ state, actions, libraries }) => {   // ...   // Load the post, but only if the data is ready.   return data.isReady ? (     <Container className="main">       <div>         <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} />          {/* Hide author and date on pages */}         {data.isPost && (           <EntryMeta>           <Author authorId={post.author} />           <PostedOn post={post} />         </EntryMeta>         )}       </div>        {/* Look at the settings to see if we should include the featured image */}       {state.theme.featured.showOnPost && (         <FeaturedMedia id={post.featured_media} />       )}        {data.isAttachment ? (         <div dangerouslySetInnerHTML={{ __html: post.description.rendered }} />       ) : (         <Content>           <Html2React html={post.content.rendered} />           {/* Add footer meta-entry */}           <EntryFooter>             <Categories cats={post.categories} />             <Tags tags={post.tags} />           </EntryFooter>         </Content>       )}     </Container>   ) : null; };  export default connect(Post); // ...
Screenshot showing header meta-entry (top) and footer meta-entry (bottom). Header has a large bold title above meta containing the author name and post date, all centered. There is a thick line between the header and content. The content is a simple paragraph containing lorem ipsum text. Dark content against a light gray background.

Adding Gutenberg block styles

WordPress uses a separate stylesheet for blocks in the Block Editor. Right now, that stylesheet isn’t being used but it would be great if we could get some base styles in there that we use for the various block content we add to pages and posts.

A post with DevTools open and highlighting the markup for the button component.
That .wp-block-buttons class is declared in the WordPress blocks stylesheet that we aren’t using… yet.

The WordPress Block Editor uses two styling files: style.css and theme.css. Let’s copy these directly from Frontity’s port of the Twenty Twenty theme because that’s how they implemented the WordPress styles. We can place those inside a /styles/gutenberg/ folder.

“Gutenberg” is the codename that was given to the WordPress Block Editor when it was in development. It’s sometimes still referred to that way.

Let’s add the above two style files to our theme root component, /src/components/index.js, just like we did earlier for globalStyles:

//  src/components/index.js import gutenbergStyle from "./styles/gutenberg/style.css"; import gutenbergTheme from "./styles/gutenberg/theme.css"

Here’s our updated <Theme /> root component:

// src/components/index.js  // ... import FontFace from "./styles/font-face"; import globalStyles from "./styles/globalStyles"; // Add Gutenberg styles import gutenbergStyle from "./styles/gutenberg/style.css"; import gutenbergTheme from "./styles/gutenberg/theme.css"  /** 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 global styles for the whole site, like body or a's.       * Not classes here because we use CSS-in-JS. Only global HTML tags. */}       <Global styles={globalStyles} />       <Global styles={css(gutenbergStyle)} />       <Global styles={css(gutenbergTheme)} />       <FontFace />       {/* Add the header of the site. */}       // ... export default connect(Theme);   {/* Delete original globalStyles css component */}  // ...

We could go about overriding styles many different ways. I went with a simple route. For example, to overriding button styles — .wp-block-buttons — in the styled-component for pages and posts.

Screenshot showing button style customization (left panel) and styled button in blue (right)

We can write override any other block styles the same way. In Frontity’s Twenty Nineteen theme, the entire stylesheet from the WordPress version of the theme is added to the Frontity version to replicate the exact same appearance. Frontity’s Twenty Twenty port uses only a select few of the styles in the WordPress Twenty Twenty themes, but as inline styles.

Additional styling resources

All the resources we covered in this section on styling are available in the GitHub repository. If you wish to expand my @frontity/labre-theme project further, here are the resources that I gathered.

Section 6: Resources and credit

There are ample resources to learn and customize your Frontity project. While preparing this post, I have referred to the following resources extensively. Please refer to original posts for more detailed information.

Frontity documentation and articles

  • Step-by-step tutorial (Frontity): This is the perfect place to start if you’re new to Frontity, or even if you’ve previously used Frontity and want to level up.
  • Conceptial guides (Frontity): These guides helps solve some of the common challenges that come up when working with dynamic server-side rendering in React apps connected to WordPress.
  • Frontity API reference (Frontity). This contains detailed information about Frontity CLI, packages, plugins and themes. Once you’ve mastered the basics of working with Frontity, this is where you’re likely to spend most of your time when working on projects.”
  • Frontity example repo (Frontity): This is a collection of Frontity projects that demonstrate how Frontity is used in the wild.

Frontity case studies

Frontity talks and videos

Frontity community

Frontity has a vibrant and engaging community forum for asking questions or getting help regarding your Frontity project.

Wrapping up and personal thoughts

If you can’t already tell from this post or the others I’ve written, I have a huge passion for headless WordPress sites. As I wrote in a previous article, I came across Frontity through when Chris posted this article. I have been experimenting with it for over six months, choosing to take a deep drive into Frontity and the building blocks used in its default Mars Theme. I must admit that it’s a fascinating software framework and I’ve had an enjoyable learning experience. I may even use this sort of setup for my own personal site!

Here are a few key takeaways from my experience working with Frontity so far:

  • It’s beginner-friendly and low maintenance: One of the things that impressed me most with Frontity is how relatively easy it is to jump into, even as a beginner. It installs with a couple of commands and takes care of all the setup and configuration for connecting to WordPress via the REST API—something I would have struggled with if left to my own devices.
  • It works with experimental block themes. In my very limited testing, Frontity’s framework works as expected with experimental block themes, just as it does with classic WordPress themes, like Twenty Twenty. I tested with the Quadrat theme that supports the experimental stuff the Gutenberg team is working on.
  • Hosting is good, but maybe too expensive: As Chris wrote, Frontity is “a perfect match for Vercel.” However, the current Jamstack pricing model that includes Vercel is unattractive for many ordinary WordPress users.
  • Frontity’s documentation is good, but could be better: The Frontity team recently reorganized Frontity documentation into tutorials, guides and an API reference. However, in my opinion it’s still confusing for those just getting into the framework.

Because I enjoyed this project so much, I am currently doing a theme project from scratch. Even in WordPress, I learned best by getting my hands dirty building WordPress themes from scratch.

While I am still doing my Gatsby and Frontity side projects, I have not lost my sight from the ongoing WordPress block editor and block-based theme development. At the time of writing, there are already sixteen block-based themes in the WordPress theme directory. I have just started exploring and understanding experimental block themes, which might be another interesting learning project.

After this project, my thoughts about Gatsby, Frontity and the concept of headless sites are still evolving. That’s only because it’s tough to make a fair comparison of when a lot of the tooling is actively in development and changing all the time. There are even experimental themes, that are much lighter and different structural markups than the current PHP-based classic themes, which might be a subject for yet another time.


Please share your experience and thoughts if you have been using Frontity in your projects. As always, I enjoy reading any comments and feedback!


The post Mars Theme: A Deep Look at Frontity’s Headless WordPress Theme appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,

Meta Theme Color and Trickery

Starting with Version 15, Safari supports the theme-color <meta> tag both on macOS and iOS. That’s exciting news because now the first desktop browser supports this <meta> tag and it also supports the media attribute and the prefers-color-scheme media feature.

I never really took much note of the theme-color meta tag, but now is a good time to learn about its features and limitations and try to discover some interesting use cases.

Features and limitations

Here’s how I’ve been using the theme-color meta tag for the past few years: just a good ‘ol hex code for the content attribute.

<meta name="theme-color" content="#319197">

According to tests I made earlier this year, this works in Chrome, Brave and Samsung Internet on Android, installed PWAs in Chrome and now also in Safari Technology Preview.

Hex color support is great in all supported browsers.

CSS color support

One of the first questions that came to my mind was “Can we use color keywords, hsl(), rgb(), too?” According to the HTML spec, the value of the attribute can be any CSS color. I’ve created this theme-color testing CodePen to verify that.

<meta name="theme-color" content="hsl(24.3, 97.4%, 54.3%)">
Blank webpage with orange header.
The theme-color meta tags supports CSS colors in any form: keywords, rgb(), hsl() or hex code.
Blank webpage with a hot pink header. There are controls to the right of the webpage for browser testing.
Looking at Chrome 90 on an Android Galaxy S20

All supported browsers also support hsl() and rgb(). This is awesome because it allows us to do some pretty cool stuff with JavaScript. We’ll talk about that later, but first let’s look at some limitations.

Transparency

HEX codes, rbg(), hsl() and keywords are well and consistently supported, but colors that include transparency: not so much. Actually, they are supported in most browsers, but the results aren’t very consistent and sometimes unexpected.

transparent is a CSS color and used in the theme-color meta tag most browsers do what you’d expect. All regular mobile browsers don’t change color and display the default tab bar, but Safari on macOS and the Chrome Canary PWA on macOS turn the tab bar black. The PWA on Android falls back to theme-color defined in the manifest.json, which we’ll talk about in a bit.

Examples of the same white webpage with either white or dark headers with the browser vendor labeled above each one.
Browser with a transparent theme-color meta tag

All browsers interpret hsla() and rgba(), but they set the alpha value to 1. The only exception is Safari on macOS; it interprets the transparency, but it seems like the transparent color has a black baseline. This has the effect that the light orange color looks like dark orange.

Same browser comparison but all with orange headers, except Safari which is a darker brown.
hsla() applied to the theme-color meta tag

New color functions

Safari 15 is the first browser to support lab(), lch(), and hwb() color functions. These functions work if you use them in CSS, but not if you use them in the theme-color meta tag.

All three declarations work fine in Safari 15:

body {   background-color: hwb(27 10% 28%);   background-color: lch(67.5345% 42.5 258.2);   background-color: lab(62.2345% -34.9638 47.7721); }

If you use any of the new color functions in the theme-color meta tag, Safari doesn’t interpret them and falls back to its own algorithm of picking the color. It’s likely that Safari uses the background color of your <body> for the theme-color, which means that you might get the expected result without defining the theme-color explicitly.

<meta name="theme-color" content="lab(29.2345% 39.3825 20.0664)">
Green webpage with green header.

Please be aware that at the time of writing Safari 15 is the only browser to support these new colors functions.

currentColor

If CSS colors are supported, currentColor should work, too, right? No, unfortunately not in any browser. It’s probably an uncommon use case, but I would expect that we can set the theme-color to the current color of the <body> or <html> element.

<style>   body {     color: blue;   } </style>  <meta name="theme-color" content="currentColor">

I found a ticket in the WebKit bug tracker titled <meta name="theme-color" content="..."> should also support CSS currentcolor.” Support might change in the future, if someone picks the ticket up.

Prohibited colors

When I was testing CSS color keywords, I used the color red and it didn’t work. First, I thought that keywords weren’t supported, but blue, hotpink, and green worked fine. As is turns out, there’s a narrow range of colors that Safari doesn’t support, colors that would get in the way of using the interface. red doesn’t work because it’s visually too close to the background color of the close button in the tab bar. This limitation is specific to Safari, in all other supported browsers any color seem to work fine.

Wbite webpage with a color picker set to red. The header of the browser is white.
If you set the theme-color to red, Safari uses any color it deems appropriate.

Custom properties

I don’t know enough about the internals of browsers and custom properties and if it’s even possible to access custom properties in the <head>, but I tried it anyway. Unfortunately, it didn’t work in any browser.

<style>   :root {     --theme: blue;   } </style>  <meta name="theme-color" content="var(--theme)">

That’s pretty much everything I wanted to know about basic support of the theme-color meta tag. Next, let’s see how to and how not to implement dark mode for the tab bar.

Dark mode

Safari 15 is the first desktop browser to support the media attribute and the prefers-color-scheme media feature on theme-color meta tags. Starting with version 93, Chrome supports it too, but only for installed progressive web apps.

According to the web app manifest page on web.dev, if you define multiple theme-color meta tags, browsers pick the first tag that matches.

<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

I was eager to find out what happens in browsers that don’t support the media attribute. I’ve created a demo page for testing dark mode that includes the meta tags above and also allows you to install the site as a PWA. The webmanifest.json includes another color definition for the theme-color.

{   "name": "My PWA",   "icons": [     {       "src": "https://via.placeholder.com/144/00ff00",       "sizes": "144x144",       "type": "image/png"     }   ],   "start_url": "/theme-color-darkmode.html",   "display": "standalone",   "background_color": "hsl(24.3, 97.4%, 54.3%)",   "theme_color": "hsl(24.3, 97.4%, 54.3%)" }

Here’s how supported browsers display the tab bar in light mode. It doesn’t matter if a browser supports the media attribute or not, it will interpret the first meta tag regardless.

Here’s how the tab bar on the same page looks like in dark mode. These results are more interesting because they vary a bit. The Canary PWA and Safari support and show the dark color. All mobile browsers use their default dark tab bar styling, except for Samsung Internet, which uses the light styling because it doesn’t support the prefers-color-scheme media feature. (TIL: This should change in the near future.)

I did one last test. I wanted to see what happens if I only define a theme color for dark mode, but access the page in light mode.

<meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

These results surprised me the most because I expected all mobile browsers to ignore the media attribute and just use the dark color in the meta tag regardless, but ordinary Chrome Canary completely ignores the whole meta tag, even though it doesn’t support the media attribute. As expected, both Canary PWAs fall back to the color defined in the manifest file.

The other interesting thing is that Safari displays a theme-color even though I haven’t defined one for light mode. That’s because Safari will pick a color on its own, if you don’t provide a theme-color. In this case, it uses the background color of the page, but it also might use the background color of the <header> element, for example.

If you want to define a theme color for light and dark mode, your best bet is to define both colors and use the first meta tag as a fallback for browsers that don’t support the media feature.

<meta name="theme-color" content="#319197" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#872e4e" media="(prefers-color-scheme: dark)">

Safari has proven that theme-color works great on desktop browsers, too. I’m sure that designers and developers will find many creative ways to use this meta tag, especially considering that the value can be changed via JavaScript. I’ve collected and created some interesting demos for your inspiration.

Demos and use cases

Theming

poolsuite.net provides different themes for the site and changes the theme-color accordingly.

Max Böck also changes the theme-color on his website when you change the theme.

Page theming

Most websites don’t provide custom themes, but you can still give your pages that certain something. Dave uses different key colors in his blog posts for links and icons, and now also in the tab bar.

Gradients

If you’re using gradients on your page, you can highlight your styling by making the gradient span the whole browser. The theme-color meta tag doesn’t support gradients, but you can use the same color for the meta tag and the start color of the gradient of you page’s background.

<meta name="theme-color" content="rgb(0, 235, 255)">  <style>   body {     background: linear-gradient(rgb(0, 235, 255), #08124a);   } </style>
Form validation

I built this proof of concept of a form that changes theme-color on form validation. It starts with a blue tab bar which turns red if the submitted data is invalid or green if it’s valid.

const email = document.querySelector('input') const themeColor = document.querySelector('meta[name="theme-color"]') const msg = document.querySelector('[aria-live]') let color = '#FA0000' let message = 'Error message'  document.querySelector('button').addEventListener('click', (e) => {   e.preventDefault()    email.reportValidity()   email.setAttribute('aria-invalid', true)    if (email.validity.valid) {     color = '#00FF00'     message = "Success message!"     email.setAttribute('aria-invalid', false)   }    msg.textContent = message   themeColor.setAttribute('content', color) });
Disco mode

I’m not saying that you should, but you could put your site in 💃 Disco Mode 🕺 by combining setInterval and hsl() colors.

/* Inspired by https://twitter.com/argyleink/status/1408184587885309952 */  const motion = window.matchMedia("(prefers-reduced-motion: no-preference)");  // Check if users don't have a preference for reduced motion if (motion.matches) {   let scheme = document.querySelector('meta[name="theme-color"]')   let hue = 0   let color    setInterval(() => {     color = `hsl($ {hue+=5} 50% 30%)`     document.body.style.background = color;     scheme.setAttribute('content', color)   }, 50)
Scrolling

Stuart had a great idea, he suggested changing theme color on scroll. I built this quick prototype, again using hsl() colors.

Please only do this if it doesn’t affect performance negatively.

Max built a demo in which he changes the theme-color according to the background color of the current section in the viewport using Intersection Observer.

const setThemeColor = (color) => {   const meta = document.querySelector('meta[name="theme-color"]')   if (meta) {     meta.setAttribute('content', color)   } }  if ("IntersectionObserver" in window) {   const observer = new IntersectionObserver(entries => {       entries.forEach(entry => {         const { isIntersecting, target } = entry         if (isIntersecting) {           const color = window.getComputedStyle(target).getPropertyValue("background-color");           setThemeColor(color)         }       })   }, {     root: document.getElementById('viewport'),     rootMargin: "1px 0px -100% 0px",     treshold: 0.1   })      document.querySelectorAll('.section').forEach(section => {     observer.observe(section)   }) }
Extracting color

Another interesting idea is to extract the dominant or average color from your header images automatically and use it as the theme-color.

<script type="module">   import fastAverageColor from "https://cdn.skypack.dev/fast-average-color@6.4.0";   const fac = new fastAverageColor();        fac.getColorAsync(document.querySelector('img'))     .then(color => {       document.querySelector('meta[name="theme-color"]').setAttribute('content', color.rgba)     })     .catch(e => {       console.log(e);     }); </script>     <img src="/amy-humphries-2M_sDJ_agvs-unsplash.jpg" alt="A sea star on blue sand." />

That is just a handful of ideas, but I already like where this is going and I’m sure that you’ll come up with even more creatives ways of using the theme-color meta tag.

Resources


The post Meta Theme Color and Trickery appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Safari 15: New UI, Theme Colors, and… a CSS-Tricks Cameo!

There’s a 33-minute video (and resources) over on apple.com covering the upcoming Safari changes we saw in the WWDC keynote this year in much more detail. Look who’s got a little cameo in there:

Perhaps the most noticeable thing there in Safari 15 on iOS is URL bar at the bottom! Dave was speculating in our little Discord watch party that this probably fixes the weird issues with 100vh stuff on iOS. But I really just don’t know, we’ll have to see when it comes out and we can play with it. I’d guess the expectation is that, in order for us to do our own fixed-bottom-UI stuff, we’d be doing:

.bottom-nav {    position: fixed; /* maybe sticky is better if part of overall page layout? */   bottom: 100vh; /* fallback? */   bottom: calc(100vh - env(safe-area-inset-bottom)); /* new thing */ }

On desktop, the most noticeable visual feature is probably the theme-color meta tags.

This isn’t even a brand new Apple-only thing. This is the same <meta> tag that Chrome’s Android app has used since 2014, so you might already be sporting it on your own site. The addition is that it supports media queries.

<meta name="theme-color"        content="#ecd96f"        media="(prefers-color-scheme: light)"> <meta name="theme-color"        content="#0b3e05"        media="(prefers-color-scheme: dark)">

It’s great to see Safari get aspect-ratio and the new fancy color systems like lab() and lch() as well. Top-level await in JavaScript is great as it makes patterns like conditional imports easier.

I don’t think all this would satisfy Alex. We didn’t exactly get alternative browser engines on iOS or significant PWA enhancements (both of which would be really great to see). But I applaud it all—it’s good stuff. While I do think Google generally takes privacy more seriously than what general internet chatter would have to believe, it’s notable to compare each company’s newly-released features. If you’ll forgive a bit of cherry-picking, Google is working on FLoC, a technology very specifically designed to help targeted advertising. Apple is working on Private Relay, a technology very specifically to making web browsing untrackable.


The post Safari 15: New UI, Theme Colors, and… a CSS-Tricks Cameo! appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Pinned Audio WordPress Theme

I’m afraid I have to start this with a whole backstory, as the journey here is the point, not so much the theme.

A fella wrote to me a while back outlining a situation he was in. His company has a bunch of WordPress sites for public radio, many of which are essentially homes for podcasts. There is one specific bit of functionality he thought would be ideal for them all: to have a “pinned” audio player. Like you could play a podcast, then continue navigating around the site without that podcast stopping.

This is somewhat tricky to pull off in WordPress, because WordPress does full page reloads like any other regular website not doing anything special with link handling or history manipulation. When a page reloads, any audio on the page stops playing. That’s just how the web works.

So how would you pull it off on a WordPress site? Well, you could make it a headless WordPress site and rebuild the entire front-end as a Single Page App. Sounds fun to me, but I’d be hesitant to make that call just for this one thing.

What else could you do? You could find a way to make the page never reload. I remember doing this on a little static site 10 years ago, but that wasn’t a full blown WordPress site and I didn’t even bother updating the URL back then.

What if you did this…

  1. Intercept internal link clicks
  2. Ajax’d the content from that URL
  3. Replaced the content on the page with that new content

I’ll do this in jQuery quick for ya:

$  ("a").on("click", () => {   const url = $  (this).attr("href");   $  .get(url + " main", (data) => {     $  ("main").html(data);     history.pushState({}, "", url);   }); });

That’s not far off from being literally functional. You’d wanna watch for a popstate event to deal with the back button, but that’s only a few more lines.

In this hypothetical world, you’d lay out the site like:

<html> <!-- ... -->  <body>    <main></main>    <audio src="" controls ...></audio> </body> </html>

So all that <main> content gets swapped out, the URL changes, but your <audio> player is left alone to keep playing in peace. You’d write more JavaScript to give people a way to update what podcast is playing and such.

Turns out there is more to think about here though. Are any inline scripts on the content going to run? What about updating the <title> too? There are enough edge concerns you probably will get annoyed dealing with it.

I wanted to have a play with this stuff, so I tossed together a WordPress theme and reached for Turbo instead of hand-writing something. Turbo (the new version of Turbolinks) is designed just for this. It’s a JavaScript library you drop on the page (no build process, no config) and it just works. It intercepts internal link clicks, Ajax’s for new content, etc. But it has this interesting feature where if you put a data-turbo-permanent attribute on an HTML element, it will persist it across that reload. So I did that for the audio player here.

Here’s the thing though.

I just don’t have time to finish this project properly. It was fun to have a play, but my interest in it has kinda petered out. So I’ll leave it alone for now:

It almost works, minus one glaring bug that the audio stops playing on the first navigation, then works after that. I’m sure it’s fixable, but I just don’t have much skin in this game. I figure I’ll just bow out and leave this code around for someone to pick up if it’s useful for them.

Another thing at play here is that Turbo is from Basecamp, and Basecamp has rather imploded recently making it not feel great to be using their software. Exacerbated by the fact that Sam Stephenson wrote 75% of Turbo and has said he won’t be touching it (or other related projects) unless the software is moved to its own foundation. Turbo was already in a shaky place since it seemed buggy compared to Turbolinks, and now is on very gnarly ground.


The post Pinned Audio WordPress Theme appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Flash of inAccurate coloR Theme (FART)

There is a lot to think about when implementing a dark mode theme on a website. We have a huge guide on it. There are some very clever quick wins out there, but there are also some quite tricky things to pull off. One of those tricky things is how it’s not a dark mode “toggle” between dark and light, but really three modes you need to support: dark, light, and user system preference. That’s similar to how audio preferences work in many apps, which allow you to very specifically cohose which audio input or output you want, or just default to the system preference.

CSS and JavaScript can handle the system preference angle, via the prefers-color-scheme API, but if the user preference has changed, and that preference is now different than the user preference, you’re in the territory of “Flash of inAccurate coloR Theme” or FART. Ok ok, it’s a tounge-in-cheek acronym, but it’s potentially quite a visually obnoxious problem so I’m keeping it. It’s in the same vein that FOUT (Flash of Unstyled Text) is for font loading.

Storing a user preference means something like a cookie, localStorage, or some kind of database. If access to that data means running JavaScript, e.g. localStorage.getItem('color-mode-preference');, then you’re in FART territory, because your JavaScript is very likely running after a page’s first render, lest you’re otherwise unnecessarily delaying page render.

User preference is “dark” mode, but the system preference is “light” mode (or unset), so when the page refreshes, you get FART.

You can access a cookie with a server-side language before page-render, meaning you could use it to output something like <html class="user-setting-dark-mode"> and style accordingly, which deftly avoids FART, but that means a site that even has access to a server-side language (Jamstack sites do not, for example).

Allllll that to say that I appreciated Rob Morieson’s article about dark mode because it didn’t punt on this important issue. It’s very specifically about doing this in Next.js, and uses localStorage, but because Next.js is JavaScript-rendered, you can force it to check the user-saved preference as the very first thing it does. That means it will render correctly the the first time (no flash). You do have to turn off server-side rendering for this to work, which is a gnarly trade-off though.

I’m not convinced there is a good way to avoid FART without a server-side language or force-delayed page renders.


The post Flash of inAccurate coloR Theme (FART) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Theming and Theme Switching with React and styled-components

I recently had a project with a requirement to support theming on the website. It was a bit of a strange requirement, as the application is mostly used by a handful of administrators. An even bigger surprise was that they wanted not only to choose between pre-created themes, but build their own themes. I guess the people want what they want!

Let’s distill that into a complete list of more detailed requirements, then get it done!

  • Define a theme (i.e. background color, font color, buttons, links, etc.)
  • Create and save multiple themes
  • Select and apply a theme
  • Switch themes
  • Customize a theme

We delivered exactly that to our client, and the last I heard, they were using it happily!

Let’s get into building exactly that. We’re going to use React and styled-components. All the source code used in the article can be found in the GitHub Repository.

The setup

Let’s set up a project with React and styled-components. To do that, we will be using the create-react-app. It gives us the environment we need to develop and test React applications quickly.

Open a command prompt and use this command to create the project:

npx create-react-app theme-builder

The last argument, theme-builder, is just the name of the project (and thus, the folder name). You can use anything you like.

It may take a while. When done, navigate it to it in the command line with cd theme-builder. Open the file src/App.js file and replace the content with the following:

import React from 'react';  function App() {   return (     <h1>Theme Builder</h1>   ); }  export default App;

This is a basic React component that we will modify soon. Run the following command from the project root folder to start the app:

# Or, npm run start yarn start

You can now access the app using the URL http://localhost:3000.

A simple heading 1 that says Theme Builder in black on a white background.

create-react-app comes with the test file for the App component. As we will not be writing any tests for the components as part of this article, you can choose to delete that file.

We have to install a few dependencies for our app. So let’s install those while we’re at it:

# Or, npm i ... yarn add styled-components webfontloader lodash

Here’s what we get:

  • styled-components: A flexible way to style React components with CSS. It provides out-of-the-box theming support using a wrapper component called, <ThemeProvider>. This component is responsible for providing the theme to all other React components that are wrapped within it. We will see this in action in a minute.
  • Web Font Loader: The Web Font Loader helps load fonts from various sources, like Google Fonts, Adobe Fonts, etc. We will use this library to load fonts when a theme is applied.
  • lodash: This is a JavaScript utility library for some handy little extras.

Define a theme

This is the first of our requirements. A theme should have a certain structure to define appearance, including colors, fonts, etc. For our application, we will define each theme with these properties:

  • unique identifier
  • theme name
  • color definitions
  • fonts
Screenshot of a code editor showing the organized structure of properties for a sea wave theme.
A theme is a structured group of properties that we’ll use in the application.

You may have more properties and/or different ways to structure it, but these are the things we’re going to use for our example.

Create and save multiple themes

So, we just saw how to define a theme. Now let’s create multiple themes by adding a folder in the project at src/theme and a file in it called, schema.json. Here’s what we can drop in that file to establish “light” and “sea wave” themes:

{   "data" : {     "light" : {       "id": "T_001",       "name": "Light",       "colors": {         "body": "#FFFFFF",         "text": "#000000",         "button": {           "text": "#FFFFFF",           "background": "#000000"         },         "link": {           "text": "teal",           "opacity": 1         }       },       "font": "Tinos"     },     "seaWave" : {       "id": "T_007",       "name": "Sea Wave",       "colors": {         "body": "#9be7ff",         "text": "#0d47a1",         "button": {           "text": "#ffffff",           "background": "#0d47a1"         },         "link": {           "text": "#0d47a1",           "opacity": 0.8         }       },       "font": "Ubuntu"     }   } }

The content of the schema.json file can be saved to a database so we can persist all the themes along with the theme selection. For now, we will simply store it in the browser’s localStorage. To do that, we’ll create another folder at src/utils with a new file in it called, storage.js. We only need a few lines of code in there to set up localStorage:

export const setToLS = (key, value) => {   window.localStorage.setItem(key, JSON.stringify(value)); }  export const getFromLS = key => {   const value = window.localStorage.getItem(key);    if (value) {     return JSON.parse(value);   } }

These are simple utility functions to store data to the browser’s localStorage and to retrieve from there. Now we will load the themes into the browser’s localStorage when the app comes up for the first time. To do that, open the index.js file and replace the content with the following,

import React from 'react'; import ReactDOM from 'react-dom'; import App from './App';  import * as themes from './theme/schema.json'; import { setToLS } from './utils/storage';  const Index = () => {   setToLS('all-themes', themes.default);   return(     <App />   ) }  ReactDOM.render(   <Index />   document.getElementById('root') );

Here, we are getting the theme information from the schema.json file and adding it to the localStorage using the key all-themes. If you have stopped the app running, please start it again and access the UI. You can use DevTools in the browser to see the themes are loaded into localStorage.

The theme with DevTools open and showing the theme properties in the console.
All of the theme props are properly stored in the browser’s localStorage, as seen in DevTools, under Application → Local Storage.

Select and apply a theme

We can now use the theme structure and supply the theme object to the <ThemeProvider> wrapper.

First, we will create a custom React hook. This will manage the selected theme, knowing if a theme is loaded correctly or has any issues. Let’s start with a new useTheme.js file inside the src/theme folder with this in it:

import { useEffect, useState } from 'react'; import { setToLS, getFromLS } from '../utils/storage'; import _ from 'lodash';  export const useTheme = () => {   const themes = getFromLS('all-themes');   const [theme, setTheme] = useState(themes.data.light);   const [themeLoaded, setThemeLoaded] = useState(false);    const setMode = mode => {     setToLS('theme', mode)     setTheme(mode);   };    const getFonts = () => {     const allFonts = _.values(_.mapValues(themes.data, 'font'));     return allFonts;   }    useEffect(() =>{     const localTheme = getFromLS('theme');     localTheme ? setTheme(localTheme) : setTheme(themes.data.light);     setThemeLoaded(true);   }, []);    return { theme, themeLoaded, setMode, getFonts }; };

This custom React hook returns the selected theme from localStorage and a boolean to indicate if the theme is loaded correctly from storage. It also exposes a function, setMode, to apply a theme programmatically. We will come back to that in a bit. With this, we also get a list of fonts that we can load later using a web font loader.

It would be a good idea to use global styles to control things, like the site’s background color, font, button, etc. styled-components provides a component called, createGlobalStyle that establishes theme-aware global components. Let’s set those up in a file called, GlobalStyles.js in the src/theme folder with the following code:

import { createGlobalStyle} from "styled-components";  export const GlobalStyles = createGlobalStyle`   body {     background: $ {({ theme }) => theme.colors.body};     color: $ {({ theme }) => theme.colors.text};     font-family: $ {({ theme }) => theme.font};     transition: all 0.50s linear;   }    a {     color: $ {({ theme }) => theme.colors.link.text};     cursor: pointer;   }    button {     border: 0;     display: inline-block;     padding: 12px 24px;     font-size: 14px;     border-radius: 4px;     margin-top: 5px;     cursor: pointer;     background-color: #1064EA;     color: #FFFFFF;     font-family: $ {({ theme }) => theme.font};   }    button.btn {     background-color: $ {({ theme }) => theme.colors.button.background};     color: $ {({ theme }) => theme.colors.button.text};   } `;

Just some CSS for the <body>, links and buttons, right? We can use these in the App.js file to see the theme in action by replace the content in it with this:

// 1: Import import React, { useState, useEffect } from 'react'; import styled, { ThemeProvider } from "styled-components"; import WebFont from 'webfontloader'; import { GlobalStyles } from './theme/GlobalStyles'; import {useTheme} from './theme/useTheme';  // 2: Create a cotainer const Container = styled.div`   margin: 5px auto 5px auto; `;  function App() {   // 3: Get the selected theme, font list, etc.   const {theme, themeLoaded, getFonts} = useTheme();   const [selectedTheme, setSelectedTheme] = useState(theme);    useEffect(() => {     setSelectedTheme(theme);    }, [themeLoaded]);    // 4: Load all the fonts   useEffect(() => {     WebFont.load({       google: {         families: getFonts()       }     });   });    // 5: Render if the theme is loaded.   return (     <>     {       themeLoaded && <ThemeProvider theme={ selectedTheme }>         <GlobalStyles/>         <Container style={{fontFamily: selectedTheme.font}}>           <h1>Theme Builder</h1>           <p>             This is a theming system with a Theme Switcher and Theme Builder.             Do you want to see the source code? <a href="https://github.com/atapas/theme-builder" target="_blank">Click here.</a>           </p>         </Container>       </ThemeProvider>     }     </>   ); }  export default App;

A few things are happening here:

  1. We import the useState and useEffect React hooks which will help us to keep track of any of the state variables and their changes due to any side effects. We import ThemeProvider and styled from styled-components. The WebFont is also imported to load fonts. We also import the custom theme, useTheme, and the global style component, GlobalStyles.
  2. We create a Container component using the CSS styles and styled component.
  3. We declare the state variables and look out for the changes.
  4. We load all the fonts that are required by the app.
  5. We render a bunch of text and a link. But notice that we are wrapping the entire content with the <ThemeProvider> wrapper which takes the selected theme as a prop. We also pass in the <GlobalStyles/> component.

Refresh the app and we should see the default “light” theme enabled.

The theme with a white background and black text.
Hey, look at that clean, stark design!

We should probably see if switching themes works. So, let’s open the useTheme.js file and change this line:

localTheme ? setTheme(localTheme) : setTheme(themes.data.light);

…to:

localTheme ? setTheme(localTheme) : setTheme(themes.data.seaWave);

Refresh the app again and hopefully we see the “sea wave” theme in action.

The same theme in with a blue color scheme with a light blue background and dark blue text and a blue button.
Now we’re riding the waves of this blue-dominant theme.

Switch themes

Great! We are able to correctly apply themes. How about creating a way to switch themes just with the click of a button? Of course we can do that! We can also provide some sort of theme preview as well.

A heading instructs the user to select a theme and two card components are beneath the heading, side-by-side, showing previews of the light theme and the sea wave theme.
A preview of each theme is provided in the list of options.

Let’s call each of these boxes a ThemeCard, and set them up in a way they can take its theme definition as a prop. We’ll go over all the themes, loop through them, and populate each one as a ThemeCard component.

{   themes.length > 0 &&    themes.map(theme =>(     <ThemeCard theme={data[theme]} key={data[theme].id} />   )) }

Now let’s turn to the markup for a ThemeCard. Yours may look different, but notice how we extract its own color and font properties, then apply them:

const ThemeCard = props => {   return(     <Wrapper        style={{backgroundColor: `$ {data[_.camelCase(props.theme.name)].colors.body}`, color: `$ {data[_.camelCase(props.theme.name)].colors.text}`, fontFamily: `$ {data[_.camelCase(props.theme.name)].font}`}}>       <span>Click on the button to set this theme</span>       <ThemedButton         onClick={ (theme) => themeSwitcher(props.theme) }         style={{backgroundColor: `$ {data[_.camelCase(props.theme.name)].colors.button.background}`, color: `$ {data[_.camelCase(props.theme.name)].colors.button.text}`, fontFamily: `$ {data[_.camelCase(props.theme.name)].font}`}}>         {props.theme.name}       </ThemedButton>     </Wrapper>   ) }

Next up, let’s create a file called ThemeSelector.js in our the src folder. Copy the content from here and drop it into the file to establish our theme switcher, which we need to import in App.js:

import ThemeSelector from './ThemeSelector';

Now we can use it inside the Container component:

<Container style={{fontFamily: selectedTheme.font}}>   // same as before   <ThemeSelector setter={ setSelectedTheme } /> </Container>

Let’s refresh the browser now and see how switching themes works.

An animated screenshot showing the theme changing when it is selected from the list of theme card options.

The fun part is, you can add as many as themes in the schema.json file to load them in the UI and switch. Check out this schema.json file for some more themes. Please note, we are also saving the applied theme information in localStorage, so the selection will be retained when you reopen the app next time.

Selected theme stored in the Local Storage.

Customize a theme

Maybe your users like some aspects of one theme and some aspects of another. Why make them choose between them when they can give them the ability to define the theme props themselves! We can create a simple user interface that allows users to select the appearance options they want, and even save their preferences.

Animated screenshot showing a modal opening with a list of theme options to customize the appearance, including the them name, background color, text color, button text color, link color, and font.

We will not cover the theme creation code explanation in details but, it should be easy by following the code in the GitHub Repo. The main source file is CreateThemeContent.js and it is used by App.js. We create the new theme object by gathering the value from each input element change event and add the object to the collection of theme objects. That’s all.

Before we end…

Thank you for reading! I hope you find what we covered here useful for something you’re working on. Theming systems are fun! In fact, CSS custom properties are making that more and more a thing. For example, check out this approach for color from Dieter Raber and this roundup from Chris. There’s also this setup from Michelle Barker that relies on custom properties used with Tailwind CSS. Here’s yet another way from Andrés Galente.

Where all of these are great example for creating themes, I hope this article helps take that concept to the next level by storing properties, easily switching between themes, giving users a way to customize a theme, and saving those preferences.

Let’s connect! You can DM me on Twitter with comments, or feel free to follow.


The post Theming and Theme Switching with React and styled-components appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Making dark theme switcher with PostCSS.

You have noticed that there is a new design trend that is floating around web design since 2019, the dark mode. Facebook, Apple, and Google both introduced the dark version of their software.

Why a dark theme

Most of you probably think this is just a trend that will disappear after some years, well, let me say that this is not like many other trends, dark UI provide different advantages and they are not something just related to the “designer mood”. Let’s see why a dark mode on your applications and websites are something useful.

Better for batteries

Pixels on a screen consume more energy to display light colors rather than dark ones. Consequently, devices’ batteries can save energy and improve their daily duration while using dark UI.

Better for dark environments

Most of us use their smartphone and laptops while at home. Such environments are typically not so bright. The dark mode can help the use of the application while indoor, without causing visual disturbances.

Better for people

Some people with — or without — visual diseases, like epilepsy, can have unfortunate events by being flashed by bright applications. Having a dark mode means being more accessible.

Preparing styles

A very simple theme switcher should offer at least 3 options:

  • Dark theme
  • Light theme
  • Automatic theme (should be on by default)

Wait, what’s the automatic theme? Well, modern operating systems allow users to change the global visual appearance by setting os-wide options that enable the dark or light mode. The automatic option make sure to respect the OS preference if the user has not specified any theme.

To make this even more simple, we’ll use PostCSS and a simple but useful plugin called postcss-dark-theme-class.

yarn add postcss-dark-theme-class

This plugin will do 70% of the work, once installed, add it to your PostCSS config and configure the selectors you want to use to activate the correct theme, which will be used by the plugin to generate the correct CSS:

module.exports = {   plugins: [     /* ...other plugins */      require('postcss-dark-theme-class')({       darkSelector: '[data-theme="dark"]',       lightSelector: '[data-theme="light"]'     })   ] }

Once the plugin is up and running, we can start defining our dark and light themes using a CSS specific media query prefers-color-scheme. This special media query will handle the automatic part of our themes by applying the correct theme based on the user’s OS preferences:

:root {   --accent-color: hsl(226deg 100% 50%);   --global-background: hsl(0 0% 100%);   --global-foreground: hsl(0 0% 0%); }  @media (prefers-color-scheme: dark) {   :root {     --accent-color: hsl(226deg 100% 50%);     --global-background: hsl(0 0% 0%);     --global-foreground: hsl(0 0% 100%);   } }

If the user is using a dark version of his OS, the set inside the media query will apply, overwriting others, otherwhise the set of properties outside the media query is used. Since it’s pure CSS, this behaviour is on by default.

Browsers will now adapt the color scheme automatically based on the users’ OS preferences. Nice done! 🚀 Now it’s time to make the theme switcher allow users to specify what theme to use, overriding the OS preference.

Making the theme switcher

s we said, our switcher should have three options, we can use a simple select element, or build a set of buttons:

<button aria-current="true" data-set-theme="auto">Auto</button> <button aria-current="false" data-set-theme="dark">Dark</button> <button aria-current="false" data-set-theme="light">Light</button>

We’ll build the switcher using vanilla JS, but you can do it with any framework you want, the concept is the same: we have to add the selectors we defined inside the PostCSS plugin to the root element, based on the clicked button.

const html = document.documentElement const themeButtons = document.querySelectorAll('[data-set-theme]');  themeButtons.forEach((button) => { 	const theme = button.dataset.setTheme;  	button.addEventListener('click', () => { 		html.dataset.theme = theme; 	}) })

Each time we click on a theme button, the value set as data-set-theme is applied as value of the data-theme attribute on the document root element.

Check it live:

Where is the magic?

The magic is made by postcss-dark-theme-class — which will add our [data-theme] custom attribute to the :root selectors we wrote — during the CSS transpilation. Here what it generates from our code:

/* Our automatic and user specified light theme */ :root {   --accent-color: hsl(226deg, 100%, 50%);   --global-background: hsl(0, 0%, 100%);   --global-foreground: hsl(0, 0%, 0%); }  /* Our automatic dark theme */ @media (prefers-color-scheme: dark) {   :root:not([data-theme="light"]) {     --accent-color: hsl(226deg, 100%, 50%);     --global-background: hsl(0, 0%, 0%);     --global-foreground: hsl(0, 0%, 100%);   } }  /* Our dark theme specified by the user */ :root[data-theme="dark"] {   --accent-color: hsl(226deg, 100%, 50%);   --global-background: hsl(0, 0%, 0%);   --global-foreground: hsl(0, 0%, 100%); }

Bonus tip

You may notice that the --accent-color custom property defined inside themes doesn’t change. If you have colors that will not change based on the theme, you can remove them from the prefers-color-scheme at-rule.

In this way, they will not be duplicated and the one defined outside the media query will always apply.

:root {   --accent-color: hsl(226deg 100% 50%);   --global-background: hsl(0 0% 100%);   --global-foreground: hsl(0 0% 0%); }  @media (prefers-color-scheme: dark) {   :root {     --global-background: hsl(0 0% 0%);     --global-foreground: hsl(0 0% 100%);   } }

The post Making dark theme switcher with PostCSS. appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Where to Learn WordPress Theme Development

Over a decade ago, I did a little three-part video series on Designing for WordPress. Then I did other series with the same spirit, like videocasting the whole v10 redesign, a friend’s website, and even writing a book. Those are getting a little long in the tooth though. You might still learn from watching them if you’re getting into WordPress theme development, but there will be moments that feel very aged (old UI’s and old versions of software). All the code still works though, because WordPress is great at backward compatibility. I still hear from people who found those videos very helpful for them.

But since time has pressed on, and I was recently asked what resources I would suggest now, I figured I’d have a look around and see what looks good to me.

Do you like how I plopped the WordPress logo over some stock art I bought that features both a computer and a chalkboard, by which to evoke a feeling of “learning”? So good. I know.

Who are we talking to?

There’s a spectrum of WordPress developers, from people who don’t know any code at all or barely touch it, to hardcore programming nerds building custom everything.

  1. Pick out a theme that looks good, use it.
  2. 🤷‍♂️
  3. 🤷‍♂️
  4. 🤷‍♂️
  5. 🤷‍♂️
  6. Hardcore programmer nerd.

I can’t speak to anybody on either edge of that spectrum. There is this whole world of people in the middle. They can code, but they aren’t computer science people. They are get the job done people. Maybe it’s something like this:

  1. Pick out a theme that will work, use it.
  2. Start with a theme, customize it a bit using built-in tools.
  3. Start with a theme, hack it up with code to do what you need it to do.
  4. Start from scratch, build out what you need.
  5. Start from scratch, build a highly customized site.
  6. Hardcore programmer nerd.

I’ve always been somewhere around #4, and I think that’s a nice sweet spot. I try to let off-the-shelf WordPress and big popular plugins do the heavy lifting, but I’ll bring-my-own front-end (HTML, CSS, and JavaScript) and customize what I have to. I’m making templates. I’m writing queries. I’m building blocks. I’m modularizing where I can.

I feel powerful in that zone. I can build a lot of sites that way, almost by myself. So where are the resources today that help you learn this kind of WordPress theme development? Lemme see what I can find.

Wing it, old school

There is something to be said for learning by doing. Trial by fire. I’ve learned a lot under these circumstances in my life.

The trick here is to get WordPress installed on a live server and then play with the settings, plugins, customizer, and edit the theme files themselves to make the site do things. You’ll find HTML in those theme files — hack it up! You’ll see PHP code spitting out content. Can you tell what and how to manipulate it? You’ll find a CSS file in the theme — edit that sucker!

Editing a WordPress theme and seeing what happens

The official documentation can help you somewhat here:

To some degree, I’m a fan of doing it live (on a production website) because it lends a sense of realness to what you are doing when you are a beginner. The stakes are high there, giving you a sense of the power you have. When I make these changes, they are for anyone in the world with an internet connection to see.

I did this in my formative years by buying a domain name and hosting, installing WordPress on that hosting, logging into it with SFTP credentials, and literally working on the live files. I used Coda, which is still a popular app, and is being actively developed into a new version of itself as I write.

This is Nova, a MacOS code editor from Panic that has SFTP built-in.

Hopefully, the stakes are real but low. Like you’re working on a pet project or your personal site. At some point, hacking on production sites becomes too dangerous of an idea. One line of misplaced PHP syntax can take down the entire site.

If you’re working on something like a client site, you’ll need to upgrade that workflow.

Modern winging it

The modern, healthy, standard way for working on websites is:

  1. Work on them locally.
  2. Use version control (Git), where new work is done in branches of the master branch.
  3. Deployment to the production website is done when code is pushed to the master branch, like your development branch is merged in.

I’ve done a recent video on this whole workflow as I do it today. My toolset is:

  • Work locally with Local by Flywheel.
  • My web hosting is also Flywheel, but that isn’t required. It could be anything that gives you SFTP access and runs what WordPress needs: Apache, PHP, and MySQL. Disclosure, Flywheel is a sponsor here, but because I like them and their service :).
  • Code is hosted on a private repo on GitHub.
  • Deployment to the Flywheel hosting is done by Buddy. Buddy watches for pushes to the master branch and moves the files over SFTP to the production site.
Local by Flywheel

Now that you have a local setup, you can go nuts. Do whatever you want. You can’t break anything on the live site, so you’re freer to make experimental changes and just see what happens.

When working locally, it’s likely you’ll be editing files with a code editor. I’d say the most popular choice these days is the free VS Code, but there is also Atom and Sublime, and fancier editors like PhpStorm.

The freedom of hacking on files is especially apparent once you’ve pushed your code up to a Git repo. Once you’ve done that, you have the freedom of reverting files back to the state of the last push.

I use the Git software Tower, and that lets me can see what files have changed since I last committed code. If I’ve made a mistake, caused a problem, or done something I don’t like — even if I don’t remember exactly what I changed — I can discard those changes back to their last state. That’s a nice level of freedom.

When I do commit code, to master or by merging a branch into master, that’s when Buddy kicks in and deploys the changes to the production site.

CSS-Tricks itself is a WordPress site, which has continuously evolved over 13 years.

But like, where do you start?

We’re talking about WordPress theme development here, so you start with a theme. Themes are literally folders of files in your WordPress installation.

root   - /wp-content/     - /themes/        - /theme-name/

WordPress comes with some themes right out of the box. As I write, the Twenty Twenty theme ships with WordPress, and it’s a nice one! You could absolutely start your theme hackin’ on that.

Themes tend to have some opinions about how they organize themselves and do things, and Twenty Twenty is no different. I’d say, perhaps controversially, that there is no one true way to organize your theme, so long as it’s valid code and does things the “WordPress” way. This is just something you’ll have to get a feel for as you make themes.

Starter themes

Starter themes were a very popular way to start building a theme from scratch in my day. I don’t have a good sense if that’s still true, but the big idea was a theme with all the basic theme templates you’ll need (single blog post pages, a homepage, a 404 page, search results page, etc.) with very little markup and no styling at all. That way you have an empty canvas from which to build out all your HTML, CSS, and JavaScript yourself to your liking. Sorta like you’re building any other site from scratch with these core technologies, only with some PHP in there spitting out content.

There was a theme called Starkers that was popular, but it’s dead now. I made one called BLANK myself but haven’t touched that in a long time. In looking around a bit, I found some newer themes with this same spirit. Here’s the best three I found:

I can’t personally vouch for them, but they’ve all been updated somewhat recently and look like pretty good starting points to me. I’d give them a shot in the case that I was starting from absolute scratch on a project. I’d be tempted to download one and then spruce it up exactly how I like it and save that as my own starter in case I needed to do it again.

It feels worth mentioning that a lot of web development isn’t starting from scratch, but rather working on existing projects. In that case, the process is still getting a local environment set up; you just aren’t starting from scratch, but with the existing theme. I’d suggest duplicating the theme and changing the name while you hack on it, so even if you deploy it, it doesn’t affect the live theme. Others might suggest using the starter as a “parent” theme, then branching off into a “child” theme.

To get your local development environment all synced up with exactly what the production website is like, I think the best tool is WP DB Migrate Pro, which can yank down the production database to your local site and all the media files (paid product and a paid add-on, worth every penny).

Fancier Starter Themes

Rather than starting from absolute scratch, there are themes that come with sensible defaults and even modern build processes for you start with. The idea is that building a site with essentially raw HTML, CSS, and JavaScript, while entirely doable, just doesn’t have enough modern conveniences to be comfortable.

Here are some.

  • Morten Rand-Hendriksen has a project called WP Rig that has all sorts of developer tools built into it. A Gulp-based build process spins up a BrowserSync server for auto updating. JavaScript gets processed in Babel. CSS gets processed in PostCSS, and code is linted. He teaches WordPress with it.
  • Roots makes a theme called Sage that comes with a templating engine, your CSS framework of choice, and fancy build process stuff.
  • Ignition has a build process and all sorts of helpers.
  • Timber comes with a templating engine and a bunch of code helpers.

I think all these are pretty cool, but are also probably not for just-starting-out beginner developers.

Books

This is tough because of how many there are. In a quick Google search, I found one site selling fifteen WordPress books as a bundle for $ 9.99. How would you even know where to start? How good can they be for that rock bottom price? I dunno.

I wrote a book with Jeff Starr ages ago called Digging Into WordPress. After all these years, Jeff still keeps the book up to date, so I’d say that’s a decent choice! Jeff has other books like The Tao of WordPress and WordPress Themes In Depth.

A lot of other books specifically about WordPress theme development are just fairly old. 2008-2015 stuff. Again, not that there isn’t anything to be learned there, especially as WordPress doesn’t change that rapidly, but still, I’d want to read a book more recent that half a decade old. Seems like a big opportunity for a target audience as large as WordPress users and developers. Or if there is already stuff that I’m just not finding, lemme know in the comments.

Perhaps learning is shifting so much toward online that people don’t write books as much…

Online learning courses

Our official learning partner Frontend Masters has one course on WordPress focused on JavaScript and WordPress, so that might not be quite perfect for learning the basics of theme development. Still, fascinating stuff.

Here’s some others that looked good to me while looking around:

Zac’s course looks like the most updated and perhaps the best option there.

A totally different direction for theme Development

One way to build a site with WordPress is not to use WordPress themes at all! Instead, you can use the WordPress API to suck data out of WordPress and build a site however the heck you please.

This idea of decoupling the CMS and the front end you build is pretty neat. It’s often referred to as using a “headless” CMS. It’s not for everyone. (One big reason is that, in a way, it doubles your technical debt.). But it can bring a freedom to both the CMS and the front end to evolve independently.

The post Where to Learn WordPress Theme Development appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

How to Build Vue Components in a WordPress Theme

Intrigued by the title and just wanna see some code? Skip ahead.

A few months ago, I was building a WordPress website that required a form with a bunch of fancy conditional fields. Different options and info were required for different choices you could make on the form, and our client needed complete control over all fields 1. In addition, the form needed to appear in multiple places in each page, with slightly different configs.

And the header instance of the form needed to be mutually exclusive with the hamburger menu, so that opening one closes the other.

And the form had text content that was relevant to SEO.

And we wanted the server response to present some cute animated feedback.

(Phew.)

The whole thing felt complex enough that I didn’t want to handle all that state manually. I remembered reading Sarah Drasner’s article “Replacing jQuery With Vue.js: No Build Step Necessary” which shows how to replace classic jQuery patterns with simple Vue micro-apps. That seemed like a good place to start, but I quickly realized that things would get messy on the PHP side of WordPress.

What I really needed were reusable components

PHP → JavaScript

I love the static-first approach of Jamstack tools, like Nuxt, and was looking to do something similar here — send the full content from the server, and progressively enhance on the client side.

But PHP doesn’t have a built-in way to work with components. It does, however, support require-ing files inside other files 2. WordPress has an abstraction of require called get_template_part, that runs relative to the theme folder and is easier to work with. Dividing code into template parts is about the closest thing to components that WordPress provides 3.

Vue, on the other hand, is all about components — but it can only do its thing after the page has loaded and JavaScript is running.

The secret to this marriage of paradigms turns out to be the lesser-known Vue directive inline-template. Its great and wonderful powers allow us to define a Vue component using the markup we already have. It’s the perfect middle ground between getting static HTML from the server, and mounting dynamic DOM elements in the client.

First, the browser gets the HTML, then Vue makes it do stuff. Since the markup is built by WordPress, rather than by Vue in the browser, components can easily use any information that site administrators can edit. And, as opposed to .vue files (which are great for building more app-y things), we can keep the same separation of concerns we use for the whole site — structure and content in PHP, style in CSS, and functionality in JavaScript.

To show how this all fits together, we’re going to build a few features for a recipe blog. First, we’ll add a way for users to rate recipes. Then we’ll build a feedback form based on that rating. Finally, we’ll allow users to filter recipes, based on tags and rating.

We’ll build a few components that share state and live on the same page. To get them to play nicely together — and to make it easy to add additional components in the future — we’ll make the whole page our Vue app, and register components inside it.

Each component will live in its own PHP file and be included in the theme using get_template_part.

Laying the groundwork

There are a few special considerations to take into account when applying Vue to existing pages. The first is that Vue doesn’t want you loading scripts inside it — it will send ominous errors to the console if you do. The easiest way to avoid this is to add a wrapper element around the content for every page, then load scripts outside of it (which is already a common pattern for all kinds of reasons). Something like this:

<?php /* header.php */ ?>  <body <?php body_class(); ?>> <div id="site-wrapper">
<?php /* footer.php */ ?>   </div> <!-- #site-wrapper --> <?php wp_footer(); ?>

The second consideration is that Vue has to be called at the end of body element so that it will load after the rest of the DOM is available to parse. We’ll pass true as the fifth argument  (in_footer) for the wp_enqueue_script  function. Also, to make sure Vue is loaded first, we’ll register it as a dependency of the main script.

<?php // functions.php  add_action( 'wp_enqueue_scripts', function() {   wp_enqueue_script('vue', get_template_directory_uri() . '/assets/js/lib/vue.js', null, null, true); // change to vue.min.js for production   wp_enqueue_script('main', get_template_directory_uri() . '/assets/js/main.js', 'vue', null, true);

Finally, in the main script, we’ll initialize Vue on the site-wrapper element.

// main.js  new Vue({   el: document.getElementById('site-wrapper') })

The star rating component

Our single post template currently looks like this:

<?php /* single-post.php */ ?>  <article class="recipe">   <?php /* ... post content */ ?>    <!-- star rating component goes here --> </article>

We’ll register the star rating component and add some logic to manage it:

// main.js  Vue.component('star-rating', {   data () {     return {       rating: 0     }   },   methods: {     rate (i) { this.rating = i }   },   watch: {     rating (val) {       // prevent rating from going out of bounds by checking it to on every change       if (val < 0)          this.rating = 0       else if (val > 5)          this.rating = 5        // ... some logic to save to localStorage or somewhere else     }   } })  // make sure to initialize Vue after registering all components new Vue({   el: document.getElementById('site-wrapper') })

We’ll write the component template in a separate PHP file. The component will comprise six buttons (one for unrated, 5 with stars). Each button will contain an SVG with either a black or transparent fill.

<?php /* components/star-rating.php */ ?>  <star-rating inline-template>   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in 5) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </star-rating>

As a rule of thumb, I like to give a component’s top element a class name that is identical to that of the component itself. This makes it easy to reason between markup and CSS (e.g. <star-rating> can be thought of as .star-rating).

And now we’ll include it in our page template.

<?php /* single-post.php */ ?>  <article class="recipe">   <?php /* post content */ ?>    <?php get_template_part('components/star-rating'); ?> </article>

All the HTML inside the template is valid and understood by the browser, except for <star-rating>. We can go the extra mile to fix that by using Vue’s is directive:

<div is="star-rating" inline-template>...</div>

Now let’s say that the maximum rating isn’t necessarily 5, but is controllable by the website’s editor using Advanced Custom Fields, a popular WordPress plugin that adds custom fields for pages, posts and other WordPress content. All we need to do is inject that value as a prop of the component that we’ll call maxRating:

<?php // components/star-rating.php  // max_rating is the name of the ACF field $  max_rating = get_field('max_rating'); ?> <div is="star-rating" inline-template :max-rating="<?= $  max_rating ?>">   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </div>

And in our script, let’s register the prop and replace the magic number 5:

// main.js  Vue.component('star-rating', {   props: {     maxRating: {       type: Number,       default: 5 // highlight     }   },   data () {     return {       rating: 0     }   },   methods: {     rate (i) { this.rating = i }   },   watch: {     rating (val) {       // prevent rating from going out of bounds by checking it to on every change       if (val < 0)          this.rating = 0       else if (val > maxRating)          this.rating = maxRating        // ... some logic to save to localStorage or somewhere else     }   } })

In order to save the rating of the specific recipe, we’ll need to pass in the ID of the post. Again, same idea:

<?php // components/star-rating.php  $  max_rating = get_field('max_rating'); $  recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $  max_rating ?>" recipe-id="<?= $  recipe_id ?>">   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </div>
// main.js  Vue.component('star-rating', {   props: {     maxRating: {        // Same as before     },     recipeId: {       type: String,       required: true     }   },   // ...   watch: {     rating (val) {       // Same as before        // on every change, save to some storage       // e.g. localStorage or posting to a WP comments endpoint       someKindOfStorageDefinedElsewhere.save(this.recipeId, this.rating)     }   },   mounted () {     this.rating = someKindOfStorageDefinedElsewhere.load(this.recipeId)       } })

Now we can include the same component file in the archive page (a loop of posts), without any additional setup:

<?php // archive.php  if (have_posts()): while ( have_posts()): the_post(); ?> <article class="recipe">   <?php // Excerpt, featured image, etc. then:   get_template_part('components/star-rating'); ?> </article> <?php endwhile; endif; ?>

The feedback form

The moment a user rates a recipe is a great opportunity to ask for more feedback, so let’s add a little form that appears right after the rating is set.

// main.js  Vue.component('feedback-form', {   props: {     recipeId: {       type: String,       required: true     },     show: { type: Boolean, default: false }   },   data () {     return {       name: '',       subject: ''       // ... other form fields     }   } })
<?php // components/feedback-form.php  $  recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $  recipe_id ?>" v-if="showForm(recipe-id)">   <form class="recipe-feedback-form" id="feedback-form-<?= $  recipe_id ?>">     <input type="text" :id="first-name-<?= $  recipe_id ?>" v-model="name">     <label for="first-name-<?= $  recipe_id ?>">Your name</label>     <?php /* ... */ ?>   </form> </div>

Notice that we’re appending a unique string (in this case, recipe-id) to each form element’s ID. This is to make sure they all have unique IDs, even if there are multiple copies of the form on the page.

So, where do we want this form to live? It needs to know the recipe’s rating so it knows it needs to open. We’re just building good ol’ components, so let’s use composition to place the form inside the <star-rating>:

<?php // components/star-rating.php  $  max_rating = get_field('max_rating'); $  recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $  max_rating ?>" recipe-id="<?= $  recipe_id ?>">   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>     <?php get_template_part('components/feedback-form'); ?>   </div> </div>

If at this point you’re thinking, “We really should be composing both components into a single parent component that handles the rating state,” then please give yourself 10 points and wait patiently.

A small progressive enhancement we can add to make the form usable without JavaScript, is to give it the traditional PHP action and then override it in Vue. We’ll use @submit.prevent to prevent the original action, then run a submit method to send the form data in JavaScript.

<?php // components/feedback-form.php  $  recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $  recipe_id ?>">   <form action="path/to/feedback-form-handler.php"        @submit.prevent="submit"       class="recipe-feedback-form"        id="feedback-form-<?= $  recipe_id ?>">     <input type="text" :id="first-name-<?= $  recipe_id ?>" v-model="name">     <label for="first-name-<?= $  recipe_id ?>">Your name</label>    <!-- ... -->   </form> </div>

Then, assuming we want to use fetch, our submit method can be something like this:

// main.js  Vue.component('feedback-form', {   // Same as before    methods: {     submit () {       const form = this.$  el.querySelector('form')       const URL = form.action       const formData = new FormData(form)       fetch(URL, {method: 'POST', body: formData})         .then(result => { ... })         .catch(error => { ... })     }   } })

OK, so what do we want to do in .then and .catch? Let’s add a component that will show real-time feedback for the form’s submit status. First let’s add the state to track sending, success, and failure, and a computed property telling us if we’re pending results.

// main.js  Vue.component('feedback-form', {   // Same as before    data () {     return {       name: '',       subject: ''       // ... other form fields       sent: false,       success: false, ​​      error: null     }   },   methods: {     submit () {       const form = this.$  el.querySelector('form')       const URL = form.action       const formData = new FormData(form)       fetch(URL, {method: 'POST', body: formData})         .then(result => {            this.success = true          })         .catch(error => {            this.error = error          })       this.sent = true     }   } })

To add the markup for each message type (success, failure, pending), we could make another component like the others we’ve built so far. But since these messages are meaningless when the server renders the page, we’re better off rendering them only when necessary. To do this we’re going to place our markup in a native HTML <template> tag, which doesn’t render anything in the browser. Then we’ll reference it by id as our component’s template.

<?php /* components/form-status.php */ ?>  <template id="form-status-component" v-if="false">   <div class="form-message-wrapper">     <div class="pending-message" v-if="pending">       <img src="<?= get_template_directory_uri() ?>/spinner.gif">       <p>Patience, young one.</p>     </div>     <div class="success-message" v-else-if="success">       <img src="<?= get_template_directory_uri() ?>/beer.gif">       <p>Huzzah!</p>     </div>     <div class="success-message" v-else-if="error">       <img src="<?= get_template_directory_uri() ?>/broken.gif">       <p>Ooh, boy. It would appear that: {{ error.text }}</p>     </div>   </div </template>

Why add v-if="false" at the top, you ask? It’s a tricky little thing. Once Vue picks up the HTML <template>, it will immediately think of it as a Vue <template> and render it. Unless, you guessed it, we tell Vue not to render it. A bit of a hack, but there you have it.

Since we only need this markup once on the page, we’ll include the PHP component in the footer.

<?php /* footer.php */ ?>  </div> <!-- #site-wrapper --> <?php get_template_part('components/form-status'); ?> <?php wp_footer(); ?>

Now we’ll register the component with Vue…

// main.js  Vue.component('form-status', {   template: '#form-status-component'   props: {     pending: { type: Boolean, required: true },     success: { type: Boolean, required: true },     error: { type: [Object, null], required: true },   } })

…and call it inside our form component:

<?php // components/feedback-form.php  $  recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $  recipe_id ?>">   <form action="path/to/feedback-form-handler.php"          @submit.prevent="submit"         class="recipe-feedback-form"          id="feedback-form-<?= $  recipe_id ?>">     <input type="text" :id="first-name-<?= $  recipe_id ?>" v-model="name">     <label for="first-name-<?= $  recipe_id ?>">Your name</label>     <?php // ... ?>   </form>   <form-status v-if="sent" :pending="pending" :success="success" :error="error" /> </div>

Since we registered <form-status> using Vue.component, it’s available globally, without specifically including it in the parent’s components: { }.

Filtering recipes

Now that users can personalize some bits of their experience on our blog, we can add all kinds of useful functionality. Specifically, let’s allow users to set a minimum rating they want to see, using an input at the top of the page.
The first thing we need is some global state to track the minimum rating set by the user. Since we started off by initializing a Vue app on the whole page, global state will just be data on the Vue instance:

// main.js // Same as before  new Vue({   el: document.getElementById('site-wrapper'),   data: {     minimumRating: 0   } })

And where can we put the controls to change this? Since the whole page is the app, the answer is almost anywhere. For instance, at the top of the archive page:

<?php /* archive.php */ ?>  <label for="minimum-rating-input">Only show me recipes I've rated at or above:</label> <input type="number" id="minimum-rating-input" v-model="minimumRating">  <?php if (have_posts()): while ( have_posts()): the_post(); ?> <article class="recipe">   <?php /* Post excerpt, featured image, etc. */ ?>    <?php get_template_part('components/star-rating'); ?> </article> <?php endwhile; endif; ?>

As long as it’s inside our site-wrapper and not inside another component, it’ll just work. If we want, we could also build a filtering component that would change the global state. And if we wanted to get all fancy, we could even add Vuex to the mix (since Vuex can’t persist state between pages by default, we could add something like vuex-persist to use localStorage).

So, now we need to hide or show a recipe based on the filter. To do this, we’ll need to wrap the recipe content in its own component, with a v-show directive. It’s probably best to use the same component for both the single page and the archive page. Unfortunately, neither require nor get_template_part can pass parameters into the called file — but we can use global variables:

<?php /* archive.php */ ?>  <label for="minimum-rating-input">Only show me recipes I've rated at or above:</label> <input type="number" id="minimum-rating-input" v-model="minimumRating">  <?php  $  is_archive_item = true; if (have_posts()): while ( have_posts()): the_post();   get_template_part('components/recipe-content'); endwhile; endif; ?>

We can then use $ is_archive_item as a global variable inside the PHP component file to check if it is set and true. Since we won’t need to hide the content on the single post page, we’ll conditionally add the v-show directive.

<?php  // components/recipe-content.php  global $  is_archive_item; ?> <div is="recipe-content">   <article class="recipe"      <?php if ($  is_archive_item): ?>        v-show="show"     <?php endif; ?>   >     <?php     if ($  is_archive_item):       the_excerpt();     else       the_content();     endif;          get_template_part('components/star-rating');     ?>   </article> </div>

In this specific example, we could have also tested with  is_archive() inside the component, but in most cases we’ll need to set explicit props.

We’ll need to move the rating state and logic up into the <recipe-content> component so it can know if it needs to hide itself. Inside <star-rating>, we’ll make a custom v-model by replacing rating with value, and this.rating = i with  $ emit('input', i) as well . So our component registration will now look like this:

// main.js  Vue.component('recipe-content', {   data () {     rating: 0   },   watch: {     rating (val) {       // ...     }   },   mounted () {     this.rating = someKindOfStorageDefinedElsewhere.load(this.recipeId)       } })  Vue.component('star-rating', {   props: {     maxRating: { /* ... */ },     recipeId: { /* ... */ },     value: { type: Number, required: true }   },   methods: {     rate (i) { this.$  emit('input', i) }   }, })

We’ll add v-model in star-rating.php and change rating to value. In addition, we can now move the <feedback-form> up into <recipe-content>:

<?php // components/star-rating.php  $  max_rating = get_field('max_rating'); $  recipe_id = get_the_ID(); ?> <div is="star-rating"    inline-template    :max-rating="<?= $   max_rating ?>"    recipe-id="<?= $  recipe_id ?>"    v-model="value" >   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="value === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="value >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </div>
<?php // components/recipe-content.php  global $  is_archive_item; ?> <div is="recipe-content">   <article class="recipe"      <?php if ($  is_archive_item): ?>        v-show="show"     <?php endif; ?>   >          <?php     if ($  is_archive_item):       the_excerpt();     else       the_content();     endif;          get_template_part('components/star-rating');     get_template_part('components/feedback-form');     ?>   </article> </div>

Now everything is set up so the initial render shows all recipes, and then the user can filter them based on their rating. Moving forward, we could add all kinds of parameters to filter content. And it doesn’t have to be based on user input — we can allow filtering based on the content itself (e.g. number of ingredients or cooking time) by passing the data from PHP to Vue.

Conclusion

Well, that was a bit of a long ride, but look at what we’ve built: independent, composable, maintainable, interactive, progressively enhanced components in our WordPress theme. We brought together the best of all worlds!

I’ve been using this approach in production for a while now, and I love the way it allows me to reason about the different parts of my themes. I hope I’ve inspired you to try it out too.


  1. Of course, two days before launch, the client’s legal department decided they don’t want to collect all that info. Currently the live form is but a shadow of its development self.
  2. Fun fact: Rasmus Lerdorf said that his original intent was for PHP to be templating only, with all business logic handled in C. Let that sink in for a moment. Then clear an hour from your schedule and watch the whole talk.
  3. There are third-party WordPress templating engines that can compile down to optimized PHP. Twig, for example, comes to mind. We’re trying to go the reverse route and send vanilla PHP to be handled by JavaScript.

The post How to Build Vue Components in a WordPress Theme appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Variations on Theme: Reinventing Type on the Web

If anyone knows anything about me, it’s usually one of two things: that I have two outrageously fluffy dogs, or that I like fonts and typography. Like, really really like them. So while I am super excited about how well Tristan is doing with his hydrotherapy —we’re walking 50% further than he was able just a couple months ago, without having to take breaks in the middle—I’m even more riled up about variable fonts.

Image of Tristan and Tillie explaining variable fonts

I know, you’re probably all really shocked.

I’ve been pretty single-minded about them since their introduction three years ago, and think they are going to be a massively Big Thing. But it just hasn’t gotten to that tipping point—yet. But a few things are coming together that make me think this just might be the year. Let me step back a bit and explain.

See the Pen
Variable font, outlined
by Jason Pamental (@jpamental)
on CodePen.

Plex Sans Variable, with outlines showing the range of weights and widths possible with a single file

The future is variable

Variable fonts are an evolution of the OpenType font specification that allows a single file to contain all of the variations of width, weight, slant, italics, and virtually any other permutation of a typeface the type designer want’s to expose—all in a single file. That one file is highly efficient, so it’s far smaller than all of the individual files. For the web, that means we can in many cases save considerable data download, reduce the number of HTTP requests, and in general vastly increase the design flexibility at our disposal. A pretty big change from those plain ‘ol static web fonts we’ve been using for the past 10 years. There’s loads of good stuff out there about them (I’ve written a few here, here, here, and here), but you can also browse the tag right here on CSS Tricks.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
66 53 62 No 17 11

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
11.0-11.2 No No 76 78 68
Variable fonts are viewable by 87% of devices on the web according to caniuse.com.

Now that browser support is pretty complete (for the most part everything but IE11 and a couple of Android browsers like Baidu and UC), availability of variable fonts is improving (check out v-fonts.com), and Google is launching support for them via their new API—it feels like this might finally be the breakout year for them.

What excites me the most about that is what we’ll see people create. I’m sure at first it will be lots of “rip and replace”, similar to what we saw on the Nielson/Norman group site with their inclusion of Source Sans Variable on their site last year, or what Google has been testing with Oswald Variable on sites 148 million times a day for the past several months. Basically just using that instead of a few static instances to reap the benefits of faster page loads and less code.

Which is great, but only just a beginning.

What I’m looking forward to seeing is people embracing the full range of what these fonts can do. Going form ultra-light to super-heavy, super-condensed to extra-wide. Whatever the fonts support is there for us to explore. I’m hoping to see designers dive in and explore the power of great typography. Type that speaks more and varied volumes to help guide us around our sites and apps. Type set in motion that can lead us from here to there.

See the Pen
Layout variations, part deux
by Jason Pamental (@jpamental)
on CodePen.

This is one of the explorations I did for my newsletter and talks, playing with notions of legibility and the tension between reading fast and slow.

Will some of it be awful?

Probably. But so were a lot of early responsive websites. And lots of sites with static web fonts when they first launched. Or Flash-based sites. All of these have been evolutions of what we can design and make on the web. It’s up to us to do it well instead of poorly. I’m excited to learn together.

After all, if type is the voice of our words—with variable fonts—that voice has become a chorus.

The post Variations on Theme: Reinventing Type on the Web appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]