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 );
Mark Ratledge says
Bill, Very nice grid setup, and I’d like to use it in a non-genesis theme, but I’m not having any luck putting the advanced example in functions.php and having it work. I’m using the standard loop in index.php. But it’s not hooking in and working; any ideas on what do I need to try to get this to work in a non-genesis theme?
Mark Ratledge says
Bill, Well, you can delete that last comment of mine. I did get it to work; don’t know what I forgot! But I find I need to use much different CSS because I need to use text (from the_excerpt) rather than images. And each text block will vary in size, and the CSS and loop in your code doesn’t display an even grid with uneven amounts of text. I’m working with another post grid system. Thanks anyway, Mark
julia says
Hi,
I added something so that the gridlooppage doesn’t look wierd on a mobile responsive theme (genesis):
remove_action( ‘genesis_meta’, ‘prose_add_viewport_meta_tag’ );
But would it be possible to make it specific for the grid loop category so the other pages would have the mobile responsive design? Where in the code should this line then be placed?
Bill Erickson says
I recommend you update your responsive CSS so that the grid loop looks good on mobile devices, and all pages of your site look the same.
But to address your question about how to remove that action only if the grid loop is being used, try this code: https://gist.github.com/4024820
julia says
Thank you very much.
Tom Beck says
Bill,
I noticed the WP-Rotator is not longer being updated. What responsive slider do you recommend?
Thanks
Bill Erickson says
The Genesis Responsive Slider works pretty well. But for almost every client site I build, I end up building a custom rotator specifically for their needs, based on Flex Slider.
Tom Beck says
Oh, so you put the html code in the editor in lieu of using a featured image?
Bill Erickson says
No, I don’t typically place HTML in the editor. The approach I take depends on their needs. Sometimes I’ll create a ‘rotator’ post type if they have a lot of information for each slide (image, title, subtitle, link, button…). Sometimes I’ll add a checkbox to the media uploader “Include in Rotator” and build a rotator with all images attached to the current page marked that. And sometimes I’ll create a metabox for them to manage the rotator.
Thomas Bock says
Bill,
I understand, Thanks!
Ian says
Hey Bill,
This is a fantastic tutorial. I know enough to be dangerous so this has really helped.
One thing I’m trying to figure out but can’t so far…
How do I change the class of the featured and teaser images from alignleft to aligncenter? I’m using the Genesis Sandbox theme which means I changed the one-third class to fourcol and it is wrapping the titles around the image, but if I change the class to aligncenter in Chrome, it works.
Thanks for your time, and your epic resources on how to bend Genesis to your own will! 🙂
Ian
Bill Erickson says
Genesis hardcodes the ‘alignleft’ class in there, so the best thing to do is unhook the Genesis Post Image function and rebuild it as needed. I pulled the code from /lib/functions/post.php. I changed it to check to see if ‘one-third’ is in the current post class; if it is, use aligncenter; if it isn’t, use alignleft.
https://gist.github.com/4059773
Ian says
Thanks for this bill, it worked perfectly for what I needed.
I also wanted to ask, you suggested the following snippet to show only posts from a specific category on the homepage:
/**
* Limit homepage to one category
*
*/
function be_home_query( $query ) {
if( $query->is_main_query() && $query->is_home() && !is_admin() )
$query->set( ‘category_name’, ‘sample-category’ );
}
add_action( ‘pre_get_posts’, ‘be_home_query’ );
When I implement this, the featured post image size is the same as the grid image size. Is that normal behavior? And if so, how would one go about amending it to the larger image size?
Thanks again for all your work on this. It never ceases to amaze me how skilled you are!
Cheers,
Ian
Bill Erickson says
No, modifying the main query shouldn’t affect the image size. I’d need to see all the code to know what’s going wrong though.
Ian says
Strange that it’s doing that then. Obviously I have broken it somewhere.
Here is my code: https://gist.github.com/4203459
Thanks for any help you can give me 🙂
Cheers,
Ian
Alex says
I cheated and used the plugin.
Big thanks for it and this code. Am hoping to see higher page views because of grid archive presentation.
Genesis rocks!
Cheers from Italy,
Alex
Mike Dutton says
Massive thanks for all this.
I have learnt such a lot from your posts.
I have a quick question about how to deal with targeting just one category.
Looking at the section starting “Sections of site that should use grid loop”
I am changing is_home() to is_category() which works to target categories and not home
but when I then try to specify a category as “is_category(4) ” or using the category name or slug
I can’t seem to target one particular category.
As an aside to this what do you think the best way to apply different grids to different categories is.
Thank you again for your tutorials.Mike.
Bill Erickson says
All of the options for is_category() should work. See here: http://codex.wordpress.org/Conditional_Tags#A_Category_Page
If you want to apply different grids to different categories, I recommend you use the plugin and then the filters to customize. Set the default settings in Genesis > Grid Loop, then use the ‘genesis_grid_loop_args’ filter to modify it for certain sections. See the second code snippet here: https://github.com/billerickson/Genesis-Grid/wiki
Mike Dutton says
Hi Bill,
That does the trick, thanks.
Mike.
Amy says
Hi Bill,
Thanks for this code and the plugin as well. I works great but I am really stuck on how to style my text in the teasers. All my h tags are ignored as well as the strong tag.
Thanks for the help.
Bill Erickson says
HTML tags are stripped out of excerpts. If you want to use markup, use “Show full content” in Genesis > Theme Settings and place the more tag where you want it to end. More info: http://en.support.wordpress.com/splitting-content/more-tag/
Amy says
Thanks for the quick replay! I didn’t realize that HTML tags were stripped out of excerpts. I corrected it.
Thanks – the Genesis Grid plugin works great!
JonBreitenbucher says
What if you want to use some of the features of the grid loop like feature_content_limit and grid_content_limit rather than the excerpt or full post?
Amy says
Following up on my comment, the html tags are being output as text as seen below:
p>Services: Detailing, Fabrication, Joist & Deck, Sound Wall, Safety Line
I am sure I am missing something – the text should be able to be styled.
Thanks,
Amy
Bill Erickson says
(my comments allow HTML so you should break it up before commenting, ex: < strong > )
What you’re seeing is how WordPress is designed to work. See the Codex: Also in the latter case, HTML tags and graphics are stripped from the excerpt’s content.
If you display full post content and use the more tag, HTML tags will NOT be stripped out.
Amy says
Hi Bill,
There is the word “grid” just below the title and above the image when displayed in a grid.
I made the following changes to remove the links from the title and image as all there is no need to click through to another page.
I added this code to my functions.php file:
https://gist.github.com/4693055
Thanks for the insight you may have to remove the pesky “grid” word!
~Amy
Bill Erickson says
The code you provided did not contain the word ‘grid’ so it’s not responsible for that. Search through your whole functions.php to see if you can find the word ‘grid’ anywhere.
Amy says
I took a look through my functions file and revised the line that brings the image back in. The new section of code looks like this:
https://gist.github.com/4694648
Thanks for your help! It looks great now on the archive page with no title or image links to a single post.
~Amy
Dorian Speed says
Bill, thanks so much for this detailed code. I’ve used it on a couple of sites already. I may be missing something, but is there a way to assign different character limits to the features and to the teasers?
Bill Erickson says
If you’re using the plugin version of this code, you can add this to your theme’s functions.php to change the limits: https://www.billerickson.net/code/genesis-grid-change-length-of-content-in-grid/
This does it based on words. For characters, use wp_html_excerpt() instead of wp_trim_words(). More info: https://www.billerickson.net/code/truncate-by-word-or-character/
Dorian Speed says
Thanks – I tried that yesterday, but I’m not using the plugin. I’ve added CPTs to the grid loop so I didn’t think that would work with the plugin – maybe it can, though, and I can go back and fiddle with that. But what I’m wondering is what code to use if I’m just using a version of what you’ve posted here.
I have a feeling it just requires changing the filter, maybe? Wasn’t sure what the filter should be, though. Because I think what happened when I tried this was that it looked at the filter and said, “well, nothing on the site is doing the Genesis grid loop, so I guess I don’t need to do anything.” I wasn’t sure how to specify “if it is doing the grid loop on the page” if it’s not specifically the Genesis grid loop.
Bill Erickson says
Try this instead: https://gist.github.com/billerickson/4947529
The filter in the previous link doesn’t exist in the advanced example above, only the plugin.
You can use the plugin with custom post types by using the ‘genesis_grid_loop_sections’ filter ( https://www.billerickson.net/code/limit-genesis-grid-to-specific-category/ ). I prefer to use the plugin rather than the advanced example above to keep the theme simpler.