2026年11月13日
2026年11月13日
Switch WordPress Post Lists Between 1, 2 and 3 Columns with CSS Grid
This article is a localized edition of the Japanese original. Read the Japanese original
To let readers switch your WordPress post list between 1, 2 and 3 columns with buttons, apply CSS Grid to the element that wraps the list and switch the column count with a data-columns attribute. If the JavaScript only rewrites that attribute and CSS decides all of the appearance, the result is hard to break and easy to fix.
The code in this article uses only functions available in WordPress 6.9 as of November 2026, plus CSS and JavaScript that work in major browsers. It assumes you are adding it to a child theme of a classic theme (a theme built from PHP template files).
Check first: a fixed column count can be set in the editor
If readers do not need to switch, and you only want to "show the list in 3 columns", you do not need any code. In a block theme, open the list template in the Site Editor, select the "Post Template" block inside the "Query Loop", and choose "Grid view" in the toolbar; you can then set the number of columns.
This article shows how to build it when you want readers to choose the layout.
Overall structure
| File | Role |
|---|---|
functions.php |
Loads the CSS and JavaScript only on list pages |
index.php (or archive.php) |
Outputs the switch buttons and the list with a data-columns attribute |
css/layout-switch.css |
Sets the column count for each data-columns value |
js/layout-switch.js |
Rewrites data-columns when a button is pressed and saves the choice |
Step 1: Set the column count in CSS
Create css/layout-switch.css in your child theme folder.
.post-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.post-grid[data-columns="1"] {
grid-template-columns: minmax(0, 1fr);
}
.post-grid[data-columns="2"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.post-grid[data-columns="3"] {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.post-grid img {
width: 100%;
height: auto;
}
.layout-switch {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.layout-switch button[aria-pressed="true"] {
font-weight: bold;
text-decoration: underline;
}
/* On smartphones, always use 1 column and hide the switch buttons */
@media (max-width: 781px) {
.post-grid[data-columns] {
grid-template-columns: minmax(0, 1fr);
}
.layout-switch {
display: none;
}
}
There are two key points.
- Using
minmax(0, 1fr)instead of1frkeeps the columns evenly wide even when there are long URLs or large images. With1fralone, a column cannot shrink below its content's minimum width, and column widths can become uneven. - The column count is switched with a
data-columnsattribute on the list element itself. You could also add a class to a parent element and target it with a descendant selector, but if the element the JavaScript adds the class to and the CSS selector get out of sync, it stops working; keeping both on the same element's attribute makes mistakes less likely.
Step 2: Write the switch script
Create js/layout-switch.js in your child theme folder.
( function () {
var grid = document.querySelector( '.post-grid' );
var buttons = document.querySelectorAll( '.layout-switch [data-columns]' );
if ( ! grid || ! buttons.length ) {
return;
}
var STORAGE_KEY = 'mytheme-columns';
function apply( columns ) {
grid.setAttribute( 'data-columns', columns );
buttons.forEach( function ( button ) {
var pressed = button.getAttribute( 'data-columns' ) === columns;
button.setAttribute( 'aria-pressed', pressed ? 'true' : 'false' );
} );
}
// Use the previously chosen column count if there is one (ignored where storage is unavailable)
var saved = null;
try {
saved = window.localStorage.getItem( STORAGE_KEY );
} catch ( e ) {}
if ( saved === '1' || saved === '2' || saved === '3' ) {
apply( saved );
}
buttons.forEach( function ( button ) {
button.addEventListener( 'click', function () {
var columns = button.getAttribute( 'data-columns' );
apply( columns );
try {
window.localStorage.setItem( STORAGE_KEY, columns );
} catch ( e ) {}
} );
} );
} )();
- The chosen column count is saved in the browser's
localStorage, so the same layout is shown the next time the page opens. It is wrapped intryso that it does not stop with an error where saving is unavailable, such as in private browsing. aria-pressedis an attribute that tells screen readers whether a button is in the pressed state. The CSS also uses it to change the look of the pressed button.
Step 3: Load them in functions.php
Do not write <link> or <script> directly in the template; load CSS and JavaScript with the wp_enqueue_scripts hook. Add this to your child theme's functions.php.
<?php
// Add to your child theme's functions.php
add_action( 'wp_enqueue_scripts', 'mytheme_layout_switch_assets' );
function mytheme_layout_switch_assets() {
// Load only on post lists (blog home, archives, search results)
if ( ! is_home() && ! is_archive() && ! is_search() ) {
return;
}
$dir = get_stylesheet_directory_uri(); // For a parent theme, use get_template_directory_uri()
wp_enqueue_style( 'mytheme-layout-switch', $dir . '/css/layout-switch.css', array(), '1.0.0' );
wp_enqueue_script(
'mytheme-layout-switch',
$dir . '/js/layout-switch.js',
array(),
'1.0.0',
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
Passing an array as the fifth argument of wp_enqueue_script() (specifying defer with strategy) is available from WordPress 6.3. A script with defer runs after the HTML has been parsed, so the script never fails to find the list element.
Add the code after the existing contents of your child theme's functions.php. The file already starts with <?php, so do not write a second one. The loading mechanism is explained in detail in How to load JS and CSS with wp_enqueue_scripts in WordPress.
Step 4: Write the list and buttons in the template
Copy the template that outputs your post list (index.php in many themes, archive.php for category and other lists) from the parent theme to the child theme and edit it. Write the list part like this.
<?php get_header(); ?>
<main id="main" class="site-main">
<div class="layout-switch" role="group" aria-label="Number of columns in the list">
<button type="button" data-columns="1" aria-pressed="false">1 column</button>
<button type="button" data-columns="2" aria-pressed="false">2 columns</button>
<button type="button" data-columns="3" aria-pressed="true">3 columns</button>
</div>
<?php if ( have_posts() ) : ?>
<div class="post-grid" data-columns="3">
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'post-grid__item' ); ?>>
<a href="<?php the_permalink(); ?>">
<?php if ( has_post_thumbnail() ) : ?>
<?php the_post_thumbnail( 'medium_large' ); ?>
<?php endif; ?>
<h2 class="post-grid__title"><?php the_title(); ?></h2>
</a>
<?php the_excerpt(); ?>
</article>
<?php endwhile; ?>
</div>
<?php the_posts_pagination(); ?>
<?php else : ?>
<p>No posts found.</p>
<?php endif; ?>
</main>
<?php get_footer(); ?>
- The initial column count is set by
data-columns="3"on the list andaria-pressed="true"on the 3-column button. To make 2 columns the default, change both together. - Match the
idand class names ofmainto your theme's original template. The sidebar and content width are set by the theme's CSS, so it is safest to keep the outer structure of the original template and replace only the list part. - Because the buttons are
<button type="button">, readers can also move to them with the Tab key and switch with Enter or Space.
Step 5: Check the result
- Open a list page and check that pressing the buttons changes the column count.
- Reload the page and check that it stays at the column count you chose.
- Narrow the browser to 781px or less (the device mode in the developer tools also works) and check that it becomes 1 column and the buttons disappear.
If it does not switch, check the following in the developer tools.
- In "Network", check that
layout-switch.cssandlayout-switch.jsload with status 200. If you get 404, review where the files are and whether you usedget_stylesheet_directory_uri()(child theme) orget_template_directory_uri()(parent theme). - In "Elements", check whether
data-columnson.post-gridchanges when you press a button. If it changes, the problem is on the CSS side; if not, it is on the JavaScript side. - If you use a caching plugin or a CDN, clear the cache before checking. When you fix the CSS or JavaScript, raising the version (
1.0.0) inwp_enqueue_style()andwp_enqueue_script()makes it less likely that browsers keep the old file.
Related articles
- How to use CSS Grid layout in WordPress
- How to add CSS to a WordPress child theme
- How to load JS and CSS with wp_enqueue_scripts in WordPress
References: wp_enqueue_script() (WordPress Developer Resources), grid-template-columns (MDN), aria-pressed (MDN)
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)