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 );
Eva says
Hi Bill,
Thank you for the plugin, I have installed it and I do hope it will help me to achieve what I want 🙂
I would like to use it together with Easy Content Types by Pippins Plugins to show an unlimited number of book covers (features images) in 4 columns on the home page of an editor with a right sidebar.
Do you think that would be possible?
I hope to hear from you.
Thanks in advance,
Eva
Bill Erickson says
I’ve never used Easy Content Types, but yes this plugin should work with any custom post types you develop. In the backend of your site go to Genesis > Grid Loop and you’ll see a list of checkboxes for areas of the site. Your custom post types will be listed there. Check the “book covers” post type (or whatever you’ve called it), and set features to 0 and teasers to 4 columns.
Eva says
Hey Bill, thanks for your prompt reply! That’s what I did, but unfortunately it does not show anywhere. I guess I have to create a special template to show the custom types. I am using the stripped Genesis Sample Theme by Sridhar Katakam, which gets rid of Genesis Page Templates. Easy Content Types doesn’t support automatic template files when using a child theme, so I guess I have to create a template… By the way, when that’s done, can I set the number of teasers to 200 or even more to show all covers, or would there be a better way to do that?
Thanks again for your time. Highly appreciated!!
Eva
Bill Erickson says
Yes. If you’re using the default WordPress query (ex: custom post type archive), pre_get_posts will let you change the posts displayed on each page (tutorial here). If you’re using a custom WordPress query in a page template, you will specify the ‘posts_per_page’ in the query arguments (tutorial here).
Torben says
Hey Bill,
I use your grid loop on multiple websites. It has been very useful in the past. Thanks for that.
Since the latest updates of the Genesis Framework and WordPress, the latest posts which are released are not be displayed as the first post on the page. I have to set them as “sticky” in order to show them at the first position.
Do you know if something relevant has changed and I might have to add?
if( $query->is_main_query() && $query->is_archive() ) {
$query->set( ‘orderby’, ‘date’ );
$query->set( ‘order’, ‘desc’ );
}
Thanks!
Bill Erickson says
You shouldn’t need to modify the query in any way. Genesis Grid Loop doesn’t affect the WordPress query other than to change the number of posts per page.
You can disable the plugin to confirm it is caused by something else. Then look for any plugins or code in your theme that might be modifying the query.
Torben says
Hey Bill,
thanks for the quick reply. I will investigate – however I do not use the Genesis Grid Plugin, I use the code above within the functions.php. I am just wondering because I have not changed anything, just updated WP, Genesis and Plugins.
Lindsay says
Hi Bill,
Your code to output my genesis grid loop as two or three columns works like a charm, but it’s also effecting my featured-posts widget on my home page, so that the image and text of the widget is forced into either half or one-third the space of the widget area.
I am not adept with PHP — can you tell me how to add a conditional statement so that the layout modification does not apply to the home page?
Thanks very much for all your help!
Bill Erickson says
Are you using the most up-to-date version of the code? In the first code snippet the $wp_query->is_main_query() check is specifically so that it doesn’t affect the Featured Posts widget.
If you’re still having issues, I recommend you remove all the code you added and use my Genesis Grid plugin instead. It’s the same basic code, but you can tweak the settings in Genesis > Grid Loop instead of by editing code.
Lindsay says
Hi Bill,
When I use the code from the first snippet it crashes my site (500 error) and I have to go into my host account file manager to delete the code.
I’ve been using the code you recommend for site-wide application (this one): https://gist.github.com/billerickson/2046751#file-gistfile1-aw
Like I said it works great; it just also works on the featured posts widget! Is there a way to create a conditional tag that excludes just the front page?
Thanks!
Bill Erickson says
Change this:
if( is_singular() )
To this:
if( is_singular() || is_front_page() )
Lindsay says
That did it! Thanks so much Bill 🙂
Just curious — is it possible to make *only* the first post on each page display horizontally (photo to right and text on left) and the rest of the posts display in columns of three (photo above, text below)? Can you do this with PHP?
Lindsay says
Hi Bill,
I spoke too soon. Actually, adding the code
if( is_singular() || is_front_page() )
just deactivates the whole code, so the blog page is no longer in columns.
It seems like it should work regardless of my Reading settings. What do you think is going wrong here? Much thanks for your help!
Lindsay says
Hi Bill,
I tried
if( is_singular() || is_home() )
and
if( is_singular() || is_active_widget() )
but neither of these solved my problem. So I downloaded your Genesis Grid Plugin. It works like a charm on my category pages, but NOT on my blog page. I can’t figure out why after spending 1.5 hours looking at my settings and the code.
I suspect it is because I set up a custom blog page template so that I could add static material to the top of my blog page.
If you have any suggestions at all, I will be most appreciative.
Thank you
Lindsay
Bill Erickson says
Yes, that’s exactly the issue. The code above only applies to actual blog archives ( where is_home() is true). If you created a page template, that’s a Page, not the blog archive. See this post for more information: https://www.billerickson.net/dont-use-genesis-blog-template/
Ferlin says
Hi Bill
I used your code, works very well, thanks.
Only 1 question, how can I add a text box with some content above the Feature Post (top of the page but under header)?
I tried adding through content area on WP, not luck. I sifter through the gridloop code, is there something to add there?
Thanks
Bill Erickson says
See here: https://www.billerickson.net/dont-use-genesis-blog-template/#comment-13307
Ferlin says
Hi Bill
I’m using Genesis Sample theme, added the “Advanced Code” above to functions.php. All works.
I created a page called “Blog1” and made that my Blog page through Reading> Posts Page > Blog1
That works .
The link you suggested to add content to the top of the blog page says create a home.php file and add new code. When I do that and change the Posts page to Home (using home.php) the blog page is blank.
I must be doing something wrong. Is my code in functions.php to be replaced with the first code blocks?
Thanks
Bill Erickson says
Yes, as Ivan said, it sounds like you didn’t include genesis() at the bottom of the theme file.
Code in functions.php applies site-wide. Code in home.php applies only to the blog homepage.
If you’re still having issues with it, delete the home.php file, place the code in functions.php, and limit it to the homepage like this: https://gist.github.com/billerickson/44bf1782129b92b967bf
Ferlin says
Now works – thank you!
I think before I also might have created the new Blog Page and ascribed the home template to the blog.
Bill, I see your area is more involved coding but appreciate you still helping those of us with the basics – great model for paying it forward. The code snippets do make a difference, so thanks very much.
Ivan says
Always use an Genesis theme file as your starting point. For example index.php.
In your case you are probably missing genesis() function that echo the html code.
Ferlin says
Thanks Ivan, is working now, I posted to Bill.
Deborah says
Hi Bill, the plugin works wonderfully! However, I’m a PHP novice and can’t figure out how to change the number of columns for specific category archive pages (without changing the global number of columns). I’ve tried creating an Archive.php page in the child theme, plus adding this to the functions.php, but it’s not working — throws an error or just displays a blank page: https://gist.github.com/billerickson/1999b06362cc10e27d19
Any help would be greatly appreciated. Thank you so much!
Bill Erickson says
First, look at the template hierarchy to figure out what template file you should create. If you want to apply it to a category called dog, you could create a file called category-dog.php.
Make sure that template file has the
genesis();function in it so that it works. Then use the ‘genesis_grid_loop_args’ filter to customize the grid loop.Here’s a working example: https://www.billerickson.net/code/change-grid-arguments-for-a-specific-category/
Deborah says
That worked beautifully. Thank you so much! I’ll PayPal you my thanks 🙂
Gene Whitehead says
Hi Bill,
After some issues with the Grid Loop plugin, I found this tutorial which does fix my issues. The one problem I’m having is my teasers are staggered.
I did change to 2 columns and I changed the amount of featured and teaser posts displayed. I’ve searched all over and read almost all of the comments here but didn’t come across this issue.
Here’s what I have on functions.php: https://gist.github.com/anonymous/c2c15514f0f9cec2ce22
And here’s the site: http://genewhitehead.com/
Thank you for any advice, and happy new year!
Bill Erickson says
I’m not seeing anything wrong with the grid. Did you solve the problem? If not, can you provide a more detailed description of the issue?
Thanks
Gene Whitehead says
My apologies, I turned the plugin back on temporarily yesterday, it’s now off again and I put this code I shared back in.
The teasers are not aligning vertically, they are staggering, leaving a gap between some of them. The other thing I noticed, and it may somehow be related, the pagination on the second page is in the middle of the page and the teasers are very sporadically placed.
I messed with this for quite some time and I just cannot fix it! That’s why I jumped back to the plugin last night. I’ll leave it like this now, I certainly appreciate your help!