Sorry! Internet Explorer is not supported on this site. Please view on Chrome, Firefox, or Edge.

Having fun at Zao is one of our values. We’ve put limited animated flourishes throughout our site to communicate our love of levity. We also recognize that onscreen movement is not fun or possible for everyone. We've turned off all our animations for you per your browser's request to limit motion. That said, we don't want you to miss out on the party.

Here's a funny joke to enjoy!

Do you know why you never see elephants hiding up in trees?

Because they’re really good at it.

Crafting a Starter Gutenberg Theme | Tips and Findings

tl;dr

In trying to create a Gutenberg-friendly starter theme, I researched the two major approaches theme developers use to handle regular, wide and full width alignments in Gutenberg. I’ve deemed these the No Container Approach and the Max-width Container Approach.

The No Container Approach has issues with tables and lists and requires funky specificity that overshadows its initially promising approach. The Max-width Container Approach appears to be the most widely adopted approach but it seems to ignore wide width in the editor and has some front end wide width issues depending on the block. You need to do extensive testing to be sure you’re safe.

I ended up creating and using the CSS Grid Approach. This works great on the front and backend. It struggles in IE11, but you can add some simple JavaScript to make it behave. According to Death to IE11, mainstream support will end in about two months at the time of this article, so you might be golden with no IE11 support. In fact, for our own new site, we’re eliminating IE support entirely, opting instead to alert IE users that they should use a different browser.

Check out these gists to see the Sass and IE JS code we’re using in production on several projects.

UPDATE: I have run into Gutenberg changing their editor class names and HTML structure at least two to three times since I wrote this post. The approach I’ve outlined in the CSS Grid section still works, but you might need to update your editor class names periodically. This won’t impact the frontend, but can impact your alignment in the editor.

Getting Started

Recently, I built a Gutenberg Starter Theme to use for developing custom themes in the future. I took the Twentynineteen theme and stripped out most of the Sass to allow a fresh slate when developing.

I spent a lot of time researching the different approaches towards alignment with Gutenberg. It quickly became clear to me that alignment is still evolving and no one has figured out the golden bug-free approach. The same person might write several posts with slight variations of their code in each article.

In my research, I came across two approaches over and over again. The first option is the No Container Approach. This uses the > * selector to target all immediate children of a container and set the max-width on the child rather than the container.

The second approach was developed to work in partnership with existing themes that utilize wrapper containers for content. It is the Max-width Container approach.

My hope was to find the best approach with the following parameters in mind:

  • Simple and logical
  • Responsive
  • Compatible between frontend and editor

There are many great tutorials and documentation on how to make your theme be Gutenberg ready. That is not the scope of this article. Rather I am deep diving into things that surfaced for me as I played around with both approaches. There are “gotchas” about both approaches. My hope is that this article will help you make the most educated decision on what approach to take for your situation.

No Container Approach

Basics

Most theme development up to now has utilized wrappers or containers to manage the max-width the content should be. Now that we’re working with wide and full width blocks in Gutenberg, having a container is problematic.

The no container approach utilizes the > * selector to apply the max widths to all immediate children elements except for children elements that should be wide or full width.

.entry-content > *:not( .alignwide ):not( .alignfull ) {
	margin-left: auto;
	margin-right: auto;
	max-width: 900px;
	padding-right: 1em;
	padding-left: 1em;
}

In the code above we set the left and right margins to auto in order to center the content on the page. The max-width is self explanatory. The left and right padding are supposed to create a gutter between the content and the browser window. This specifically engages when we haven’t hit our max-width, and therefore, don’t have any margin. The padding accomplishes this goal in most cases.

Issues

I thought the No Container Approach was going to be my go-to method for building themes from scratch because of the simplicity and minimal code. However, did you catch my “most cases” caveat above? I discovered this rule doesn’t work as desired for all elements.

Ordered and unordered lists generally have left padding to account for the bullets/numbers. Having a global right and left padding set for all the children elements overrides their padding, and makes the content out of alignment.

The bullets extend past the Categories heading alignment

Table elements have a display: table. Padding is not respected on the outside of the table.

The table extends beyond the Table heading alignment

This might not seem like that big of a deal until you view things on mobile device.

Yuck! We definitely don’t want the content sitting all the way against the edge of the browser.

Troubleshooting (and its issues)

Lists

To fix the alignment issue for lists we would need to account for the numbers/bullets with something like this:

.entry-content > ul:not(.alignwide):not(.alignfull),
.entry-content > ol:not(.alignwide):not(.alignfull) {
	padding-left: 2rem;
}

Keep in mind you have to include the :not(.alignwide):not(.alignfull) to overcome the specificity of our initial rule.

This solution is problematic for a number of reasons. First we’re assuming anytime we have an ol or ul as a direct descendent in the content we’re going to display the number or bullet, which may not always be the case.

If any plugins, widgets, etc utilize ul lists for things like grids or displaying repeating patterns we’re going to override the padding for those elements with our powerful specificity.

The code I’ve provided above is vanilla CSS. In reality, most of us use a preprocessor of some sort to utilize variables among other things. The padding-left rule would get more complicated based on the value of our variables.

Tables

I haven’t been able to come up with a solution for the table because you cannot have any sort of combination with auto margin.

// This code won't work
.entry-content > table:not(.alignwide):not(.alignfull) {
      margin-left: calc(auto + 1em);
      margin-right: calc(auto + 1em);
}

Summary

Personally, I’m not comfortable with all the unknowns and issues of the No Container Approach. The biggest benefit of taking this approach was the simple and minimal code. But its issues create the need for more complex code to workaround its limits. I also don’t know what other plugins or elements with custom padding might be used by the end user, which could introduce more inconsistencies. Since I don’t have a solution for tables, I don’t think this approach is a viable option for a starter theme.

I never ended up testing this approach in the editor because I ruled it out before I got there, but I actually imagine that this approach would work well in the backend. If not better than the Max-width Container Approach that we’re about to explore.

Max-width Container Approach

The Max-width container approach works well with the constraints that a lot of themes are bound by. Many themes, before Gutenberg, utilize containers that are given a max-width so the main content does not stretch the width of incredibly wide browsers.

Gutenberg has blocks that are full and wide width that intend to go beyond the max-width of the container. Therefore, a solution needs to pull the content outside of the parent container. The most common approach I found was to use negative margins.

Unsurprisingly, the code for this approach is still evolving. In fact, by the time I got around to writing this article many bloggists removed some of their older renditions involving percentages, presumably because of the inconsistencies that surface with % over vw.

Full Width Alignment

There are several renditions of similar code that generally accomplish the same thing with slight variations. For alignfull elements, below are the most common snippets I ran across. I won’t spend much time explaining them as the tutorials they are affiliated with do a good job of that, but I will walk through some of the issues that surface with each of the approaches.

.alignfull {
	margin-left: calc( -100vw / 2 + 100% / 2 );
	margin-right: calc( -100vw / 2 + 100% / 2 );
	max-width: 100vw;
}
.alignfull {
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
	max-width: 100vw;
	width: 100vw;
}

At first glance, these snippets are very similar. In fact, excluding the width: 100vw in the second snippet they are doing the same code but factored differently. They make the margins negative in order to pull the element out of the parent element.

Issue with not setting the width

The difference between those two snippets turns out to be very important. Without width: 100vw, some elements won’t go full width, while others will, giving you a false confidence that the code is working when it is fragile.

In pullquotes and video embeds, the code seems to be working as expected

However, when full width is set to Gutenberg’s native cover, the width does not go full width and has unexpected alignment issues.

Simply adding width: 100vw in addition to max-width: 100vw forces all elements to hit their expected widths.

To reiterate, the code below is necessary to make this full width approach work consistently.

.alignfull {
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
	max-width: 100vw;
	width: 100vw;
}

Issues with sidebars

The above code works great on the frontend for full width pages but is problematic for pages with sidebars because the content will be pulled behind the sidebars.

This is an issue on the backend given that the admin has the admin sidebar and settings sidebar.

Backend layout
Simplified backend view with sidebars

Here are two examples of text being pulled behind the admin and/or setting sidebars with the negative margin approach shown above.

A major goal of Gutenberg is to mirror the frontend in the editor. This clipped content is a problem.

Wide Width Alignment

The most common approaches I found to setting wide width was the following:

.alignwide {
	margin-left: calc(25% - 25vw);
	margin-right: calc(25% - 25vw);
	width: auto;
	max-width: 100vw;
}

//Doesn't center
.alignwide {
	margin-left: calc(25% - 25vw);
	margin-right: calc(25% - 25vw);
	width: 100vw;
	max-width: 100vw;
}

You can tell from my comments that setting width: 100vw causes alignment issues. And the screenshots below demonstrate the issue.

Therefore, unlike .alignfull we need to be sure to set the width: auto for .alignwide to not have unexpected centering issues.

Editor issues

I have yet to find a starter theme that handles .alignwide. Instead they all seem to act as though it doesn’t exist in the editor.

Summary

This is the approach that most of the developers I follow take. It is a pretty good solution and works in most scenarios unless you have sidebars, then it can be problematic.

CSS Grid Approach

After all my research I was planning on going the max-width container approach, even with its issues. However, right as I was about to implement it, I wished that I could use CSS Grid. It seems the perfect solution for these different widths. I decided to give it a shot and was very happy with the outcome. The only caveat: if you must support IE 11, this is not a working solution.

Setting up CSS Grid to work for Gutenberg blocks is a very simple process. First we need to make the parent container a grid and create the different columns that we want regular, width, and full width elements to span.

With tools like autoprefixer and Sass this is a breeze.

.entry-content {
    display: grid;
    grid-template-columns: 2em 1fr 1fr 5fr 1fr 1fr 2em;
}

I actually have the 2em value set to a variable called $size_site-gutter which is set in my variables file, this is then minimum gutter I want for all my content on mobile devices.

Seeing the grid in action can help us make visual sense of the code above. If you look at the image below you can see 7 columns. The farthest left and right columns are the gutter column so that when we go mobile, my regular content will align against the gutter, but the full width will still span the entire screen.

The second column on the left, and second to last column create the buffer between the full width and wide width content alignments. I have it set to 1fr. In case you’re unfamiliar, an fr is a fractional unit. I could have used a percentage, em, or pixel but frs are a very common unit in CSS Grid.

The third and fifth columns follow the same pattern as the second and sixth columns to create the space between the regular and wide width content.

The fourth, or middle column, is where most of the content will live. It is set to 5fr which means it gets the lion’s share of the real estate. If I wanted a true max-width to support the most optimal reading length of 50-60 characters per I could easily set this column width to be 560px or 35em depending on my font-family and size. My code would look like this:

.entry-content {
    display: grid;
    grid-template-columns: 2em 1fr 1fr 560px 1fr 1fr 2em;
}

Next we need to tell the children elements where to go, or we will wind up with a huge unintended mess on the page.

The painful result of not telling the children elements where to go

To get the layout to work, we tell the children elements based on whether they are regular, wide, or full width how many columns to span and we use the powerful > * selector to be sure we are targeting all direct children.

.entry-content > * {
   grid-column: 2 / -2;
  
   @include media(tablet) {
       grid-column: 4 / -4;
   }
}

The above code is written with Sass and it says to make all the direct children of the .entry-content (which is my parent div) span everything but the first and last column (the gutters) when on a mobile device, and when the screen is larger than my tablet media query to span only column four or the middle column. If you need to know more about grid-column, CSS Tricks has an in-depth guide for you.

That code makes everything line up vertically on the page in the middle column, including the full and wide width content.

Now we must tell our full and wide width elements what they need to span.

.entry-content > .alignfull {
    grid-column: 1 / -1;
}

.entry-content > .alignwide {
    grid-column: 2 / -2;

    @include media(tablet) {
        grid-column: 3 / -3;
    }
}

The alignfull code tells anything with that class to span all the columns in the grid. I could just as easily set my alignwide to be full width on mobile if I wanted with the same code as alignfull, and then have it go to wide width above tablet.

Instead I have alignwide operating like regular width on mobile where we have a little gutter, and then span columns three, four, and five on larger screens.

Here are all the alignments on mobile

Just like that, we have a beautiful solution for Gutenberg alignments.

Look how easy it is to change the widths.

CSS Grid in the Editor

Another great thing about the CSS Grid solution is that it translates really well to the editor in the backend. A lot of themes I checked out didn’t even have wide width treatment on the backend.

I ended up writing mixins for my regular, wide, and full width alignments and then reused those mixins on my front and backend files since the editor has different HTML than the frontend.

@mixin regularContentMaxWidth() {
    grid-column: 2 / -2;

    @include media(tablet) {
        grid-column: 4 / -4;
    }
}

@mixin wideContentMaxWidth() {
    grid-column: 2 / -2;

}

@mixin fullContentMaxWidth() {
    grid-column: 1 / -1;
}

To have the styles show up in the editor, I used the following code:

.editor-block-list__layout {
    display: grid;
    grid-template-columns: $size__site-gutter 1fr 1fr 5fr 1fr 1fr $size__site-gutter;
    padding-left: 0;
    padding-right: 0;

    & > * {
        @include regularContentMaxWidth();
    }

    & > .wp-block[data-align="wide"] {
        @include wideContentMaxWidth();
    }

    & > .wp-block[data-align="full"] {
        @include fullContentMaxWidth();
    }
}

// Fixes off centered look on really wide screens because
// Gutenberg sets max-widths on wp-block
.wp-block {
    &:not( [data-align=full] ) {
        @include media(wide) {
            max-width: 100%;
        }
    }
	
    &[data-align=wide] {
        @include media(wide) {
            max-width: 100%;
        }
    }
}

The top code is everything we walked through for the frontend. The only difference is it focuses on classes that are in the editor.

Gutenberg gives .wp-block a max-width of 610px which works fine for medium screens, but if your browser is really wide (over 1400px) everything looks heavy on the left because .wp-block won’t span the entire column. Adding the max-width lets the element take up the entire column which repairs the centering.

CSS Grid issues in Internet Explorer 11

According to this CSS Tricks article, IE 11 should support the CSS Grid code if I use the right prefixes, but it clearly doesn’t (see below).

Everything stacks on top of each other in IE11 instead of using implicit rows

My assumption is that because we are telling the children elements what columns to span, it is overriding any implicit rows IE was able to create if we were using CSS Grid in a more traditional manner.

There is a fairly simple solution for this IE issue, but it requires a little JS. Add the following code in a JS file and you’ll have the layout supported in ie11.

Essentially, injectIE11Grid() counts all the direct descents of the grid parent element (.entry-content) and then for each direct descendant we use the nth-child selector to assign that child the same row number. For example:

.entry-content > *:nth-child(3) {
    -ms-grid-row: 3
}

The third direct child of .entry-content will be placed on the third row. We only want this to apply to IE11. You can use if( Modernizr.cssgridlegacy ) if you already have a Modernizr dependency or if( typeof CSS === 'undefined' ) does the same trick in our circumstances.

Using the typeof CSS works because while it is undefined in IE11, when you check it in Chrome and Firefox, you get a function. When you check it in Edge or Safari, you get an object.

Sass Tips and an Update

I use variables as much as possible. As of the time of writing this article the markup in the editor was quite different than the front end so there is still some maintaining of both the backend and frontend code. This adds further support for using variables.

Now that I’ve used CSS grid on several Gutenberg projects, I’ve updated my approach a little bit from what I detailed above. The principle still works, but I’ve included some code below that I have out in the wild that has been working well for me.

Variables Partial

This is where I set all my site variables. I found it is helpful to use columns instead of one larger value (i.e. 5fr) so that I can use this grid not only on the main content layout, but apply it to specific design elements that I need to set placement based on a grid.

Mixins Partial

Most designs I work on do have a max-width, hence the addition of it in the gridSection mixin. I’ve found that I pretty much always want wide width to stretch the entire width except for the bumper. You could add another media query at a larger breakpoint if you don’t want its expanse to be as wide (i.e. grid-column: 3 / -3; at desktop width).

Theme Partials

Editor Sass File

Bonus: Media Query Mixin

Most of the code above uses this mixin, so I figured I’d include so you can hit the ground running.

Conclusion

We’ve spent the last several years now thinking through the impact of Gutenberg on the landscape of WordPress, and the web in general. This starter theme is the outcome of years of work now with Gutenberg. We’ve been fortunate to partner with some incredible clients to push the boundaries of what is possible in WordPress, and we’re just getting started 😍. We’ll keep sharing everything we learn along the way. Want to partner with us on your next ambitious project? Get in touch.

Join the Conversation

2 Comments

  1. It looks like the starter theme was pulled off of GitHub. Curious to take a deeper look, any chance that you’ll be bringing it back?

Leave a comment

Your email address will not be published. Required fields are marked *