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 );
Lauren says
Hi Bill!
Thank you so much for this. I’ve gone the plugin route, and was able to use some of your coding advice to move the thumbnail over the post title… Perfect…
But now I’m wondering.. Is it possible to have the post title OVER the thumbnail on just the featured posts, but not the grid?
Grid = Title underneath image,
Feature = Title over image…
Any advice would be amazing! Been picking away at this for a while now.. thanks 🙂
Bill Erickson says
Yes, just check
get_post_class()to make sure you’re in a teaser post first (name for non-feature posts in the grid): https://gist.github.com/billerickson/85d5cc268b0a2255a19cCorinne Kelley says
I think I almost cried after finding this solution. You are amazing. I used your grid layout, but the layout of the grid page was all over the place, because if the post had a long title it would push the thumbnail down which made the rows look all off. Now with this solution, at least all of the thumbnails are in a nice neat row, no matter the post title length. Is there also possibly a way to limit the amount of lines or even characters a post title can be in the grid layout, cutting it off with a … ?
Bill Erickson says
While you can’t limit the number of lines, you can limit the title by character count or word count. Use the
genesis_post_title_textfilter along with one of these truncation methods.Corinne Kelley says
Hi Bill,
thank you so much for your response. You are amazing to take the time to respond to these questions. I know how much time it can take up. Many thanks.
Lorand says
Thank you so much for such a useful plugin, is the first time I used and is exactly what I was needed. I don’t want to bother you but I looked everywhere for a solution and I didn’t find one that really works… is any chance to insert an AD by replacing a teaser, for example the first position in grid to be a banner instead of a post? Thank you!
Bill Erickson says
You can use a similar approach as outlined in the above post, but you won’t be able to use this plugin. Basically you’ll need to create your own custom counter rather than relying on the post counter in
$wp_query. So at the top of your loop, do something like:global $be_counter; $be_counter = 0;. Then after each post do$be_counter++to increment it upwards.Then, before the post displays check to see if the counter equals some specific number (ex: $be_counter == 2 for the 3rd slot), and if it does, display the ad and increment the counter upwards.
Temitayo Boboye says
Did you get this to work Koran, I have tried wrapping my head around it but couldn’t. If you did find a work around it would be nice to share
Chris says
Hi Bill, love this plugin thank you. I currently have a 1600px banner set as my featured image in posts, however this shows up extremely small as a grid teaser image. How can I assign different sizing to my teaser images, is there a teaser class I can specify and add some CSS for the desired effect e.g. teaser_image_size {height: auto; width: 100%;} ? Thanks!
Bill Erickson says
A better option is to register a new image size to be used for the teasers, then go to Genesis > Grid Loop and select the appropriate image size to be used for features and teasers.
Chris says
Thanks Bill! I appreciate the pointer, worked like a charm.
Question about cropping, I sometimes use this handy plugin called Manual Image Crop for cropping after an image has been uploaded. However without the aid of any plugins, is the only way to achieve a secondary teaser crop 270px x 300px of a featured image, but uploading a new featured image?
Thanks again
Bill Erickson says
You can use the Image Override plugin to upload an alternative image for specific image sizes. You’ll need to crop it to the correct dimensions yourself in Photoshop though.
Chris says
Thank you Bill!
Ivan says
Hi Bill. I am really glad I found your blog. Great information.
Since this post was published about 3 years ago I just wanted to check with you if this is still valid and best way of doing it with the current version of Genesis 2.1.x.
Second, how would you recommend displaying different info from the post instead of the regular Title, excerpt, cat, tags, etc. … I need to display the featured image along with a few custom fields for a custom post type.
Thanks in advance.
Bill Erickson says
Yes, this is still the best way to build a grid for the main loop.
You can always unhook the functions you don’t want running in the loop (see /wp-content/themes/genesis/lib/structure/post.php); or if everything will be completely different, unhook the whole loop ( remove_action( ‘genesis_loop’, ‘genesis_do_loop’ ) ) and build your own custom loop.
Ivan says
Thanks for pointing this out. I’ll dig into the code. I am still getting familiar with Genesis.
Torben says
Hey Bill,
thanks for sharing the valuable input on your page. I find it very useful in many aspects.
I used some parts of your code above and it works really well, but I have one question left:
When using the above mentioned code for the grid, is there any possibility to create a hook/custom html code which will be inserted in the transition from featured posts to the grid/teaser view? I want the grid/teaser view to have a headline.
Would be really glad if you have an answer to that. Have a nice weekend.
Bill Erickson says
Hook into ‘genesis_before_entry’, and check the post count. If it’s the first post that’s supposed to be in the grid loop, you know you can output the grid/teaser headline.
If you have 2 feature articles and 8 teasers on the first page and 10 teasers on the subsequent pages (so no headline needed), the code might look like this: https://gist.github.com/billerickson/7e1c1de1e6ff592aa14a
Torben says
Ha. Quite simple. Sometimes you just don’t get it when sitting in front of the screen for hours.
Thanks. Works like a charm 😉
Torben says
Hey Bill,
I have another question related to the grid loop which may be useful for others too:
How can I achieve that the featured image of a post is displayed in different content sections dependent if its a feature or a teaser?
In the features I want to achieve the following order:
– Posting date
– Headline
– Excerpt
– Image
The teasers should look like:
– Image
– Posting date
– Headline
– Excerpt
I tried it with:
https://gist.github.com/elbsurfer/d90d729115e274c173ce#file-gistfile1-php
With this solution the features are shown in good order, but the teasers have an image at the end of the entry_header, which is the wrong order, and an image at the end of entry_content which I in general think its weird because the post_class “one-half” does not have a class “feature” – So the after_entry_content should not be fired if its a “one-half” teaser.
Do you have a hint how you would solve this? Would be great. Have a good Sunday.
Torben says
I added a comment, because I solved it I guess 😉
Amber @ Au Coeur Design says
Hi Bill,
For some reason, when I add your code, as written in the advanced example, to a website running the Beautiful child theme, the pagination disappears (on both the home and categories). Any idea why this might be? http://50.87.248.130/~fitfood6/
Thank you!
AnitaC says
Hi Bill,
Using the plugin… for some reason the pagination is missing on one site and on another site the pagination is there but it’s out of place way at the top in between two posts. I see another person on the plugin page having the pagination issue as well. I think it happened after the last WP update.
Thanks,
Anita
jodie scott says
Ive done another dev site test running 4.1.1, this time with only the grid plugin installed…. nothing else. The pagination now appears at the bottom of the page, however the plugin is ignoring all settings such as 2 columns, posts per page etc. The image size also isnt responding, it will put the size image up that is set in the theme settings only.
Thanks
Jodie
Bill Erickson says
It sounds like you’re having a clearing issue in your CSS. Try adding this to style.css
.archive-pagination { overflow: hidden; width: 100%;}
AnitaC says
Thanks for responding Bill. On my demo site using Modern Studio the pagination isn’t showing up at all. I added the CSS to my demo just to see if it would show up and it hasn’t. When I reveal source, I don’t see this “” as I do on the regular blog page, like it’s missing. Here is a screenshot using Firebug where I can see the DIV on the pagination of the blog archive page – http://goo.gl/QMZHge. Here is the screenshot of the grid layout without the DIV – http://goo.gl/e4pE0N. I deactivated all plugins too.
Bill Erickson says
Have you done anything to modify the WordPress query on those pages? Or have you used the ‘genesis_grid_loop_args’ filter?
Pagination functions look at paraemters in $wp_query, specifically the total posts and posts per page. Since this plugin can have a different number of posts per page on the homepage and inner pages, I had to rebuild how it calculates this number so it won’t be off. See here. I’d start your debugging there.