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 );
Greg says
The code in the “advanced example” does not seem to apply to the built in Genesis blog page template . Any hints to make the teasers and features settings apply to that as well?
Thx,
Greg
Bill Erickson says
Don’t use the blog page template 🙂
The blog page template removes the default loop and replaces it with its own. The only way to make it apply is to delete everything in that page template, or just don’t use it.
There’s no reason for using that template to begin with. The only reason it is in Genesis still is for legacy support.
If you want your blog on a page other than the front page, do the following:
– Go to Pages > Add New and create a page called Home and one called Blog
– Go to Settings > Reading, set the front page to “Home” and the posts page to “Blog”
Greg says
Thank you Bill, I sincerely appreciate your reply!
Greg says
Hello again,
As the page_blog.php file is part of the core Genesis framework and can’t be removed, is there a way to make it display the default loop instead of it being replaced?
I’m thinking of a circumstance where a child theme includes a home.php file. Users of child themes appear to be accustomed to the home page just working with out choosing a home page in the reading settings. Then they create their blog page with the built in template.
While this may not be ideal and it is advisable to not use page_blog.php altogether, if there was a way to make the template just load the default loop, then there would not be a need for special user instructions.
Am I making sense?
Thanks,
Greg
Bill Erickson says
You can create a file in your child theme named page_blog.php, and it will replace the one in Genesis. But keep in mind you’ll need to build a custom loop in there. The code described above is designed for modifying the main query (the proper approach). You’ll need to do this on a custom query.
A better approach would be to rename home.php to front-page.php (as it should’ve been done in the first place). Then you can create a Home and Blog page, set them up in Settings > Reading, and the main blog loop will be used on the Blog page.
Greg says
Thanks again. I’d like to do things the proper way when advised so I’ll get this working with the with front-page.php and the reading settings.
I get so used to doing things the StudioPress child theme way that sometimes I don’t realize that their approach out of the box is not always what is most recommended.
Take care,
Greg
Rod says
Hi Bill,
Grid post layout looks fantastic. I’m trying to do something like this for archive pages (search, tags, categories) on my website using WordPress with slightly customised Twenty Twelve theme (changing css, child theme, layout) . Being new to WordPress coding I tried following instructions, but grid does not seem to work. I guess it will only work with Genesis out of the box? Any hints on how this could work with default WordPress theme?
Thanks in advance
Bill Erickson says
If you add the column classes to your CSS file you should be able to use it in any theme.
Try creating a page with the following content (switch to HTML view in WordPress, then paste): https://gist.github.com/billerickson/4978875
If that works, it means you’ve set up the column classes in your CSS correctly
T.Marquez says
I’ve been trying to implement the “Advanced Example” in my functions.php and have set my “Blog” in Reading Settings, however when I load the Blog page, nothing appears. I’m not quite sure what I’m missing. I did see any posts above we are not to use page_blog.php as a template to make this work (though I could have misunderstood that). I’m not seeing the results of what I put into the functions.php on any of my pages (archives, etc.).
Bill Erickson says
Have you tried using the plugin listed at the top of the post?
I’m sorry but I can’t troubleshoot this issue with the information you’ve provided.
T.Marquez says
I have the plugin installed too, but no luck for our web site. Here is what our blog page looks like: http://www.miscindiana.com/newblog/
Bill Erickson says
Based on the body classes of that page, it looks like you haven’t set up that page as the Posts page in Settings > Reading. It is just a standard page right now.
T.Marquez says
Sorry, I was troubleshooting. The Settings > Reading Posts Page is actually: http://www.miscindiana.com/2013blog/
I created a page called “2013BLOG” and it is assigned the “Default Template”. It just shows the main page.
Bill Erickson says
Does your theme have a home.php? If so, rename it to something else.
T.Marquez says
Wow, I had no clue having the default home.php template in the Enterprise1.1 theme I use would cause me this many hours of grief. I won’t pretend to understand why this was causing our issues, though I’m a curious. Anyway, you should have my email address. I’ll assume you have a PayPal, send me your info and I will compensate you for your time answering my clueless posts. Thanks!
Bill Erickson says
The home.php file is used for styling the main posts page. It’s named this way because it was created before you could have your main posts page on other pages.
Despite my many complaints, StudioPress still builds their homepages using home.php, rather than the correct theme file: front-page.php. Any time I use a StudioPress theme I rename their home.php to front-page.php.
This is why Genesis has that blog page template – because the way StudioPress uses home.php means you can’t have a blog the normal WordPress way.
My email is [email protected]. Thanks!
Greg says
But if StudioPress didn’t use home.php that way, then for their users to get the demo’d home page, they would have to create a blank page and set it as the front page in the reading settings, correct? Is this perceived as too many steps/too confusing and perhaps why they won’t make the change to do it the WordPress way?
Bill Erickson says
That’s exactly why they do it. They think doing it the WordPress way would result in more support requests, which is probably true. But by making it slightly easier for users who do want it done the StudioPress way, they make it very difficult for users who want it the WordPress way (you have to edit theme files)
It’s too late for them to change it now since all their current themes and support documents reflect this way. But I like to bring it up to point out that just because a large company does it one way, doesn’t mean it’s the right way. When considering what template to use, consult the code and Codex.
Dorian Speed says
I didn’t realize this because I have cut my teeth on the Genesis framework. Would it be advisable to go back and duplicate the home.php files on various sites I have built, save each duplicated file as front-page.php, and change the settings accordingly? Or should I not bother but just make sure to do it right going forward?
Bill Erickson says
I wouldn’t bother going back and changing anything. It works, so no need to mess with it.
It’s just a good idea going forward to name your theme files according to how WordPress will use them.
Do you want this file to be used on the front page? Then use front-page.php (example: a bunch of widget areas, like most of StudioPress’ homepages)
Do you want this file to be used for displaying blog posts, regardless of where the blog is? Use home.php
Greg says
It is what it is, but would it make sense for WordPress to change how they do it, so a theme developer could force a front page layout without the user having to create a blank or “dummy” home page?
Bill Erickson says
A similar feature was actually slated for 3.5. When in Settings > Reading, you could have WordPress create the page your blog was going onto (one less step). I wasn’t following it closely and was surprised when I didn’t see it in 3.5. I dug up the ticket and it was pushed for usability reasons (couldn’t figure out the easiest way to do it).
I don’t think it’s a good idea to allow themes to force anything. But if you really wanted to, you could code your theme so that on activation it created a Home and Blog page, and then updated the database options to use Home on front and Blog for posts. I think WooThemes’ support plugin does something similar to this.
Michelle says
Hi Bill,
I wanted to thank you so much for posting this. It has been a huge help for me as someone that is pretty much a novice when it comes to coding such things. I also have a question if you don’t mind.
I am building a test site at kwerkykats.com with the Magazine Genesis theme. I am using the Genesis Featured Widget Amplified on the homepage to be able to setup the posts in a grid and show only the image and post title. I was wondering if there is a way to alter the grid coding to do the same for the archive pages? The archives currently show the number of comments and what the post is filed under and tagged with (I already added your coding to get rid of the excerpt) and I’d like to get rid of that if possible, leaving just the image and post title as on the homepage, but have no idea where to start.
Thank you so much again!
Bill Erickson says
Yes you can. Just hook in before the post is displayed and check the post classes to make changes.
Let’s say on the grid posts you want to remove the post info, post meta, and post content/excerpt. Something like this should do it: https://gist.github.com/billerickson/5021632
That code snippet assumes your grid posts have a class of ‘one-third’. If you’ve broken them into two columns, you’d use ‘one-half’.
Michelle says
Thanks, Bill! This worked perfectly.
Hina says
Hello Bill,
I am using the plugin and I need the exact functionality as per Michelle but unable to get it by above code snippet. Are there any modifications for the above code snippet for plugin users
Please ignore my earlier comment where I posted the same problem as Michelle did.
Thanks
Hina
Danielle says
Hi Bill, I have installed your plugin because I desperately want to display posts like this. My question is, is there any way to change the information displayed, without editing the plugin? I’m trying to build a visual archive, and what I’d really like to do is have the “teasers” display the featured image linked to the post, and NOTHING else (no title, no post meta, no excerpt or content.) I can even do without the “featured” part, if that’s easier.
Is there a way to accomplish this with the plugin, or should I give up and start working on grid loop code?
Thanks for your help!
Bill Erickson says
Yes you can. This works exactly the same as the previous comment, except you also need to include remove_action( ‘genesis_post_title’, ‘genesis_do_post_title’ );
Example: https://gist.github.com/billerickson/5021632
Danielle says
Thanks so much! That’s just what I want to do here (sorry about the 404 on my earlier comment, left off the end.)
My next question is, I set up a page using the blog template, to display one category only (the non-visual-archive category). It seems no matter what settings I tweak, it too is displaying in the grid – is there an easy way to exclude just one category from the grid display? I didn’t see a way on the plugin settings.
Bill Erickson says
Don’t use the blog page template. The proper way of doing this is modifying the main WordPress query (the template is still in Genesis for legacy reasons).
1. Set the page template to “Default”.
2. Go to Settings > Reading and set “Blog” as the Posts page.
3. If the blog doesn’t show up, your theme is probably using home.php for the homepage instead of front-page.php. Look in your theme and, if you see home.php, rename it to front-page.php.
4. Now Blog will list all posts. You want to limit it to a specific category. Add this code to your theme’s functions.php file (without the opening https://gist.github.com/billerickson/5021680
Danielle says
I tried the code snippet to remove info from the grid loop, but it didn’t work. Here’s the code I put in:
https://gist.github.com/anonymous/a3fd877c3aee0c882d1d
And here’s the result (no change.)
What did I do wrong?
Danielle says
Hi again Bill,
I’m still unable to get these adjustments working properly (I commented a while ago but it was moderated and never appeared.)
Basically, following the functions.php edits above, if I leave it as:
if( !in_array( ‘one-third’ …
nothing is removed.
If I change it to:
if( in_array( ‘one-third’ …
The title, content, meta and info are removed as desired, but for some reason they are also removed on the single post display. (No title, content, image, etc is displayed.)
What am I doing wrong?
Danielle says
Never mind, I changed the class to ‘one-third teaser’ and that fixed things right up. Thanks again for your help!
Richard Buff says
Bill, Earlier in these comments you said you use a custom Flexslider for all clients. I recently saw you mention Soliloquy on your post about consulting businesses. On the Soliloquy site is a quote from you “I used to custom build rotators for every project since I hadn’t found a tool with the flexibility I needed. Now I have Soliloquy. It works great out of the box, has an easy-to-use interface, and gives you the power to customize everything with hooks and filters.” Do you still use Flexslider or has Soliloquy met all your recent needs? I’m thinking about purchasing a Soliloquy developer license.
Bill Erickson says
I use Soliloquy on a lot of projects where the client wants the ability to build his own rotators. But in the instances where I want to keep the backend as simple as possible (and not flexible, so the client doesn’t break it) I’ll often build my own slider.
For instance, the homepage might have a rotator on it. I’ll create a metabox that shows up only on the homepage and lets them upload as many images as they want. I’ll then use WordPress to scale/crop to right dimensions, and then render as a flexslider.
I’m sure I could use Soliloquy for these as well, but I’m used to my ways 🙂
Richard Buff says
Hopefully I’m not too much of a bother with this followup, but what about carousels? Particularly a carousel with custom post types? You ever have to custom build those? I’m about to need a carousel to allow left and right scrolling of Community thumbnails and titles, and I thought about trying to custom build one using the caroufredsel jquery script. It looks to me like you may have done exactly that on the bottom of this page: http://www.dcsimedispa.com/ . If so, is that something you’d recommend still? ( I also just discovered that Soliloquy apparently supports custom post types and has a carousel addon.)
Bill Erickson says
For jQuery rotators that are more complex than a simple image (flexslider), I hire a jQuery developer to implement it. That’s what I did for DCSI
Bruce Munson says
Hi Bill,
I’m using your ‘Advanced Example’ with class of one-half instead of one-third. Works great except for a conflict with the Genesis Featured Widget Amplified plugin.
I have the GFWA set up like so http://screencast.com/t/ObJ76IDy to display two posts in a subfooter widget area I created. When your code is used, it adds ‘one-half first’ to the class of the second post (not the first) like so http://screencast.com/t/pjghDodUDE. This obviously causes a problem in display (http://screencast.com/t/clISQFmEhpPn).
I tried to find the problem in the code, but haven’t been able to crack it. Any ideas why it is adding the extra classes to the GFWA markup?
Thanks much!
The code: http://pastebin.com/VLWFWKPv
Grumpyfoot says
Never mind my last comment. I found the solution in the comments above using https://gist.github.com/billerickson/2046892/raw/4bb66bb71cf4261b91a5a6030e763bb1f8bccf67/gistfile1.css
Let me know if I’m doing it wrong.
Kearin says
I was looking at your blog you mentioned, one thing I am trying to do is add a separator after 3 posts so I can create nice even rows, I noticed you have after each 2 posts on your blog, I did not see how to do this in the code above, would you be able to please share with us.
Bill Erickson says
Take a look at the Building My Portfolio post. Specifically, the be_project_divider() function.
Yours would be simpler than that since I only wanted to exclude the line after the 6th post since I had testimonials there.
Kearin says
Will do thank you Sir!