Application of CSS Grid in WordPress Theme Layout


Inspired by recent post (presentation) by Morten and several articles on CSS Grid Layout by Rachel Andrew, I decided to apply CSS Grid Layout in my recent flex box based WordPress theme.

Background

When I started learning web design as a spare-time hobby in early 000’s, back then table was commonly used techniques to lay out content. After practicing I became comfortable using tables & used in my personal project sites. Then my web design hobby remained in back burner for some time because of other priorities.

Around 2010-2011, I was introduced to WordPress. While learning WordPress theme development, layout has been one of my most difficult components to learn. Although there were layout frame works like bootstrap, genesis framework, page builders but the never impressed me. Flexibility to customize my page layout as desired was my highest priority. Thus, I continued my layout & styling struggle by learning / using CSS float, position etc but never got comfortable with it.

CSS Grid

2017 was year of CSS Grid layout and it dominated web design discussions. I started paying more attention to it in 2017 mid-summer. With the support of grid layout by major browsers, it was one of the most talked about web design technique in 2017. Like most web developers, I too have been following CSS grid layout with daily appearing articles, posts, codePen etc that are flooding google search result pages on CSS grid.

During the WorldCamp Europe 2017 in Paris, Morten Rand-Henriksen made a presentation on CSS Grid Changes Everything (About Web Layouts). Based on this talk, Building Production-Ready CSS Grid Layouts Today was published in Smashing magazine. Incidentally, the example theme Kuhn Morten used in both presentations was the same theme that my latest theme was based upon.

Inspired by these developments, I thought to give it a try to apply CSS grid in my latest themes, and that is how the Khesara, the theme used in this site was born.

WordPress Themes with CSS Grid Layout

After Morten’s WPCamp Europe 2017 talk and his publication, I was expecting that many web developers would adopt grid layout in their themes. Until I was preparing this post, I found only a handful of  (KuhnCreado) WordPress themes with CSS Grid. Some personal sites (meyerweb, …) appear to be adopting to CSS grid layout. One of the reasons for less adoption of CSS Grid in WordPress themes could due Gutenberg editor effect too.

Why this theme?

I am a big fan of post format and use extensively in my personal site. While learning to design post format with different full-width background, I had to spend many hours trouble shooting to make it work properly for all screen sizes. Breaking containers for CSS Styling is not easy in WordPress themes because various containers are used to hold different parts of the contents and then CSS float property is used to position desired contained in a page layout. Thus to break those containers for full-bleed is not simple.

Single Column Layout

One of the goals of my theme development was to create a single column design layout with centered content in wider viewport. In the underscores ( _S) based theme framework, this can be achieved by following simple rule and which works fine for most layout design if no further customization is desired.

site-main {
    max-width: 55em;
    margin: 0 auto;
   }

In the above code example, .site-main class holds post & page contents under the article section. By defining max-with property of .site-main class container to 55em, width of the content display could be defined. Now, by setting the margin properties to zero to top and bottom & auto to left and right, the content would be centered in larger than 900px wider viewport. This is commonly used technique to center content in larger viewport.

In my theme, I wanted to go one step further and have both the standard post and as well as post format content types (eg. asides, status, quotes, etc) with the following features:

  • Single column layout with centered content in larger viewport.
  • Full-bleed featured images
  • Wider page header than the main content
  • Post format posts: different than regular posts & pages with full-bleed background
  • Widgets (optional) and displayed at footer area only.
  • Search, 404 – full bleed page header

HTML Structure & Layout Strategy

Before considering a layout strategy, it is essential to understand how different template hierarchy works in WordPress themes so that desired templates can be targeted for styling or other customization purposes.

Note: This article is not about theme design. I am assuming that readers have an understanding of WordPress theme and its components. If not a detailed documentation is available in WordPress theme handbook. A basic knowledge of building custom themes from underscores (_S) will be very helpful.

Template Hierarchy: WordPress uses hierarchical system to select appropriate template file to display desired content by user interaction. Primarily there are following six templates (index, archive, search, error, single-post & page) types of contents that are requested:

  • index.php – this is blog (home) page and used to display content.
  • archive.php – it structurally very similar to index.php and used display contents type such as category, tag, author, date and other archives
  • search.php – for search results display
  • error or 404.php – to handle any errors or queries – error mess
  • single.php – to display single post content
  • page.php – display page content
  • post format: any contents in WordPress is classified into post or page types. If certain post content is desired to be sort, and distinct presented in different look, it is classifies as post format, which includes contents such as aside, status, images, gallery, video, audio etc.

Body Class Tags: For styling purposes, WordPress targets contents with body class tags. Underscores default theme comes with two standard body classes:

'hfeed’: refers to non-singular pages .singular.php template is used for all single-posts (single.php) and page (page.php). In addition, there are many default body class tags to target various content types for layout & CSS styling.

Adding New Body Class Tags

Since index.php (blog/home), archive.php, 404.php & search.php are similar in HTML markup (see attached figure), I grouped them into one body-class ‘archives‘ and added using filter as described by Morten Rand-Hendriksen in archive layout section as follows:

// add archives bodyclass
function my-theme_body_classes( $classes ) {
// Adds a class of hfeed to non-singular pages.
    if ( ! is_singular() ) {
    $classes[] = 'hfeed archives';
    
    return $classes;
 }
add_filter( 'body_class', 'my-theme_body_classes' );

Now by using this single archives body class, I could especially target all archive templates including the index.php (home/blog page).

I also needed create another body class ‘postformat’ to specifically target post format templates and not the standard post.  The post format specific body class tag was added in post_class() as ‘postformat’ string parameter to the HTML tag of all post format specific templates as follows:

< article id="post-<?php the_ID(); ?>" <?php post_class( 'postformat' ); ?>>

Now with the four body classes (.archives, .single, .page & .postformat), I was able to target above six templates for my CSS grid layout using them as grid container as well as for desired content styling.

Site-wide Layout

Based on my above body classes, now I could start planning for how am I going to layout these pages. The HTML structure of .site and .site-main classes look as follows:

<-! .Site class HTML Structure -> 
<div id="page" class="site"> 
  <header id="masthead" class="site-header" role="banner"></header> 
  <img class="custom-header"></img>   
  <header class="page-header"></header> 
  <main id="main" class="site-main" role="main"></main> 
  <footer id="colophon" class="site-footer" role="contentinfo"></footer> 
</div>

The site wide HTML hierarchy of all the pages is hold by .site class which contains its header section (.site-header and .custom-header (optional) classes), main body of content (.site-main class) and colophon (.site-footer class). The .page-header class, which is from archive.php template HTML structure  will collapse when its content is not available in index.php template.

Looking at the HTML structure of the main component shown (figure …), the markup of archive pages and index page is very similar except that archive.php template has an extra .page-header element. Since the .page-header element is one-level higher in hierarchy to .site-main and it is included as grid-item of .site container (figure)

Using the above HTML structure, I prepared following hierarchical sketch (left) showing site-wide components (represented in blue) with .site class as grid container. In my layout design (below right), header & footer section remain same and only post content (.site-main) section (represented in red) varies and thus I had a single site-wide layout using <strong>.site</strong> as grid container and varied layout of the .site-main depending on the page template.

Note: An alternative option would be to use each page template differently and make archives, single posts & page specific layouts. Although, this approach keeps the code separate & clean for understanding purposes, however there will be lot of code duplication  can be minimized using site-wide layout.

Offset margin & padding:

@supports (grid-area: auto) {
@media screen and (min-width: 900px) {
  .page .site-main,
  .archives .site-main,
  .single .site-main {
      max-width: none;
      padding: 0;
      }
   }
}

My layout sketch for index (home/blog) and archives pages is shown in above figure (right). As shown in my sketch, for larger than 900px viewport devices, I layout .site and .site-main components in three column tracks as follows:

@supports (grid-area: auto) {
  @media screen and (min-width: 900px) {
   .site   {
	  display: grid; 
	  grid-template-columns: 1fr auto 1fr; 
	  grid-template-rows: auto;
  }
  .site-header {
	  grid-column: 1/4;
	  grid-row: 1;
	 }
  .custom-header {
	  grid-column: 1/4;
	  grid-row: 2;
	 }
	.archives .page-header {
	  grid-column: 2/3;
	  grid-row: 3;
	 }
	.site-main {
	  grid-column: 1/4;
	  grid-row: 4;
	 }
   .site-footer {	 
	  grid-column: 1/4;
	  grid-row: 5;
	 }
  }
}

Looking at the above code snippets, the .site grid container is assigned with 3 track columns, first and last tracks were assigned with 1fr unit (to serve as padding, while middle column was assign auto. As shown in the layout sketch, .site-header, .custom-header, .site-main & .site-footer were assigned full width (line 1/4). Because .page-header component is available only in archive.php templates it needs to be specified as .archive .page-header and was assigned to center column (line 2/3). Although I wanted to have centered content in larger view post, however I wanted to have full-width featured image and posformat background, the .site-main component was expanded to all three columns.

Home (blog) & Archives Page Layout

Having taken care of header and footer components, now lets look at the site-main components of index and archive page. Looking at the HTML structure (below) and the sketch figure (above) the .site-main class contains four components (including markup components of search.php & 404.php archive templates). The .site-main component of home/blog page (index.php) contains only two sections, article (as .post and .postformat element) & page navigation (.paging-navigation element). The other two components are from search (search.php) & error (404.php) page templates, which are structurally similar to archive page. The .search-form element is wrapped with .page-content element and .secondary-title elements and will be grid-items of .site-main container.

<-! Archive Page .site-main class HTML structure -> 
<main id="main" class="site-main" role="main"></main> 
  <div class="page-content"> 
    <form role="search" method="get" class="search-form"></form> 
  </div> <h2 class="page-title secondary-title"></h2> 
  <article id="post postformat"> 
  <nav class="navigation paging-navigation" role="navigation"></nav> 
</main>

Now using grid line numbers, the grid items were assigned to grid tracks as shown in the diagram. As shown in the sketch, the .entry-header and .paging-navigation elements of post are wider than main content and thus are assigned line 2/6 tracks and the main content in the middle line 3/4 track. The feature image (.post-thumbnail) which is full-bleed was assigned to line 1/6.

@supports (grid-area: auto) {
  @media screen and (min-width: 900px) {	
  .archives .site-main  {
       grid-columns: 1fr auto 1fr; 
       grid-rows: auto;
      }
  .archives .page-content {
       grid-column:1/4
       grid-row: 1;
       text-align: center;
      }
  .archives .page-title.secondary-title {
	grid-column: 2/3
	grid-row: 2
     }
  .archives .postformat,
  .archives .post {
	grid-column: 1/4;
	grid-row: 3;
      }
  .archives .paging-navigation {
	grid-column: 2/3;
	grid-row: 4;
     }
  }
} 

Looking at the above code snippets, the grid column track of site-main and .site elements are similar but the main purpose this section is to define archive page specific .site-main component using .archives body class tag. It is important to use the .archives body class tag to all the grid items to specifically define and assign into appropriate column tracks as shown in layout sketch above; .pagin-navigation and .page-title elements were assigned into middle column (line 2/3) and other components (.page, .postformat and .page-content) to entire three columns.

Index Page Posts & Post Format Layout

The HTML markup of .post and .postformat element of archive page (index.php) is shown below. Structurally both the .post and .posformat are similar but vary in their markups. Often post format don’t contain .post-thumbnail & .entry-header element and without content these elements simply collapse without any effect on content display.

<-! Archive Page post & postformat HTML structure -> 
<article id="post postformat"> 
  <div class="post-thumbnail"></div> 
  <header class="entry-header"></header> 
  <div class="entry-content"></div> 
  <footer class="continue-reading"></footer> 
</article>

A detailed layout sketch plan of post and postformat component is shown below. In the previous section (.ste-main), .archives  .post and .archives .postformat  were assigned to all three 3 column tracks. The first and the last tracks serve as padding in a larger view port. The middle track which hold all the content was assigned auto, is further divided into 3 column tracks thus making a 5 column tracks grid as shown below.  The second and fourth tracks were assigned 10% of the tracks. The center track was assigned with a minmax() function with min-content min track sizing function and 45em as  maximum value parameter.

Now having layout page title, page content, paging navigation are nicely layout. Next we have to layout the post and postformat content.

The central grid track, representing the main content-area, is defined with minmax() function with min-content and length values. The minmax () function accepts two parameters, minimum value and maximum value. Because to avoid overflow of content (eg. wide images or wide string of text etc) minimum values is set to min-content and maximum value to 45em. The fixed maximum values allows content to centered in wide view port devices.

@supports (grid-area: auto) {
  @media screen and (min-width: 900px) {
   .archives .postformat,	  
   .archives .post  {
	display: grid; 
	grid-template-columns: 1fr 10% minmax(min-content, 45em) 10% 1fr; 
	grid-template-rows: auto;
  }

 .archives .post-thumbnail {
	  grid-column: 1/6;
      grid-row: 1;
	  margin-left: -1em;
	  margin-right: -1em;
  }
 .archives .entry-header {
	  grid-column: 2/5;
	  grid-row: 2;
  }
 .archives .entry-content {
	  grid-column: 3/4;
	  grid-row: 3;
  }
.archives  .continue-reading{
	   grid-column: 3/4;
	   grid-row: 4;
  }
 .archives .page-numbers {
      grid-column: 2/5;
      grid-row: 5;	  
  } 
  
  .postformat .entry-header {
	  grid-column: 3/4;
	  grid-row: 1;
  }
  .postformat .entry-content {
	  grid-column: 3/4;
	  grid-row: 2;
  }
  .postformat .entry-footer {
	  grid-column: 3/4;
	  grid-row: 3;
  } 
  
 }					   
} 
.archives .post-thumbnail.full-bleed {
	margin-left: -1em;
	margin-right: -1em;
}

Single Page Layout

HTML hierarchy and page layout sketch for the single post layout is shown below. Site header & footer components are already taken care of in the site-wide layout above.

Single site-main

Similar to the the archives .site-main discussed above, single page .site-main holds .post & post-navigation components. As shown in the sketch we will create .site-main container with five-columns tracks (see below) and assign post to full width (line 1/6) and post-navigation in three tacks (line 2/5).

@supports (grid-area: auto) {
   @media screen and (min-width: 900px) {
	.single	.site-main  {
		max-width: none;
		display: grid; 
		grid-template-columns: 1fr 10em minmax(min-content, 40em) 10em 1fr;  
		grid-template-rows: auto;
		}
        .single .post {
		grid-column: 1/6;
		grid-row: 1;
		}
		
	.single .post-navigation {
		grid-column: 2/5;
		grid-row: 2;
		margin-bottom: 2em;
		}	
	}
}

Note: Main purpose of the site-main container assignment is to center align the main content. However, because our design call for wider width images in the post, we will assign full-width.

For post layout, as shown in the above sketch we will target .post class as grid container and define a five-column grid layout as follows:

@supports (grid-area: auto) {
    @media screen and (min-width: 900px) {
	.single	.post {
	    max-width: none;
	    display: grid; 
	    grid-template-columns: 1fr 10em minmax(min-content, 40em) 10em 1fr; 
	    grid-template-rows: auto;
	   }
					
	.single	.entry-header {
		grid-column: 2/5;
		grid-row: 1;
	}
				
	.single	.entry-meta {
		grid-column: 2/5;
		grid-row: 2;
	   }
	.single .entry-content {
	       grid-column: 3/4;
	       grid-row: 3;
	   }
	
	 .single .entry-content > .centered-wide {
		 grid-column: 2/5;
		grid-row: 3;
		margin-right: -10%;
		margin-left: -10%;
	 }
	
	.single	.entry-footer {
		grid-column: 3/4;
		grid-row: 4;
		}
	  
	.single .comments-area {
		grid-column: 3/4;
		grid-row: 5;
	   } 
	   
	.single .centered-image {
		display: block;
		margin-left: -15%;
		 margin-right:-15%;
	 }   
	 .single .alignwide {
		 display: block;
		 margin-left: -20%;
		 margin-right:-20%;
	 } 
 }
}
  .entry-content .centered-wide {
	    display: block;
		margin-right: -20%;
		margin-left: -20%;
	 }

Page Layout

For page layout, the .page class was targeted as grid container as described in post layout (above) and grid defined as follows:

@supports (grid-area: auto) {
   @media screen and (min-width: 900px) {
	.page .site-main > * {
	     display: grid;
	     grid-template-columns: 1fr minmax(min-content, 45em) 1fr;  
	     grid-template-rows: auto;
	     grid-column-gap: 1em;
	  }	
	 .entry-header {
	     grid-column: 2/3;
	     grid-row: 1;
	  }			
	 .entry-content {
	    grid-column: 2/3;
	    grid-row: 2;/
	  } 
	 .entry-footer {
	   grid-column: 2/3;
	   grid-row: 3;/
	  } 
	 .comments-area {
	    grid-column: 2/3;
	    grid-row: 4;
	  } 
		   
	.page .centered-wide {
	    display: block;
	    margin-left: -5em;
	    margin-right: -5em;
	  }    
   }
}

That is it! The final page looks in about or archives pages here.