SWELLで、カテゴリーの選択ボタンと検索結果を別々の場所に置く方法です。メニューをサイドバー、結果を本文に配置できます。この記事では、公開済みの通常投稿をカテゴリーで絞り込み、6件ずつ表示します。
複数のカテゴリーを同時に選ぶ方式や、タグとの組み合わせが必要な場合は、複数条件の絞り込み検索を参照してください。本記事の方式はカテゴリーを1つ選ぶ用途です。親カテゴリーを選ぶと子カテゴリーの記事も含みます。
動く見本:カテゴリーを選んで記事を探す
「すべて」で条件を解除できます。「次へ」で次の6件を表示します。下の見本ではメニューと結果を近くに置いていますが、設置時にはそれぞれ別のショートコードブロックへ配置できます。
設置前に確認すること
対象はSWELLの投稿カード表示です。他のテーマでは検索処理を利用できても、カード用CSSの調整が必要です。カスタム投稿・タグ・独自タクソノミーはこのサンプルの対象外です。検索結果は公開・パスワードなしの記事に限定します。
まず検証用サイトで試してください。以下のPHPをCode Snippetsと子テーマの両方に登録するとクラス名が重複するため、登録先は1か所にします。
1.PHPを登録する
Code SnippetsでPHPスニペットを新規作成し、以下を開始タグなしで貼り付けて有効化します。Ajaxの呼び出しにも処理が必要なため「すべての場所で実行」を選びます。子テーマのfunctions.phpへ追加する場合は、既存のPHP領域へ貼り付けます。復元できるよう編集前のファイルを保存してください。
// Code Snippets の PHP スニペットに、開始タグなしで登録します。
class Waza_Category_Filter_2026 {
private static $used = false;
public static function results($category = 0, $page = 1) {
$args = array('post_type' => 'post', 'post_status' => 'publish', 'has_password' => false,
'posts_per_page' => 6, 'paged' => $page, 'ignore_sticky_posts' => true,
'orderby' => array('date' => 'DESC', 'ID' => 'DESC'));
if ($category) $args['cat'] = $category;
$previous_post = $GLOBALS['post'] ?? null;
$q = new WP_Query($args);
// 記事が減って現在のページがなくなった場合は、最後のページへ戻します。
if ($page > 1 && !$q->have_posts()) {
// 範囲外ページでは総件数が得られないため、1ページ目で取り直します。
$args['paged'] = 1;
$q = new WP_Query($args);
$page = max(1, (int) $q->max_num_pages);
if ($page > 1) {
$args['paged'] = $page;
$q = new WP_Query($args);
}
}
ob_start();
echo '<p>' . esc_html($q->found_posts) . '件の公開記事</p>';
if ($q->have_posts()) {
echo '<ul class="p-postList -type-card -pc-col2 -sp-col1">';
while ($q->have_posts()) {
$q->the_post();
echo '<li class="p-postList__item"><a class="p-postList__link" href="' . esc_url(get_permalink()) . '">';
if (has_post_thumbnail()) echo '<div class="p-postList__thumb">' . get_the_post_thumbnail(get_the_ID(), 'medium', array('loading' => 'lazy')) . '</div>';
echo '<div class="p-postList__body"><span class="p-postList__title">' . esc_html(get_the_title()) . '</span></div></a></li>';
}
echo '</ul>';
} else echo '<p>該当する記事がありません。「すべて」に戻してお試しください。</p>';
wp_reset_postdata();
if ($previous_post instanceof WP_Post) {
$GLOBALS['post'] = $previous_post;
setup_postdata($previous_post);
} else {
$GLOBALS['post'] = $previous_post;
}
echo '<nav aria-label="検索結果のページ"><button type="button" data-waza-page="' . max(1, $page - 1) . '" ' . disabled($page <= 1, true, false) . '>前へ</button> ';
echo '<span>' . esc_html($page) . ' / ' . esc_html(max(1, $q->max_num_pages)) . '</span> ';
echo '<button type="button" data-waza-page="' . ($page + 1) . '" ' . disabled($page >= $q->max_num_pages, true, false) . '>次へ</button></nav>';
return ob_get_clean();
}
public static function menu() {
$terms = get_terms(array('taxonomy' => 'category', 'hide_empty' => true));
$out = '<div class="waza-category-menu" aria-label="カテゴリーの絞り込み"><button type="button" data-waza-cat="0" aria-pressed="true">すべて</button> ';
if (!is_wp_error($terms)) foreach ($terms as $term) {
$out .= '<button type="button" data-waza-cat="' . absint($term->term_id) . '" aria-pressed="false">' . esc_html($term->name) . '</button> ';
}
return $out . '</div>';
}
public static function listing() {
self::$used = true;
return '<div id="waza-category-results" data-endpoint="' . esc_url(admin_url('admin-ajax.php')) . '"><p class="waza-category-status" role="status"></p><div class="waza-category-output">' . self::results() . '</div><noscript><p>カテゴリーの切り替えにはJavaScriptを有効にしてください。</p></noscript></div>';
}
public static function ajax() {
// 公開記事の読み取り専用。管理操作・非公開記事の取得には使いません。
$category = isset($_POST['category']) && is_scalar($_POST['category']) ? absint($_POST['category']) : 0;
$page = isset($_POST['page']) && is_scalar($_POST['page']) ? absint($_POST['page']) : 1;
if ($page < 1 || $page > 1000 || ($category && !term_exists($category, 'category'))) {
wp_send_json_error(array('message' => '条件が正しくありません。すべてに戻してください。'), 400);
}
wp_send_json_success(array('html' => self::results($category, $page)));
}
public static function script() { if (!self::$used) return; ?>
<script>
(() => {
const initialize = () => {
const root = document.getElementById('waza-category-results');
if (!root) return;
const output = root.querySelector('.waza-category-output');
const status = root.querySelector('.waza-category-status');
let category = '0', sequence = 0, controller;
async function load(nextCategory, page) {
const request = ++sequence;
if (controller) controller.abort();
controller = new AbortController();
root.setAttribute('aria-busy', 'true');
status.textContent = '記事を読み込んでいます…';
try {
const response = await fetch(root.dataset.endpoint, {method: 'POST', signal: controller.signal,
body: new URLSearchParams({action:'waza_category_filter_2026', category:nextCategory, page:String(page)})});
const data = await response.json();
if (!response.ok || !data.success) throw new Error('request');
if (request !== sequence) return;
category = nextCategory;
output.innerHTML = data.data.html;
document.querySelectorAll('[data-waza-cat]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.wazaCat === category)));
status.textContent = '検索結果を更新しました。' + output.querySelector('p').textContent;
// ページ切り替えで消えたボタンから、結果の先頭へフォーカスを移します。
output.setAttribute('tabindex', '-1');
output.focus({preventScroll:true});
} catch (error) {
if (request === sequence && error.name !== 'AbortError') status.textContent = '読み込めませんでした。表示中の結果は前の条件です。条件を選び直して再試行してください。';
} finally {
if (request === sequence) root.removeAttribute('aria-busy');
}
}
document.querySelectorAll('[data-waza-cat]').forEach(button => button.addEventListener('click', () => load(button.dataset.wazaCat, 1)));
root.addEventListener('click', event => {
const button = event.target.closest('[data-waza-page]');
if (button && !button.disabled) load(category, button.dataset.wazaPage);
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize, {once: true});
} else {
initialize();
}
})();
</script>
<?php }
}
add_shortcode('waza_category_menu', array('Waza_Category_Filter_2026', 'menu'));
add_shortcode('waza_category_results', array('Waza_Category_Filter_2026', 'listing'));
add_action('wp_ajax_waza_category_filter_2026', array('Waza_Category_Filter_2026', 'ajax'));
add_action('wp_ajax_nopriv_waza_category_filter_2026', array('Waza_Category_Filter_2026', 'ajax'));
add_action('wp_footer', array('Waza_Category_Filter_2026', 'script'));
表示中の記事が非公開になって現在のページがなくなった場合は、残っている最後のページを取り直します。
例えば7件の2ページ目を開いている間に1件が非公開になると、6件を表示する1ページ目へ戻ります。
残る公開記事が0件なら、ページ表示は1 / 1になり、前後のボタンを無効にします。
通常の取得は1回ですが、範囲外のページを補正するときは1ページ目と必要な最終ページを追加で取得します。
2.メニューと結果を配置する
メニューを置きたい場所のショートコードブロックに [waza_category_menu]、本文の結果表示位置に [waza_category_results] を入力します。結果表示は1ページに1つだけ設置してください。同じページのメニューは複数設置できますが、同じ1つの結果を操作します。
サイドバーに設置する場合は、対象ページで表示されるウィジェット領域へメニューを追加します。スマートフォンではサイドバーが本文の下へ回る場合があるため、本文上部にもメニューを置くか、本文内だけで操作が完結する配置を選びます。
3.ボタンのCSSを追加する
以下を追加CSSへ貼り付けます。専用クラスとIDに限定しているため、ほかのボタンの見た目は変更しません。jQueryの読み込み設定を変える必要はありません。
.waza-category-menu button, #waza-category-results nav button { min-height:44px; padding:.5em .8em; margin:4px; border:1px solid currentColor; border-radius:4px; background:#fff; color:#222; cursor:pointer; }
.waza-category-menu button[aria-pressed="true"] { background:#222; color:#fff; }
#waza-category-results button:disabled { opacity:.45; cursor:default; }
.waza-category-menu button:focus-visible, #waza-category-results button:focus-visible { outline:3px solid #0969da; outline-offset:2px; }
#waza-category-results .p-postList__thumb img { width:100%; height:auto; }
#waza-category-results[aria-busy="true"] .waza-category-output { opacity:.6; }検索できない・結果が変わらない場合
ショートコードが文字のまま出る場合はPHPの有効化とショートコードの綴りを確認します。読み込み失敗の表示が出る場合は、開発者ツールのネットワークで admin-ajax.php の応答を確認してください。403ならセキュリティ設定、500ならPHPエラーログを調べます。失敗時は直前の結果と選択状態を残します。
0件の場合は「すべて」に戻し、投稿の公開状態・パスワード設定・カテゴリーの割り当てを確認します。コードを変更しても反映されない場合はページキャッシュを削除してください。通信中に別の条件を選ぶと前のリクエストを中止し、古い応答による上書きを防ぎます。
結果の後に置いた記事情報と遅延読み込みを確認する
| 確認する場面 | 期待する結果 |
|---|---|
| 別の投稿ループ内に結果を表示 | 結果の後のタイトル・リンクは呼び出し元の投稿を参照する |
| 結果が0件の場合 | 呼び出し元の投稿情報を変えない |
| JavaScriptを遅延読み込みする場合 | ページの読み込み後に初期化されてもボタンを操作できる |
一覧を作る前の投稿情報を保存し、生成後に呼び出し元へ戻します。別の投稿ループから呼び出した場合も、後続のタイトルやリンクは呼び出し元を参照します。
JavaScriptは文書の読み込みが終わっていればその場で初期化し、読み込み中なら完了後に初期化します。WP Rocketなどの遅延設定を使う場合は、カテゴリー選択・次へ・前へを実際に操作して確認してください。すべての最適化設定との併用を保証するものではありません。
通信に失敗した結果を見分けて再試行する
| 表示状態 | 判断と操作 |
|---|---|
| 読み込み中 | 連打を避け、完了かエラー文を待つ |
| 通信失敗の文が出る | 表示中の記事と選択状態は前の条件 |
| 再試行が成功 | 件数・選択状態が更新され、結果へフォーカスが移る |
通信に失敗すると、前に表示していた記事を残します。
押したカテゴリーの結果へ変わったとは判断せず、状態メッセージと選択状態を確認してください。
2026年10月3日の隔離環境では、検証用の通信先からHTTP 503を返すと、6件の表示記事と「すべて」の選択を維持し、読み込み中の状態を解除してエラー文を表示しました。
通信先を復旧して画面を読み直し、同じカテゴリーをEnterキーで選ぶと、成功メッセージ、更新された選択状態、結果へのフォーカス移動を確認できました。
結果が更新されない場合は、開発者ツールのNetworkで通信先とHTTP状態を確認します。
PHPのコードを追加しただけで、JavaScriptの取得やキャッシュの更新まで確認できたとは限りません。
戻すときはこの機能のPHP登録、設置した2つのショートコード、専用CSSを取り外します。
実際のWP Rocket設定やすべての通信障害の組み合わせは、この検証の対象に含めていません。

仕様と運用上の制約
1回の取得は6件、ページ番号の上限は1000です。JavaScript無効時は最初の6件だけを表示し、条件変更とページ送りは動きません。絞り込み条件はURLへ保存しないため、戻る操作・再読み込み・URL共有による条件の復元には対応しません。検索エンジンへ見せたいカテゴリー別の一覧には、通常のカテゴリーアーカイブも使ってください。
これは公開記事を読む専用のAjax処理で、記事の更新や会員限定情報の取得には使えません。取得件数に上限を設けてもアクセス集中への対策が完了するわけではありません。大規模サイトでは応答時間を測定し、キャッシュや検索機能の専用化を検討してください。
戻す場合はショートコードと追加したCSSを削除してからPHPスニペットを停止します。元の通常投稿一覧へ戻す場合は、SWELLの投稿リストブロックに置き換えます。
実装仕様の参照:WordPress公式:WP_Query。
よくある質問:SWELLで条件を好きな場所に配置する絞り込み検索
この絞り込み検索は、何ができますか?
記事では、SWELLで、カテゴリーの選択ボタンと検索結果を別々の場所に置く方法です。メニューをサイドバー、結果を本文に配置できます。公開済みの通常投稿をカテゴリーで絞り込み、6件ずつ表示します。「すべて」で条件を解除でき、「次へ」で次の6件を表示します。
複数のカテゴリーを同時に選べますか?
記事では、本記事の方式はカテゴリーを1つ選ぶ用途です。親カテゴリーを選ぶと子カテゴリーの記事も含みます。複数のカテゴリーを同時に選ぶ方式や、タグとの組み合わせが必要な場合は、記事内で案内している複数条件の絞り込み検索の記事を参照してください。
どんなサイトで使えますか?
記事では、対象はSWELLの投稿カード表示です。他のテーマでは検索処理を利用できても、カード用CSSの調整が必要です。カスタム投稿・タグ・独自タクソノミーはこのサンプルの対象外で、検索結果は公開・パスワードなしの記事に限定します。
PHPは、どこに登録しますか?
記事では、まず検証用サイトで試し、Code Snippetsと子テーマの両方に登録するとクラス名が重複するため、登録先は1か所にします。Code Snippetsでは開始タグなしでPHPスニペットを新規作成します。メニューと結果は、それぞれ別のショートコードブロックへ配置できます。
スマホでは、どう配置するとよいですか?
記事では、スマートフォンではサイドバーが本文の下へ回る場合があるため、本文上部にもメニューを置くか、本文内だけで操作が完結する配置を選びます。
検索できない・結果が変わらないときは?
記事では、ショートコードが文字のまま出る場合はPHPの有効化とショートコードの綴りを確認します。読み込み失敗の表示が出る場合は、開発者ツールのネットワークでadmin-ajax.phpの応答を確認し、403ならセキュリティ設定、500ならPHPエラーログを調べます。0件の場合は「すべて」に戻し、投稿の公開状態・パスワード設定・カテゴリーの割り当てを確認します。コードを変更しても反映されない場合はページキャッシュを削除してください。
通信に失敗したときや、遅延読み込みを使っている場合は?
記事では、通信に失敗すると、前に表示していた記事を残し、押したカテゴリーの結果へ変わったとは判断しません。再試行が成功すると件数・選択状態が更新され、結果へフォーカスが移ります。WP Rocketなどの遅延設定を使う場合は、カテゴリー選択・次へ・前へを実際に操作して確認してください。すべての最適化設定との併用を保証するものではありません。最新は公式で確認してください。









