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 );
subversiv says
Hey there. I am using the grid loop plugin. The thumbnails are not full width and the pagination shows up in the middle. http://420rebel.com/category/environment/ Also would the code you provide here work in the front page. I want to use 2 columns on the front page. thanks
Bill Erickson says
Your thumbnails are full width. To fix the pagination issue, add this to your theme’s CSS file: https://gist.github.com/billerickson/d7027965bfa252d72cb9
Use the filter ‘genesis_grid_loop_args’ to change the number of columns. For instance, add this to your home.php or functions.php to show 2 columns on homepage: https://gist.github.com/billerickson/182114d87733ef014a92
subversiv says
Thanks! yeah I see they are full width now after I employed the code on this page, before with the grid loop plugin they were aligned to the left with spacing to the right and some words from the excerpt on the right as well. Odd after I added the code on this page that it is now full width. Some of the images on the front page still show spaces to the right of the images however. http://420rebel.com/
For the image sizes I used 300 x 100, they all appear to be different sizes.
I added the advanced code to my functions, On that homepage I currently have the featured widget home-top, and middle left. I wanted to display the middle-left in a 2 column grid instead of how it is currently, one below the other. If I place the code on the front page, would it be able to do that or would it overwrite the widget section. thanks again.
Ben says
I’m desiring to use a two-column post grid on the homepage, but also want a two-column post grid on three separate Category Posts pages where only a specific category shows on the page. Is that possible with this grip loop?
Bill Erickson says
Yes. If you’re using the plugin, go to Genesis > Grid Loop and make sure “Blog Home” and “Category Archives” are checked.
Ben S says
I have decided to use the Genesis Grid Loop plugin and have all page types checked. I chose two-column.
The homepage is setup to show “Your latest posts” and Category Pages are being added to the main menu by Appearance > Menus.
A /blog page has been created and correctly set with the template of Blog, but this one doesn’t appear visually with the two-column layout, instead is shows a single column layout. Why is that and can it be included to become a two-column layout as well?
Also, I created Category Pages via Custom Fields method just to see, and these are also showing a single column layout. Why is that and can it be included to become a two-column layout as well?
Bill Erickson says
This plugin only applies to the main query on the page, so will only work for the actual blog archive (and actual category archives). Custom page templates like the Blog page template will not work. Please see this post: Don’t use the Genesis Blog Template
Zak says
Bill, on the grid page the page title is above the image. How do I move it below, like a caption?
Bill Erickson says
Like this, but remove line 13 and 17 (which limit it to just teasers), and after the ‘remove_action’ add an ‘add_action’ to place the title where you want. Ex:
add_action( 'genesis_entry_content', 'genesis_do_post_title', 9 );Brian says
Thanks, Bill! I implemented the “Even easier sitewide” option above. It works perfectly, but I need to add a sidebar (widget area) above the “genesis_before_content_sidebar_wrap” for each category to show a featured post. I am able to achieve this with the usual code in functions.php, but the new widget area is displaying a column. Any ideas on what hook i should use or how to get a new sidebar (widget area) added above the archive posts and outside the columns? Thank you in advance for any assistance.
Best,
-Brian
Bill Erickson says
Can you post a link? It shouldn’t matter which hook you use, only the posts display in columns.
Brian says
Hey Bill, here is the link you requested. You’ll see the post in the widget is in the 1st column. Not sure what I am doing wrong here. Thanks for taking a look!
http://blog.soarlifeproducts.com/category/cognition-memory/
Currently using the following in functions.php for the widget area above the archive post columns: https://gist.github.com/billerickson/54f0aac2d36cc3d183745c9bb03e773a
Bill Erickson says
Ah, you’re using the Featured Posts widget and the column classes are being applied to it too.
In the first code snippet (the one that adds column classes to post_class filter), at the beginning it checks $wp_query->is_main_query(). Make sure that is included in your function. It limits the post_class filter from running on custom queries, like the Featured Posts widget.
I had forgotten to include that in the advanced example, but have just updated the code.
Brian says
Thanks, Bill. Just to be clear… You updated the code in the “Even easier sitewide” example above and I can just snag that now and replace what I originally used?
Bill Erickson says
Yes, I’ve updated it so you can use either. I was pointing to the earlier code because it’s shorter and easier to read.
Brian says
Thanks, Bill. My apologies, i am a bit of a rookie here… I update my functions.php with the following and I do not see a change. Featured Widget posts are still in the column. Thanks for taking another look.
https://gist.github.com/billerickson/5f77a5f5f1199183f515bc11f37ab78b
Bill Erickson says
The only recommendation I can make is to not use the Featured Posts widget. I thought they had fixed this issue but obviously it isn’t fixed.
The issue is with that widget using the post_class function which should only be used in the main loop. I remove it from every site I build so haven’t had an issue with it.
Brian says
Thanks, Bill! I’ve reached out to the http://genesisdeveloper.me/genesis-featured-posts-combo/ Developer to see if he can provide a remedy for this. I hope so, since I truly like this plugin otherwise. Thanks for all your help on this!
Best,
-B
Torben says
Hey Bill,
I used this setting to change the number of posts on archive pages:
add_filter( ‘pre_get_posts’, ‘be_archive_query’ );
function be_archive_query( $query ) {
if( ( $query->is_main_query() && !is_admin() && $query->is_archive() ) || ($query->is_main_query() && !is_admin() && $query->is_search() ) ) {
$query->set( ‘posts_per_page’, 16 );
}
}//function
The first page of the archive shows 16 posts, when I navigate to the second page the first 8 posts are the last 8 from the first page. Why is this the case, do you have an idea?
Thank you!
Bill Erickson says
I’m assuming you’re either using this code, or you’re using the Genesis Grid plugin which uses this code.
Take a look here. Because the first page can have a different number of posts per page than the rest, we’re using an offset on subsequent pages. The numbers used here come from lines 26-31, right above it.
If you’re using the plugin, go to Genesis > Grid Loop to specify the number of posts you want on the first and subsequent pages. If you’ve manually coded it, modify lines 26-31 to use the number of posts you want.
Torben says
Hey Bill,
thanks. However I have the issue, that I have the following structure on the front page:
– Last post
– Second Last post
– Grid view of all other posts
I have therefore something like this:
return array(
‘last_post_on_front’ => 1,
‘features_on_front’ => 1,
‘teasers_on_front’ => 6,
‘features_inside’ => 0,
‘teasers_inside’ => 12,
);
How do I have to change the offset? I have some issues currently, that the last 3-4 posts of the first page are the first ones on the second page etc. Can you help me? Thank you!
Bill Erickson says
‘last_post_on_front’ isn’t a parameter in my code. Is that something you added?
Based on your current settings you should see 7 posts on the first page and 12 on subsequent pages. Page 2 should start with Post #8.
Torben says
Hey Bill,
this is a parameter that I would like to add, because I display one additional post on the front page, before the grid loop starts, but I thought I have to tell the grid loop that there is one additional post on the front page in order to calculate the offset correctly. Currently it is not working properly, because the grid loop does not now about this additional post on the front page… I would like to add this piece of information to your code, but currently I am stuck with the offset calculation.
Isela Espana says
Hello,
For hours I have been reading all the comment and suggestions to try and figure out what I am doing wrong but have yet to figure it out.
I am trying to hide the excerpts. I only want the titles and images to show up but everything I have tried is not working.
Here are my settings:
post page is my home.php This is what I have in there: https://gist.github.com/anonymous/a53df08be1ca85acc8c5e3f7199ffa08
This is in my theme functions:
https://gist.github.com/anonymous/fcf31890bf0a51536845a74c016b0c15
Thoughts?
If you see any ruminants of the grid plugin, I was going to initially use, but I have since deactivated it.
Any thoughts?
Thank you,
Isela
Bill Erickson says
The code you’re using is really old. It’s designed for Genesis pre-HTML5. You need to use the more modern hooks (ex:
genesis_entry_contentinstead ofgenesis_post_content). Look at thegenesis_reset_loops()function in genesis/lib/structure/post.php to see a list of all the HTML5 hooks and their old counterparts.But more importantly, you’re using the built-in
genesis_grid_loop()function, and this whole article is about why that’s not a good idea. It’s easier and better to simply filter the post classes rather than change out the main loop like that.Isela Espana says
Hello Bill,
Thank you for your quick reply.
I think that code was just copied pasted from somewhere while I was trying to get it to work as I wanted. I know basics when it comes to php to be honest.
I was actually trying to use the plugin but couldn’t get to do what I was looking to accomplish so I gave this snippet you have here a try.
Do you suggest I just try something else then? Is there no easy fix to what I already have?
I feel like it’s soooo close to working, but just not there.
Gene Whitehead says
Hi Bill,
I’ve been using your work here for well over a year without issues and I’m thankful for that!
Sadly, today I noticed that my featured images are no longer full width. I can’t for the life of me find any recent changes that would cause this.
Here’s the grid images section from my functions.php, but nothing changed. Am I looking in the wrong place?
https://gist.github.com/anonymous/fae7604bb66b68d1415138a2e76f5d41
/**
* 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 query_vars[‘paged’] && $wp_query->current_post query_vars[‘paged’] && $wp_query->current_post > ( $grid_args[‘features_on_front’] – 2 ) ) || ( $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’ );