Don’t feel like coding? I built a plugin that does this for you.
See the Genesis Grid Loop plugin.
Note: This technique can be used by all WordPress themes. I’m proposing it as a replacement for the Genesis-specific grid loop, so aspects of this post might be Genesis-specific.
A popular request is to list posts in multiple columns. I do it on my blog, and do it often on my clients’ sites.
Genesis developed a Grid Loop, which you can utilize inside your Genesis themes for this effect, and is how this blog does it. While very useful, it can be difficult to set up. I believe this is because they combined two separate functions: what content to display (your query) and how to display it.
By breaking those two functions we can use another feature of Genesis, the column classes, to build grid loops easier. You can also copy that CSS to any WordPress theme and make it work too.
For this example I’ll be using a Gallery theme I recently built. On the archive pages, it displays posts in three columns. See the screenshot above, or click here) for an example.
Step 1: Multiple columns using post_class
The post_class filter lets us customize the classes applied to each post in the loop. Since I want this to only apply to archive pages, I’m placing it in archive.php.
/**
* Archive Post Class
* @since 1.0.0
*
* Breaks the posts into three columns
* @link http://www.billerickson.net/code/grid-loop-using-post-class
*
* @param array $classes
* @return array
*/
function be_archive_post_class( $classes ) {
global $wp_query;
if( ! $wp_query->is_main_query() )
return $classes;
$classes[] = 'one-third';
if( 0 == $wp_query->current_post % 3 )
$classes[] = 'first';
return $classes;
}
add_filter( 'post_class', 'be_archive_post_class' );
The first line adds a class of ‘one-third’ to all posts. Then I grab the current post counter out of $wp_query, and if this is the first post ( 0 == $wp_query->current_post ) or if the remainder of the current post divided by 3 is zero (this tells us the current post is the first in a row), apply a class of “first” as well.
That’s it! You now have your content broken into multiple columns. If you want two columns, use ‘one-half’ and divide the current post by 2. If you want four columns, use ‘one-fourth’ and divide the current post by 4.
Step 2: Customize the Query
When I view the archive page now, it’s in three columns but it’s only displaying 10 posts. The last post sits by itself in its own row. I’m going to modify the main query to show 27 posts per page. You could use any number you want, just make sure it’s a multiple of columns. For more information on customizing the main query, see this post.
/**
* Archive Query
*
* Sets all archives to 27 per page
* @link http://www.billerickson.net/customize-the-wordpress-query/
*
* @param object $query
*/
function be_archive_query( $query ) {
if( $query->is_main_query() && $query->is_archive() ) {
$query->set( 'posts_per_page', 27 );
}
}
add_filter( 'pre_get_posts', 'be_archive_query' );
This code must go in functions.php, since the main query runs before it reaches archive.php (it checks the query to figure out what template to load).
Even easier sitewide
If you’re using this for all listings of posts (home, archive, search…), it is even easier to set up. Put the post_class filter in functions.php so it runs sitewide, and add a conditional to check if it isn’t singular:
/**
* Archive Post Class
*
* Breaks the posts into three columns
* @link http://www.billerickson.net/code/grid-loop-using-post-class
*
* @param array $classes
* @return array
*/
function be_archive_post_class( $classes ) {
// Don't run on single posts or pages
if( is_singular() )
return $classes;
$classes[] = 'one-third';
global $wp_query;
if( 0 == $wp_query->current_post || 0 == $wp_query->current_post % 3 )
$classes[] = 'first';
return $classes;
}
add_filter( 'post_class', 'be_archive_post_class' );
And instead of the function to customize the number of posts, go to Settings > Reading and tweak it there.
Advanced Example
The code snippet below (added to functions.php), modifies the blog’s homepage and archive pages to display 5 features and 6 teasers (in three columns) on the first page. On inner pages, it displays 0 features and 12 teasers (in three columns). It also updates the post image to use image sizes specifically created for features and teasers.
The first function, be_grid_loop_pagination(), is where we control the grid loop. Under the comment that says “Sections of site that should use grid loop”, you can modify that list to specify where you want the grid loop displayed. Right now it is running when is_home() or is_archive() is true. The second part, under the comment that says “Specify pagination”, is where you specify how many features and teasers to show on the homepage and subsequent pages.
The second function, be_grid_loop_query_args(), doesn’t require any customization from you. It uses the pagination information you added to the previous function to tell WordPress how many posts show up on each page.
The third function, be_grid_loop_post_classes(), applies relevant classes to each post. It’s adding a class of ‘feature’ to each feature post, and column classes to the teasers. The only thing that you’d need to change is the teasers sections if you want something other than three columns. Here’s what you’d need to change if you wanted two columns.
The fourth function, be_grid_image_sizes(), specifies the two image sizes. Change these to whatever size you’d like. If you don’t want images, you can leave out this function and the last one, be_grid_loop_image(). Also note that you need to have “Include Featured Image” checked in Genesis > Theme Settings > Content Archives.
The fifth function, be_grid_loop_image(), overrides the image size set in Genesis > Theme Settings > Content Archives with the image sizes we created in the previous function. No changes need to be made to this function.
The sixth function, be_fix_posts_nav() does as its name implies. The post navigation (Older/Newer, numerical links to posts pages…) uses $wp_query->max_num_pages to know how many pages there are, and this is based on the current page’s posts_per_page. So if you have less posts on your homepage than inner pages, the post navigation on the homepage will be off (this is noticeable if you’re using numerical links). This code changes the max_num_pages based on the grid args.
/**
* Grid Loop Pagination
* Returns false if not grid loop.
* Returns an array describing pagination if is grid loop
*
* @author Bill Erickson
* @link http://www.billerickson.net/a-better-and-easier-grid-loop/
*
* @param object $query
* @return bool is grid loop (true) or not (false)
*/
function be_grid_loop_pagination( $query = false ) {
// If no query is specified, grab the main query
global $wp_query;
if( !isset( $query ) || empty( $query ) || !is_object( $query ) )
$query = $wp_query;
// Sections of site that should use grid loop
if( ! ( $query->is_home() || $query->is_archive() ) )
return false;
// Specify pagination
return array(
'features_on_front' => 5,
'teasers_on_front' => 6,
'features_inside' => 0,
'teasers_inside' => 12,
);
}
/**
* Grid Loop Query Arguments
*
* @author Bill Erickson
* @link http://www.billerickson.net/a-better-and-easier-grid-loop/
*
* @param object $query
* @return null
*/
function be_grid_loop_query_args( $query ) {
$grid_args = be_grid_loop_pagination( $query );
if( $query->is_main_query() && !is_admin() && $grid_args ) {
// First Page
$page = $query->query_vars['paged'];
if( ! $page ) {
$query->set( 'posts_per_page', ( $grid_args['features_on_front'] + $grid_args['teasers_on_front'] ) );
// Other Pages
} else {
$query->set( 'posts_per_page', ( $grid_args['features_inside'] + $grid_args['teasers_inside'] ) );
$query->set( 'offset', ( $grid_args['features_on_front'] + $grid_args['teasers_on_front'] ) + ( $grid_args['features_inside'] + $grid_args['teasers_inside'] ) * ( $page - 2 ) );
// Offset is posts on first page + posts on internal pages * ( current page - 2 )
}
}
}
add_action( 'pre_get_posts', 'be_grid_loop_query_args' );
/**
* Grid Loop Post Classes
*
* @author Bill Erickson
* @link http://www.billerickson.net/a-better-and-easier-grid-loop/
*
* @param array $classes
* @return array $classes
*/
function be_grid_loop_post_classes( $classes ) {
global $wp_query;
// Only run on main query
if( ! $wp_query->is_main_query() )
return $classes;
// Only run on grid loop
$grid_args = be_grid_loop_pagination();
if( ! $grid_args || ! $wp_query->is_main_query() )
return $classes;
// First Page Classes
if( ! $wp_query->query_vars['paged'] ) {
// Features
if( $wp_query->current_post < $grid_args['features_on_front'] ) {
$classes[] = 'feature';
// Teasers
} else {
$classes[] = 'one-third';
if( 0 == ( $wp_query->current_post - $grid_args['features_on_front'] ) || 0 == ( $wp_query->current_post - $grid_args['features_on_front'] ) % 3 )
$classes[] = 'first';
}
// Inner Pages
} else {
// Features
if( $wp_query->current_post < $grid_args['features_inside'] ) {
$classes[] = 'feature';
// Teasers
} else {
$classes[] = 'one-third';
if( 0 == ( $wp_query->current_post - $grid_args['features_inside'] ) || 0 == ( $wp_query->current_post - $grid_args['features_inside'] ) % 3 )
$classes[] = 'first';
}
}
return $classes;
}
add_filter( 'post_class', 'be_grid_loop_post_classes' );
/**
* Grid Image Sizes
*
*/
function be_grid_image_sizes() {
add_image_size( 'be_grid', 175, 120, true );
add_image_size( 'be_feature', 570, 333, true );
}
add_action( 'genesis_setup', 'be_grid_image_sizes', 20 );
/**
* Grid Loop Featured Image
*
* @param string image size
* @return string
*/
function be_grid_loop_image( $image_size ) {
global $wp_query;
$grid_args = be_grid_loop_pagination();
if( ! $grid_args )
return $image_size;
// Feature
if( ( ! $wp_query->query_vars['paged'] && $wp_query->current_post < $grid_args['features_on_front'] ) || ( $wp_query->query_vars['paged'] && $wp_query->current_post < $grid_args['features_inside'] ) )
$image_size = 'be_feature';
if( ( ! $wp_query->query_vars['paged'] && $wp_query->current_post > ( $grid_args['features_on_front'] - 1 ) ) || ( $wp_query->query_vars['paged'] && $wp_query->current_post > ( $grid_args['features_inside'] - 1 ) ) )
$image_size = 'be_grid';
return $image_size;
}
add_filter( 'genesis_pre_get_option_image_size', 'be_grid_loop_image' );
/**
* Fix Posts Nav
*
* The posts navigation uses the current posts-per-page to
* calculate how many pages there are. If your homepage
* displays a different number than inner pages, there
* will be more pages listed on the homepage. This fixes it.
*
*/
function be_fix_posts_nav() {
if( get_query_var( 'paged' ) )
return;
global $wp_query;
$grid_args = be_grid_loop_pagination();
if( ! $grid_args )
return;
$max = ceil ( ( $wp_query->found_posts - $grid_args['features_on_front'] - $grid_args['teasers_on_front'] ) / ( $grid_args['features_inside'] + $grid_args['teasers_inside'] ) ) + 1;
$wp_query->max_num_pages = $max;
}
add_filter( 'genesis_after_endwhile', 'be_fix_posts_nav', 5 );
Hina says
Hello Bill,
Many Many thanks for this wonderful tutorial.
I am trying to achieve the look as per your Gallery theme http://photography.billerickson.net/sets/all/
I am using your plugin I used this code to remove the content on teasers:
https://www.billerickson.net/code/no-content-on-teasers-in-grid-loop/
I have almost acheived the look except under the title it shows 3 dots (…).
How to totally disable entry content division so that only image and title below it could be seen?
Thank you for your help in advance.
Thanks
Samantha says
Is there a way to make this a masonry layout (pinterest style) instead of each row lining up?
Bill Erickson says
This plugin (and the code displayed in the post) is designed to create a grid layout, not a masonry layout.
This theme might be available soon through StudioPress. But you can still view the code (masonry args) to see how he’s done it.
Janet Atkinson says
Hello Bill,
Thank you for your informative website!
My question is this:
I need to create a grid display for 3 separate category sections of all posts on the home page.
How do I create a separate query for each category? I’ve used your post-class code in my functions.php and have got my 3 columns setup to display all at the moment. Just need to subdivide by category. I can create a separate home.php file if necessary.
Any tips would be greatly appreciated! Thanks so much!
Bill Erickson says
This plugin is not appropriate for what you’re trying to do. You’ll have to build a custom page template with three separate queries (one for each category/column). Here’s some more information on that: https://www.billerickson.net/custom-wordpress-queries/
Janet Atkinson says
Hello All,
To follow up I’d like to share my solution.
I used Bill’s plug-in Display Posts Shortcode.
Inserted shortcode for each categories on one static page. Worked like a charm!
Nathan says
Great tut. Will this work in the ‘genesis_after_entry’ filter on a single-custom_post_type.php page? I’m trying to make a grid that displays related posts from the same taxonomy as the current custom post type, and I’d like to incorporate this into my template. Essentially, I’m trying to build a ‘related products’ grid that pulls items from the taxonomy the current custom post type shown is from.
Where I have some confusion: Does a single post/page template have a loop run in it, or does it output only a single page/post? If no loop, natively, I’m guessing I probably need to:
1. Call a genesis loop into the genesis_after_entry hook.
2. Then add an action to the loop to begin entering you function/similar to achieve what I’m trying to do?
I’m guessing I will have to figure out how to query the taxonomy and then write something to eliminate the existing post ID from displaying in that loop feed, but I’m just having some trouble trying to figure out where to start with this. I really want to try to keep this contained to this template file only, where it will serve its purpose.
Any tips or ideas to push me in the right direction?
BTW, thanks for the great tuts… your blog has been very helpful as I am trying to learn how to edit my themes’ php files.
Bill Erickson says
No, it will not work on the ‘genesis_after_entry’ hook. This only applies to the main query on the page, not to custom queries.
The loop on the single post/page is what outputs the single post title and content.
For what you’re looking to do you’ll need to do a custom query. When looping through you posts, wrap them in column classes to break it into multiple columns. Here’s an example: https://gist.github.com/billerickson/7271816
Brandon says
Hi Bill,
Thanks for the great tutorial! I’m hoping you can help me solve a responsiveness issue I’m experiencing with this method. I would like for the posts to be in two columns once the screen size gets to a certain width. I’ve achieved this by changing .one-third to 42%. The problem is that after every third post, the next post starts a new row. So the format ends up like
this: instead of this (what I want):
Post 1 Post 2 Post 1 Post 2
Post 3 Post 3 Post 4
Post 4 Post 5 Post 5 Post 6
Post 6
Do you have any thoughts on how this could be fixed? The problem can be seen on the site that I listed in the comment form.
Thanks a lot, Bill!
Brandon says
Oh man, the format of those “posts” didn’t turn out the way I meant it to. Sorry about that!
Bill Erickson says
The way I’ve done this before is different classes. Use one-third and first for the desktop view. Then use tablet-one-half and tablet-first for lower resolutions.
Hope that helps
Brandon says
Bill,
Thanks for the response. I was actually able to get it to work by removing the clear: both attribute from the .first class.
Heidi @ One Creative Mommy says
Hi, Bill. I absolutely love your plugin! Thank you so much for creating it. In reading through all of these comments, I’ve found lots of tweaks that I can implement on my site. Before I knew about the plugin, I started with a free chlld theme that included the loop function (Adaptation). I couldn’t get the loop to look right, so I deleted that plugin’s home.php file (the place where that theme included the loop functionality) and added your Genesis Grid plugin instead. It worked fantastically! I just have one problem. I can’t figure out how to get any of the posts (the full-width or the teasers) to show a [read more] link. I have one full-width teaser followed by grid teasers. If you’d like to see my site, it’s OneCreativeMommy.com. Thanks so much!
Bill Erickson says
Your theme might have disabled the excerpt more text. Try adding this to the bottom of your theme’s functions.php file: https://gist.github.com/billerickson/8919278
Heidi @ One Creative Mommy says
Worked like a charm! You’re a genius. Thanks so much for taking the time to help me fix it.
Heidi @ One Creative Mommy says
That’s weird. It worked perfectly on my test site, but on my real site, it only adds the read more text to the smaller excerpt post and not to the full-size feature. They both have exactly the same functions.php file. I must have something different in the settings somewhere. I’m happy to have it partially working, though. Thanks again.
Heidi@OneCreativeMommy says
I figured out the problem. It’s the posts with a manual excerpt included that won’t show the read more text. I’ve been fiddling with it, but it’s too advanced for me. I had this code working (although I hated the styling)
function excerpt_read_more_link($output) {
global $post;
return $output . ‘ID) . ‘”> Read More…‘;
}
add_filter(‘the_excerpt’, ‘excerpt_read_more_link’);
but when I checked back on my site a little while ago, all the text of the posts disappeared. I put your code back. I guess I’d better just be happy with what I have. Maybe I’ll just quit putting in excepts! Thanks again.
Chris says
Hi Bill,
This was the exact bit of code I was looking for to go into a current project. I have the advanced version of your code working like I need it and have added in the custom excerpt length code which you have here:
https://www.billerickson.net/code/genesis-grid-change-length-of-content-in-grid/
The last bit that I’d like to do is add the formatting and so forth back to the excerpts for the featured posts and add in a read more link as well.
I have a rather long excerpt on the featured post and it just looks like one massive slab of text at present so really hope you have some magic up your sleeve that can point me in the right direction.
Drew | The Hungry Partier says
Hi Bill,
Great Tutorial!
I am using Grid Loop on my homepage of my blog, and it is only showing 24 posts, when I want it to show 35. I have tried everything and I just can’t seem how to change this!
Can you please offer any advice?
Thanks so much!
Drew
http://www.thehungrypartier.com
Amy @ Wildflower Ramblings says
Hi, Bill, this is so helpful (as I am very new at coding if I can call it that) — I want to put post features and snippets like you’re doing, but above them I want two full posts. How can I do this? Thanks for any help you can give!!!
Robin Rowell says
Bill, this has got me banging my head against the wall trying to extend things. Using your “Easier Sitewide” example, I am trying to figure out how to make the class “one-half” if there are 2 posts and “full” (or no class at all) if the post count is one. Have you done something like this? Thanks in advance if you can help.
Bill Erickson says
You can use
global $wp_query; $wp_query->found_poststo see how many posts have been returned by the query.