Ajax category posts shortcode
August 7, 2025 · AI Conversations
– Displays **product categories (WooCommerce)**
– Shows **posts based on the selected category**
– Loads **default posts of the first category**
– Uses **AJAX to filter posts by category**
– Includes **pagination**
—
### ✅ Step 1: Register the Shortcode
Add the following to your theme’s `functions.php` or a custom plugin:
add_shortcode('ajax_product_category_posts', 'custom_ajax_product_category_posts');
function custom_ajax_product_category_posts() {
ob_start();
$terms = get_terms([
'taxonomy' => 'product_cat',
'hide_empty' => true,
]);
if (empty($terms) || is_wp_error($terms)) return '';
$first_cat_id = $terms[0]->term_id;
echo '<div id="product-category-tabs">';
foreach ($terms as $term) {
echo '<button class="category-tab" data-cat="' . esc_attr($term->term_id) . '">' . esc_html($term->name) . '</button>';
}
echo '</div>';
echo '<div id="product-posts-container" data-category="' . esc_attr($first_cat_id) . '">';
echo '<div class="loading">Loading...</div>';
echo '</div>';
return ob_get_clean();
}
—
### ✅ Step 2: Enqueue JS + Localize Script
Add this to the same file:
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('ajax-category-filter', get_template_directory_uri() . '/js/ajax-category-filter.js', ['jquery'], null, true);
wp_localize_script('ajax-category-filter', 'ajax_filter_params', [
'ajax_url' => admin_url('admin-ajax.php'),
]);
});
> Create a file `/js/ajax-category-filter.js` in your theme. Add JS in step 4 below.
—
### ✅ Step 3: Create the AJAX Handlers
Add this to `functions.php` as well:
add_action('wp_ajax_load_product_category_posts', 'load_product_category_posts_callback');
add_action('wp_ajax_nopriv_load_product_category_posts', 'load_product_category_posts_callback');
function load_product_category_posts_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$args = [
'post_type' => 'product',
'posts_per_page' => 6,
'paged' => $paged,
'tax_query' => [[
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $cat_id,
]],
];
$query = new WP_Query($args);
if ($query->have_posts()) {
echo '<div class="product-list">';
while ($query->have_posts()) {
$query->the_post();
echo '<div class="product-item">';
echo '<a href="' . get_permalink() . '">' . get_the_title() . '</a>';
echo '</div>';
}
echo '</div>';
// Pagination
echo '<div class="ajax-pagination">';
echo paginate_links([
'total' => $query->max_num_pages,
'current' => $paged,
'format' => '',
'prev_text' => '« Prev',
'next_text' => 'Next »',
]);
echo '</div>';
} else {
echo '<p>No products found.</p>';
}
wp_die();
}
—
### ✅ Step 4: Add the JS (`ajax-category-filter.js`)
jQuery(document).ready(function ($) {
function loadCategoryPosts(catId, page = 1) {
$('#product-posts-container').html('<div class="loading">Loading...</div>');
$.post(ajax_filter_params.ajax_url, {
action: 'load_product_category_posts',
category_id: catId,
page: page
}, function (response) {
$('#product-posts-container').html(response);
});
}
// Load default on page load
const defaultCatId = $('#product-posts-container').data('category');
loadCategoryPosts(defaultCatId);
// Category tab click
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
loadCategoryPosts(catId);
});
// Pagination click
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const currentCatId = $('#product-posts-container').data('category');
loadCategoryPosts(currentCatId, page);
});
});
—
### 🧪 Usage
Paste this shortcode where you want the component to appear:
[ajax_product_category_posts]
—
### ✅ Notes
– You can style `.category-tab`, `.product-list`, `.product-item`, and `.ajax-pagination` with your own CSS.
– Adjust `posts_per_page` as needed.
Let me know if you want it to work with **custom post types**, **custom taxonomies**, or add a **grid layout**.
—
### ✅ Step 1: Create the Template File
Create a file at:
/wp-content/themes/your-theme/partials/ajax-product-category-template.php
**`ajax-product-category-template.php`**:
<?php
/**
* @var WP_Term[] $terms
* @var int $first_cat_id
*/
?>
<div id="product-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="product-posts-container" data-category="<?= esc_attr($first_cat_id) ?>">
<div class="loading">Loading...</div>
</div>
—
### ✅ Step 2: Modify the Shortcode Function
Update your shortcode handler in `functions.php`:
add_shortcode('ajax_product_category_posts', 'custom_ajax_product_category_posts');
function custom_ajax_product_category_posts() {
ob_start();
$terms = get_terms([
'taxonomy' => 'product_cat',
'hide_empty' => true,
]);
if (empty($terms) || is_wp_error($terms)) return '';
$first_cat_id = $terms[0]->term_id;
$template_path = locate_template('partials/ajax-product-category-template.php');
if ($template_path) {
include $template_path;
} else {
echo '<p>Template not found.</p>';
}
return ob_get_clean();
}
—
### ✅ Optional: Move AJAX Response Markup to a Separate File
If you'd also like to separate the HTML that’s returned via AJAX (`load_product_category_posts_callback()`), create:
/wp-content/themes/your-theme/partials/ajax-product-category-results.php
**`ajax-product-category-results.php`**:
<?php
/**
* @var WP_Query $query
*/
?>
<div class="product-list">
<?php while ($query->have_posts()) : $query->the_post(); ?>
<div class="product-item">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</div>
<?php endwhile; ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $query->max_num_pages,
'current' => max(1, get_query_var('paged')),
'format' => '',
'prev_text' => '« Prev',
'next_text' => 'Next »',
]);
?>
</div>
Then update your AJAX handler:
function load_product_category_posts_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$args = [
'post_type' => 'product',
'posts_per_page' => 6,
'paged' => $paged,
'tax_query' => [[
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $cat_id,
]],
];
$query = new WP_Query($args);
if ($query->have_posts()) {
$template_path = locate_template('partials/ajax-product-category-results.php');
if ($template_path) {
include $template_path;
} else {
echo '<p>Results template not found.</p>';
}
} else {
echo '<p>No products found.</p>';
}
wp_die();
}
—
Let me know if you'd like me to structure this into a mini plugin or if you're using a child theme and want to override these templates from there.
—
## ✅ Final Directory Structure
/wp-content/themes/your-child-theme/
│
├── inc/
│ └── shortcodes/
│ └── ajax-product-category-posts.php
│
├── partials/
│ ├── ajax-product-category-template.php
│ └── ajax-product-category-results.php
│
├── js/
│ └── ajax-category-filter.js
│
├── functions.php
—
## ✅ Step 1: Register the Shortcode in `functions.php`
Add this to your child theme's `functions.php`:
require_once get_stylesheet_directory() . '/inc/shortcodes/ajax-product-category-posts.php';
—
## ✅ Step 2: Create the Shortcode File
**`/inc/shortcodes/ajax-product-category-posts.php`**:
<?php
add_shortcode('ajax_product_category_posts', 'custom_ajax_product_category_posts');
function custom_ajax_product_category_posts() {
ob_start();
$terms = get_terms([
'taxonomy' => 'product_cat',
'hide_empty' => true,
]);
if (empty($terms) || is_wp_error($terms)) return '';
$first_cat_id = $terms[0]->term_id;
$template_path = get_stylesheet_directory() . '/partials/ajax-product-category-template.php';
if (file_exists($template_path)) {
include $template_path;
} else {
echo '<p>Template not found.</p>';
}
return ob_get_clean();
}
// Enqueue script
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('ajax-category-filter', get_stylesheet_directory_uri() . '/js/ajax-category-filter.js', ['jquery'], null, true);
wp_localize_script('ajax-category-filter', 'ajax_filter_params', [
'ajax_url' => admin_url('admin-ajax.php'),
]);
});
// AJAX handler
add_action('wp_ajax_load_product_category_posts', 'load_product_category_posts_callback');
add_action('wp_ajax_nopriv_load_product_category_posts', 'load_product_category_posts_callback');
function load_product_category_posts_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$args = [
'post_type' => 'product',
'posts_per_page' => 6,
'paged' => $paged,
'tax_query' => [[
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $cat_id,
]],
];
$query = new WP_Query($args);
if ($query->have_posts()) {
$template_path = get_stylesheet_directory() . '/partials/ajax-product-category-results.php';
if (file_exists($template_path)) {
include $template_path;
} else {
echo '<p>Results template not found.</p>';
}
} else {
echo '<p>No products found.</p>';
}
wp_die();
}
—
## ✅ Step 3: Create the Template Files
**`/partials/ajax-product-category-template.php`**:
<div id="product-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="product-posts-container" data-category="<?= esc_attr($first_cat_id) ?>">
<div class="loading">Loading...</div>
</div>
**`/partials/ajax-product-category-results.php`**:
<div class="product-list">
<?php while ($query->have_posts()) : $query->the_post(); ?>
<div class="product-item">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</div>
<?php endwhile; ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $query->max_num_pages,
'current' => max(1, intval($_POST['page'] ?? 1)),
'format' => '',
'prev_text' => '« Prev',
'next_text' => 'Next »',
]);
?>
</div>
—
## ✅ Step 4: JS File
**`/js/ajax-category-filter.js`**:
jQuery(document).ready(function ($) {
function loadCategoryPosts(catId, page = 1) {
$('#product-posts-container').html('<div class="loading">Loading...</div>');
$.post(ajax_filter_params.ajax_url, {
action: 'load_product_category_posts',
category_id: catId,
page: page
}, function (response) {
$('#product-posts-container').html(response);
$('#product-posts-container').data('category', catId); // Update for pagination
});
}
// Load default
const defaultCatId = $('#product-posts-container').data('category');
loadCategoryPosts(defaultCatId);
// On tab click
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
loadCategoryPosts(catId);
});
// On pagination click
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const catId = $('#product-posts-container').data('category');
loadCategoryPosts(catId, page);
});
});
—
### ✅ Use the Shortcode
Place this where you want it to appear:
[ajax_product_category_posts]
—
Let me know if you also want:
– Grid layout for products
– Product images and prices in the result
– Styling with CSS
– Support for other post types or taxonomies
You’re right — my previous code fetches **WooCommerce products**. Since you now want this for **default WordPress posts** and **post categories** (not `product_cat`), I’ll update everything accordingly:
—
## ✅ Updated Objective
> ✅ Show **default WordPress posts**
> ✅ List **default post categories** (`category`) at the top
> ✅ Load posts using **AJAX** on category click
> ✅ Default posts load from the **first category**
> ✅ Include **pagination**
> ✅ Files:
> – Shortcode in `/inc/shortcodes/`
> – Templates in `/partials/`
> – All inside **child theme**
—
## ✅ Step-by-Step Setup
### 📁 1. File: `/inc/shortcodes/ajax-post-category-filter.php`
<?php
add_shortcode('ajax_post_category_filter', 'shortcode_ajax_post_category_filter');
function shortcode_ajax_post_category_filter() {
ob_start();
$terms = get_terms([
'taxonomy' => 'category',
'hide_empty' => true,
]);
if (empty($terms) || is_wp_error($terms)) return '';
$first_cat_id = $terms[0]->term_id;
$template = get_stylesheet_directory() . '/partials/ajax-post-category-template.php';
if (file_exists($template)) {
include $template;
} else {
echo '<p>Template not found.</p>';
}
return ob_get_clean();
}
// Enqueue JS
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('ajax-post-category', get_stylesheet_directory_uri() . '/js/ajax-post-category.js', ['jquery'], null, true);
wp_localize_script('ajax-post-category', 'ajax_post_data', [
'ajax_url' => admin_url('admin-ajax.php'),
]);
});
// AJAX Handlers
add_action('wp_ajax_load_post_category', 'ajax_load_post_category_callback');
add_action('wp_ajax_nopriv_load_post_category', 'ajax_load_post_category_callback');
function ajax_load_post_category_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$args = [
'post_type' => 'post',
'posts_per_page' => 5,
'paged' => $paged,
'cat' => $cat_id,
];
$query = new WP_Query($args);
if ($query->have_posts()) {
$template = get_stylesheet_directory() . '/partials/ajax-post-results.php';
if (file_exists($template)) {
include $template;
} else {
echo '<p>Results template missing.</p>';
}
} else {
echo '<p>No posts found.</p>';
}
wp_die();
}
—
### 📁 2. File: `/partials/ajax-post-category-template.php`
<div id="post-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>">
<div class="loading">Loading...</div>
</div>
—
### 📁 3. File: `/partials/ajax-post-results.php`
<div class="post-list">
<?php while ($query->have_posts()): $query->the_post(); ?>
<div class="post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
</div>
<?php endwhile; ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'format' => '',
'prev_text' => '« Prev',
'next_text' => 'Next »',
]);
?>
</div>
—
### 📁 4. File: `/js/ajax-post-category.js`
jQuery(document).ready(function ($) {
function loadPosts(catId, page = 1) {
$('#post-results-container').html('<div class="loading">Loading...</div>');
$.post(ajax_post_data.ajax_url, {
action: 'load_post_category',
category_id: catId,
page: page
}, function (response) {
$('#post-results-container').html(response);
$('#post-results-container').data('category', catId);
});
}
const defaultCat = $('#post-results-container').data('category');
loadPosts(defaultCat);
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
loadPosts(catId);
});
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const catId = $('#post-results-container').data('category');
loadPosts(catId, page);
});
});
—
### ✅ Add to `functions.php`
In your **child theme’s `functions.php`**:
require_once get_stylesheet_directory() . '/inc/shortcodes/ajax-post-category-filter.php';
—
### ✅ Use the Shortcode
Anywhere in your content:
[ajax_post_category_filter]
—
Would you like me to also:
– Style this using custom CSS?
– Show post thumbnails, dates, or authors?
– Use tabs or dropdown instead of buttons?
Let me know how you'd like to enhance it!
> ✅ By default, show **6 posts from any one category** (e.g. the first category)
> ✅ Without running AJAX initially
> ✅ AJAX should only run **on category tab click or pagination click**
—
Here’s how to adjust your shortcode setup accordingly:
—
## ✅ Step-by-Step Update
### 📁 `/inc/shortcodes/ajax-post-category-filter.php` (UPDATED)
<?php
add_shortcode('ajax_post_category_filter', 'shortcode_ajax_post_category_filter');
function shortcode_ajax_post_category_filter() {
ob_start();
$terms = get_terms([
'taxonomy' => 'category',
'hide_empty' => true,
]);
if (empty($terms) || is_wp_error($terms)) return '';
$first_cat_id = $terms[0]->term_id;
// Get initial posts from first category (no AJAX)
$initial_query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 6,
'cat' => $first_cat_id,
'paged' => 1,
]);
$template = get_stylesheet_directory() . '/partials/ajax-post-category-template.php';
if (file_exists($template)) {
include $template;
} else {
echo '<p>Template not found.</p>';
}
return ob_get_clean();
}
// Enqueue JS
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('ajax-post-category', get_stylesheet_directory_uri() . '/js/ajax-post-category.js', ['jquery'], null, true);
wp_localize_script('ajax-post-category', 'ajax_post_data', [
'ajax_url' => admin_url('admin-ajax.php'),
]);
});
// AJAX Handlers
add_action('wp_ajax_load_post_category', 'ajax_load_post_category_callback');
add_action('wp_ajax_nopriv_load_post_category', 'ajax_load_post_category_callback');
function ajax_load_post_category_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 6,
'paged' => $paged,
'cat' => $cat_id,
]);
if ($query->have_posts()) {
$template = get_stylesheet_directory() . '/partials/ajax-post-results.php';
if (file_exists($template)) {
include $template;
} else {
echo '<p>Results template missing.</p>';
}
} else {
echo '<p>No posts found.</p>';
}
wp_die();
}
—
### 📁 `/partials/ajax-post-category-template.php` (UPDATED)
<div id="post-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>">
<?php
// Load 6 posts initially from $initial_query
if ($initial_query->have_posts()):
include get_stylesheet_directory() . '/partials/ajax-post-results.php';
else:
echo '<p>No posts found.</p>';
endif;
?>
</div>
—
### 📁 `/partials/ajax-post-results.php` (UNCHANGED)
Same as before:
<div class="post-list">
<?php while ($query->have_posts()): $query->the_post(); ?>
<div class="post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
</div>
<?php endwhile; ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'format' => '',
'prev_text' => '« Prev',
'next_text' => 'Next »',
]);
?>
</div>
—
### 📁 `/js/ajax-post-category.js` (UPDATE: remove initial load)
jQuery(document).ready(function ($) {
function loadPosts(catId, page = 1) {
$('#post-results-container').html('<div class="loading">Loading...</div>');
$.post(ajax_post_data.ajax_url, {
action: 'load_post_category',
category_id: catId,
page: page
}, function (response) {
$('#post-results-container').html(response);
$('#post-results-container').data('category', catId);
});
}
// NO default loading here
// On tab click
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
loadPosts(catId);
});
// On pagination click
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const catId = $('#post-results-container').data('category');
loadPosts(catId, page);
});
});
—
### ✅ Done!
– ✅ 6 default posts are shown initially from the first category.
– ✅ AJAX only triggers on tab or pagination click.
Let me know if you want:
– Post thumbnails
– Active tab highlighting
– Grid layout or CSS styling for posts
—
## ✅ Updated Feature List
– Show default 6 posts (non-AJAX)
– Category tabs at top
– Search bar between tabs and posts
– AJAX on:
– Category tab click
– Pagination
– Typing/search submit
—
## ✅ Step-by-Step Changes
### 📁 `/partials/ajax-post-category-template.php` (UPDATE)
Add a search form **after** category buttons:
<div id="post-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="post-search-form">
<input type="text" id="post-search-input" placeholder="Search posts..." />
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>" data-search="">
<?php
if ($initial_query->have_posts()):
include get_stylesheet_directory() . '/partials/ajax-post-results.php';
else:
echo '<p>No posts found.</p>';
endif;
?>
</div>
—
### 📁 `/inc/shortcodes/ajax-post-category-filter.php` (UPDATE AJAX handler)
Add support for search term:
function ajax_load_post_category_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$search = sanitize_text_field($_POST['search'] ?? '');
$args = [
'post_type' => 'post',
'posts_per_page' => 6,
'paged' => $paged,
'cat' => $cat_id,
];
if (!empty($search)) {
$args['s'] = $search;
}
$query = new WP_Query($args);
if ($query->have_posts()) {
$template = get_stylesheet_directory() . '/partials/ajax-post-results.php';
if (file_exists($template)) {
include $template;
} else {
echo '<p>Results template missing.</p>';
}
} else {
echo '<p>No posts found.</p>';
}
wp_die();
}
—
### 📁 `/js/ajax-post-category.js` (UPDATE FULL)
jQuery(document).ready(function ($) {
function loadPosts(catId, page = 1, search = '') {
$('#post-results-container').html('<div class="loading">Loading...</div>');
$.post(ajax_post_data.ajax_url, {
action: 'load_post_category',
category_id: catId,
page: page,
search: search
}, function (response) {
$('#post-results-container').html(response);
$('#post-results-container').data('category', catId);
$('#post-results-container').data('search', search);
});
}
// On category tab click
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
const search = $('#post-search-input').val();
loadPosts(catId, 1, search);
});
// On pagination click
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const catId = $('#post-results-container').data('category');
const search = $('#post-search-input').val();
loadPosts(catId, page, search);
});
// On search input typing (optional debounce)
let timer;
$('#post-search-input').on('keyup', function () {
clearTimeout(timer);
const search = $(this).val();
const catId = $('#post-results-container').data('category');
timer = setTimeout(() => {
loadPosts(catId, 1, search);
}, 500);
});
});
—
## ✅ Output Structure
Now you’ll have:
– 🔼 Category tabs
– 🔍 Search input
– 📰 Posts list
– 🔁 Pagination
– 🎯 Fully AJAX-enabled with default 6 posts preloaded
—
Let me know if you'd like:
– A **search submit button** instead of live typing
– Highlighted keywords in results
– A **"Clear Search"** button or UX improvement
Ready for styling help too if needed!
—
## ✅ Final Setup with Search Button
### 📁 `/partials/ajax-post-category-template.php` (UPDATED)
<div id="post-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="post-search-form">
<form id="post-search-form-element">
<input type="text" id="post-search-input" placeholder="Search posts..." />
<button type="submit" id="post-search-button">Search</button>
</form>
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>" data-search="">
<?php
if ($initial_query->have_posts()):
include get_stylesheet_directory() . '/partials/ajax-post-results.php';
else:
echo '<p>No posts found.</p>';
endif;
?>
</div>
—
### 📁 `/js/ajax-post-category.js` (UPDATED)
jQuery(document).ready(function ($) {
function loadPosts(catId, page = 1, search = '') {
$('#post-results-container').html('<div class="loading">Loading...</div>');
$.post(ajax_post_data.ajax_url, {
action: 'load_post_category',
category_id: catId,
page: page,
search: search
}, function (response) {
$('#post-results-container').html(response);
$('#post-results-container').data('category', catId);
$('#post-results-container').data('search', search);
});
}
// On category tab click
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
const search = $('#post-search-input').val();
loadPosts(catId, 1, search);
});
// On pagination click
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const catId = $('#post-results-container').data('category');
const search = $('#post-search-input').val();
loadPosts(catId, page, search);
});
// On search form submit
$('#post-search-form-element').on('submit', function (e) {
e.preventDefault();
const search = $('#post-search-input').val();
const catId = $('#post-results-container').data('category');
loadPosts(catId, 1, search);
});
});
—
### ✅ What’s Now Happening:
– User clicks **Search** → triggers AJAX filter with search term
– Categories and pagination respect the current search
– No auto-trigger on typing
—
Let me know if you'd like to:
– Add a **reset/clear button** beside the search
– Highlight matching terms in results
– Add **loading spinner** or style enhancements
Want me to drop in sample CSS too?
– The form lacks `action="#"` or uses default behavior
– The script is not correctly preventing default behavior in **admin/editor mode**
—
## ✅ Goal
Ensure the **shortcode stays on the page without navigating away or showing raw content** in the page editor when "Search" is submitted.
—
## ✅ Fix (JavaScript + HTML)
### ✅ 1. Add `action="#"` and `autocomplete="off"` to your form
<form id="post-search-form-element" action="#" autocomplete="off">
### ✅ 2. In your JS, use `e.preventDefault()` and explicitly **return false** to fully block submission
Update this section of `ajax-post-category.js`:
$('#post-search-form-element').on('submit', function (e) {
e.preventDefault();
const search = $('#post-search-input').val();
const catId = $('#post-results-container').data('category');
loadPosts(catId, 1, search);
return false; // <-- Ensure nothing triggers after
});
—
## ✅ Optional Safety (Editor Check)
Add this early in your JS file to skip AJAX if you're inside the admin editor:
if (window.wp && window.wp.data && window.wp.data.select('core/editor')) {
// Probably inside block editor preview — do nothing
return;
}
—
## ✅ Summary
| Fix | Why |
|—–|—–|
| Add `action="#"` to form | Prevent browser from navigating |
| Use `e.preventDefault()` and `return false` | Ensure full suppression of native submit |
| Optionally detect block editor | Avoid running AJAX in admin previews |
—
Let me know if you'd like this shortcode to also be **editor preview safe**, or if you want to **hide the form in the editor** and only show it on the front.
<?php
// Load 6 posts initially from $initial_query
if ($initial_query->have_posts()):
include get_stylesheet_directory() . '/partials/blog-posts-tmpl.php';
else:
echo '<p>No posts found.</p>';
endif;
?>
### ❌ Problem
Duplicated block of code = duplicated output = potential broken markup or unexpected behavior (especially in page editors like Gutenberg).
—
### ✅ Fix
Remove the **second instance** of this block. Your final `ajax-post-category-template.php` should look like this:
<div id="post-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="post-search-form">
<form id="post-search-form-element" action="#" autocomplete="off">
<input type="text" id="post-search-input" placeholder="Search posts..." />
<button type="submit" id="post-search-button">Search</button>
</form>
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>">
<?php
if ($initial_query->have_posts()):
include get_stylesheet_directory() . '/partials/blog-posts-tmpl.php';
else:
echo '<p>No posts found.</p>';
endif;
?>
</div>
—
### 🔍 Additional Tips
– Always inspect for duplicate `include`/`echo` blocks.
– Use `View Page Source` in frontend or `Error Log` in browser console (Developer Tools) to spot malformed HTML or unexpected output.
– In Gutenberg editor, use `Preview` > "Preview in New Tab" for better rendering test.
Let me know if you want to **cleanly support block editor previews** too.
– `$terms` and `$initial_query` are both defined **before** this template is included.
– `blog-posts-tmpl.php` is present and structured correctly.
—
### ✅ Checklist for Proper Execution
Please confirm the following:
#### 1. `ajax-post-category-template.php` is loaded via shortcode:
include get_stylesheet_directory() . '/partials/ajax-post-category-template.php';
#### 2. The variables `$terms` and `$initial_query` are passed like this (in your shortcode function):
$terms = get_terms([
'taxonomy' => 'category',
'hide_empty' => true,
]);
$first_cat_id = $terms[0]->term_id;
$initial_query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 6,
'cat' => $first_cat_id,
]);
#### 3. `blog-posts-tmpl.php` only expects `$initial_query` or `$query` (not undefined variables):
Example `blog-posts-tmpl.php`:
<?php while ($initial_query->have_posts()): $initial_query->the_post(); ?>
<div class="post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
</div>
<?php endwhile; ?>
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => 1,
]);
wp_reset_postdata();
?>
—
### 🛠 Error Fix Tip
If you're getting this error:
> Warning: Undefined variable `$initial_query`…
→ Make sure `ajax-post-category-template.php` is **included inside the same function scope** where `$initial_query` is defined — or use `global $initial_query;`.
—
### ✅ Final Word
Your current code block:
<div id="post-category-tabs"> ... </div>
<div id="post-search-form"> ... </div>
<div id="post-results-container"> ... </div>
…is **correct** as long as:
– `$initial_query` exists
– `blog-posts-tmpl.php` uses it safely
– There is no duplicate output or include
—
Want me to give you a final working example of `blog-posts-tmpl.php` or wrap it in a safe check with fallbacks?
—
## ✅ GOAL
– Load blog posts via AJAX by **category**, **search**, and **pagination**
– Use shortcode in the frontend
– Templates are in `partials/`
– Works without AJAX for initial load
—
## ✅ FILE STRUCTURE
/your-child-theme/
├── inc/
│ └── shortcodes/
│ └── ajax-post-category-filter.php
├── partials/
│ ├── ajax-post-category-template.php
│ └── blog-posts-tmpl.php
├── js/
│ └── ajax-post-category.js
└── functions.php
—
## ✅ 1. Shortcode + AJAX Logic
### 📄 `/inc/shortcodes/ajax-post-category-filter.php`
<?php
add_shortcode('ajax_post_category_filter', 'shortcode_ajax_post_category_filter');
function shortcode_ajax_post_category_filter() {
ob_start();
$terms = get_terms(['taxonomy' => 'category', 'hide_empty' => true]);
if (empty($terms) || is_wp_error($terms)) return '';
$first_cat_id = $terms[0]->term_id;
$initial_query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 6,
'cat' => $first_cat_id,
'paged' => 1,
]);
$template = get_stylesheet_directory() . '/partials/ajax-post-category-template.php';
if (file_exists($template)) include $template;
return ob_get_clean();
}
// Enqueue script
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('ajax-post-category', get_stylesheet_directory_uri() . '/js/ajax-post-category.js', ['jquery'], null, true);
wp_localize_script('ajax-post-category', 'ajax_post_data', [
'ajax_url' => admin_url('admin-ajax.php'),
]);
});
// AJAX handler
add_action('wp_ajax_load_post_category', 'ajax_load_post_category_callback');
add_action('wp_ajax_nopriv_load_post_category', 'ajax_load_post_category_callback');
function ajax_load_post_category_callback() {
$cat_id = intval($_POST['category_id']);
$paged = intval($_POST['page'] ?? 1);
$search = sanitize_text_field($_POST['search'] ?? '');
$args = [
'post_type' => 'post',
'posts_per_page' => 6,
'paged' => $paged,
'cat' => $cat_id,
];
if (!empty($search)) $args['s'] = $search;
$query = new WP_Query($args);
if ($query->have_posts()) {
include get_stylesheet_directory() . '/partials/blog-posts-tmpl.php';
} else {
echo '<p>No posts found.</p>';
}
wp_die();
}
—
## ✅ 2. Template with Initial Load
### 📄 `/partials/ajax-post-category-template.php`
<div id="post-category-tabs">
<?php foreach ($terms as $term): ?>
<button class="category-tab" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div id="post-search-form">
<form id="post-search-form-element" action="#" autocomplete="off">
<input type="text" id="post-search-input" placeholder="Search posts..." />
<button type="submit">Search</button>
</form>
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>" data-search="">
<?php
if ($initial_query->have_posts()) {
include get_stylesheet_directory() . '/partials/blog-posts-tmpl.php';
} else {
echo '<p>No posts found.</p>';
}
?>
</div>
—
## ✅ 3. AJAX Blog Output Template
### 📄 `/partials/blog-posts-tmpl.php`
<div class="post-list">
<?php while ($query->have_posts()): $query->the_post(); ?>
<div class="post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
</div>
<?php endwhile; ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'format' => '',
'prev_text' => '« Prev',
'next_text' => 'Next »',
]);
?>
</div>
<?php wp_reset_postdata(); ?>
—
## ✅ 4. JavaScript for AJAX Logic
### 📄 `/js/ajax-post-category.js`
jQuery(document).ready(function ($) {
function loadPosts(catId, page = 1, search = '') {
$('#post-results-container').html('<div class="loading">Loading...</div>');
$.post(ajax_post_data.ajax_url, {
action: 'load_post_category',
category_id: catId,
page: page,
search: search
}, function (response) {
$('#post-results-container').html(response);
$('#post-results-container').data('category', catId).data('search', search);
});
}
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
const search = $('#post-search-input').val();
loadPosts(catId, 1, search);
});
$(document).on('click', '.ajax-pagination a', function (e) {
e.preventDefault();
const page = $(this).text();
const catId = $('#post-results-container').data('category');
const search = $('#post-search-input').val();
loadPosts(catId, page, search);
});
$('#post-search-form-element').on('submit', function (e) {
e.preventDefault();
const search = $('#post-search-input').val();
const catId = $('#post-results-container').data('category');
loadPosts(catId, 1, search);
});
});
—
## ✅ 5. Load the Shortcode
In any post/page editor:
[ajax_post_category_filter]
—
## 🔧 Optional Enhancements
– Add loading spinner or fade-in animation
– Add active class to selected tab
– Use `data-max-pages` to improve pagination logic
—
Would you like:
– CSS for styling?
– Thumbnails & meta in post items?
– Scroll to top on pagination click?
Let me know!
<?php while ($initial_query->have_posts()): $initial_query>the_post(); ?>
—
## ❌ Problem
You wrote:
$initial_query>the_post(); // ❌ Invalid syntax
PHP interprets `>` as a greater-than operator, not as an object access operator. This causes a **fatal error**, which prevents WordPress from properly rendering or saving the page in the editor — it *crashes the page render* and shows raw output or nothing at all.
—
## ✅ Fix
Correct the object operator `>` to `->` like this:
<?php while ($initial_query->have_posts()): $initial_query->the_post(); ?>
<div class="post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
</div>
<?php endwhile;
wp_reset_postdata();
?>
—
## 🔄 Optional: Add Defensive Check
To ensure `$initial_query` is always a valid query object, you can also do:
<?php if ($initial_query instanceof WP_Query && $initial_query->have_posts()): ?>
<?php while ($initial_query->have_posts()): $initial_query->the_post(); ?>
<div class="post-item">
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p><?php echo wp_trim_words(get_the_excerpt(), 20); ?></p>
</div>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
<?php else: ?>
<p>No posts found.</p>
<?php endif; ?>
—
## ✅ Summary
| ❌ Bug | ✅ Fix |
|————————|—————————|
| `$initial_query>the_post();` | `$initial_query->the_post();` |
After fixing this, your shortcode will:
– Stop breaking the page/post editor
– Render correctly
– Publish safely
Let me know if you also want a fallback for `$initial_query` being empty or to clean up for block editor compatibility.
– ✅ **Featured image**
– ✅ **Category name**
– ✅ **Post title**
– ✅ **Read more** link
—
## ✅ Updated `/partials/blog-posts-tmpl.php`
<div class="post-list">
<?php while ($initial_query->have_posts()): $initial_query->the_post(); ?>
<div class="post-item">
<?php if (has_post_thumbnail()): ?>
<div class="post-thumbnail">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('medium'); ?>
</a>
</div>
<?php endif; ?>
<div class="post-meta">
<span class="post-category">
<?php
$categories = get_the_category();
if (!empty($categories)) {
echo esc_html($categories[0]->name);
}
?>
</span>
</div>
<h3 class="post-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<a class="read-more" href="<?php the_permalink(); ?>">Read More</a>
</div>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => 1,
]);
?>
</div>
—
## ✅ Optional CSS (Minimal Styling)
You can add this to your theme's CSS:
.post-item {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 20px;
}
.post-thumbnail img {
width: 100%;
height: auto;
}
.post-category {
font-size: 13px;
color: #888;
text-transform: uppercase;
display: block;
margin-bottom: 5px;
}
.post-title {
margin: 10px 0;
}
.read-more {
display: inline-block;
margin-top: 10px;
color: #0073aa;
text-decoration: none;
}
.read-more:hover {
text-decoration: underline;
}
—
Let me know if you'd like to:
– Show multiple categories instead of just the first
– Add date, author, or excerpt
– Display in a grid layout with responsive design
I can provide that too!
const catId = $(this).data('cat');
loadPosts(catId);
});
add active calls for clicked category and remove for others
—
## ✅ Updated JavaScript
$(document).on('click', '.category-tab', function () {
const catId = $(this).data('cat');
// Remove .active from all buttons and add to the clicked one
$('.category-tab').removeClass('active');
$(this).addClass('active');
const search = $('#post-search-input').val();
loadPosts(catId, 1, search);
});
—
## ✅ Optional CSS for `.active` class
Add this to your theme’s stylesheet or a custom CSS section:
.category-tab {
padding: 8px 15px;
border: 1px solid #ccc;
background-color: #f5f5f5;
cursor: pointer;
margin-right: 5px;
}
.category-tab.active {
background-color: #0073aa;
color: #fff;
border-color: #0073aa;
}
—
### ✅ Result
– Clicking a tab will:
– Load posts via AJAX
– Highlight the clicked tab with `.active`
– Remove `.active` from other tabs
Let me know if you'd like:
– Default `.active` set on first load
– Smooth scroll to post list on click
– Category tabs converted to a dropdown on mobile
—
## ✅ Structure Overview (Matches Image)
– Top horizontal **filter tabs** (`Recente`, `Gids`, `Kennisbank`, etc.)
– **Search bar** centered with arrow button
– Grid of **post cards**:
– Label/tag on top-left
– Image
– Title
– "Lees verder →" styled button
– **Pagination dots** at the bottom
—
## ✅ 1. Updated HTML Structure (Replace Existing Template Markup)
Here’s the revised version of your `ajax-post-category-template.php`:
<div class="blog-header">
<div class="blog-category-tabs" id="post-category-tabs">
<?php foreach ($terms as $term):
$active = ($term->term_id == '1126') ? 'active-cat' : '';
?>
<button class="category-tab <?= $active ?>" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
<div class="blog-search-wrapper">
<form id="post-search-form-element" action="#" autocomplete="off">
<input type="text" id="post-search-input" placeholder="Wat ga je onderzoeken?" />
<button type="submit" id="post-search-button"><span>→</span></button>
</form>
</div>
</div>
<div id="post-results-container" data-category="<?= esc_attr($first_cat_id) ?>">
<?php
if ($initial_query->have_posts()):
include get_stylesheet_directory() . '/partials/blog-posts-tmpl.php';
else:
echo '<p>No posts found.</p>';
endif;
?>
</div>
—
## ✅ 2. Update `/partials/blog-posts-tmpl.php`
<div class="blog-post-grid">
<?php while ($initial_query->have_posts()): $initial_query->the_post(); ?>
<div class="blog-card">
<div class="blog-card-inner">
<?php if (has_post_thumbnail()): ?>
<a href="<?php the_permalink(); ?>" class="blog-thumb">
<?php the_post_thumbnail('medium'); ?>
</a>
<?php else: ?>
<a href="<?php the_permalink(); ?>" class="blog-thumb">
<img src="/wp-content/uploads/91b8b9287795f27f29956b7ebf80502ae809e8ad-scaled.jpg" alt="Default" />
</a>
<?php endif; ?>
<span class="blog-tag">
<?php
$categories = get_the_category();
if (!empty($categories)) {
echo esc_html($categories[0]->name);
}
?>
</span>
<h3 class="blog-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<a class="blog-read-more" href="<?php the_permalink(); ?>">Lees verder →</a>
</div>
</div>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
</div>
<div class="blog-pagination">
<?php
echo paginate_links([
'type' => 'list',
'total' => $initial_query->max_num_pages,
'current' => 1,
'prev_text' => '',
'next_text' => '',
]);
?>
</div>
—
## ✅ 3. Suggested CSS (Paste into your child theme stylesheet)
.blog-header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40px;
}
.blog-category-tabs {
display: flex;
gap: 20px;
justify-content: center;
margin-bottom: 20px;
}
.category-tab {
background: none;
border: none;
font-size: 16px;
padding: 8px 16px;
cursor: pointer;
color: #333;
position: relative;
font-weight: 500;
}
.category-tab.active-cat {
font-weight: bold;
color: #0073aa;
border-bottom: 2px solid #0073aa;
}
.blog-search-wrapper form {
display: flex;
justify-content: center;
width: 100%;
max-width: 600px;
border: 1px solid #ccc;
border-radius: 25px;
overflow: hidden;
}
.blog-search-wrapper input {
flex: 1;
padding: 10px 20px;
border: none;
outline: none;
font-size: 16px;
}
.blog-search-wrapper button {
background: #00a99d;
color: #fff;
padding: 0 25px;
border: none;
font-size: 20px;
cursor: pointer;
}
.blog-post-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 30px;
padding: 0 20px;
}
.blog-card {
background: #f4f4f4;
border-radius: 12px;
overflow: hidden;
position: relative;
transition: transform 0.3s ease;
}
.blog-card-inner {
padding: 20px;
}
.blog-thumb img {
width: 100%;
height: auto;
display: block;
border-radius: 12px 12px 0 0;
}
.blog-tag {
position: absolute;
top: 15px;
left: 15px;
background: #00a99d;
color: white;
padding: 5px 12px;
font-size: 12px;
border-radius: 20px;
font-weight: 600;
text-transform: uppercase;
}
.blog-title {
margin: 20px 0 10px;
font-size: 18px;
line-height: 1.4;
}
.blog-read-more {
color: #0073aa;
font-weight: 500;
text-decoration: none;
}
.blog-pagination {
text-align: center;
margin-top: 30px;
}
.blog-pagination ul {
list-style: none;
padding: 0;
display: flex;
justify-content: center;
gap: 10px;
}
.blog-pagination li span,
.blog-pagination li a {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
background: #ccc;
}
.blog-pagination li .current {
background: #00a99d;
}
—
## ✅ Final Result
You’ll get:
– Horizontal tabs with active highlighting
– Centered pill-style search bar
– Responsive grid of image cards with tags, title, and read more
– Dotted pagination
—
Would you like:
– Hover effects on cards?
– Responsiveness tweaks?
– Add animations (e.g., fade in)?
Just say the word!
– The **featured image is the background**
– Overlaid on top:
– **Category label** (top-left)
– **Post title** (center-left or top-left)
– **"Lees verder →" button** at bottom-right
I'll give you the updated **HTML structure** (without changing classes) and the full **CSS** to get that polished look.
—
## ✅ 1. ✅ Updated PHP/HTML Code (use your current classes)
Replace your loop with this:
<div class="post-list">
<?php while ($initial_query->have_posts()): $initial_query->the_post(); ?>
<div class="post-item">
<div class="post-thumbnail" style="background-image: url('<?php echo get_the_post_thumbnail_url(get_the_ID(), 'medium') ?: '/wp-content/uploads/91b8b9287795f27f29956b7ebf80502ae809e8ad-scaled.jpg'; ?>');">
<div class="post-overlay">
<div class="post-meta">
<span class="post-category">
<?php
$categories = get_the_category();
if (!empty($categories)) {
echo esc_html($categories[0]->name);
}
?>
</span>
</div>
<h3 class="post-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
<a class="read-more" href="<?php the_permalink(); ?>">Lees verder <span>→</span></a>
</div>
</div>
</div>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'format' => '',
'prev_text' => '',
'next_text' => '',
]);
?>
</div>
—
## ✅ 2. CSS Styling (copy into your CSS)
.post-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 30px;
}
.post-item {
position: relative;
overflow: hidden;
border-radius: 15px;
height: 340px;
}
.post-thumbnail {
background-size: cover;
background-position: center;
height: 100%;
width: 100%;
position: relative;
border-radius: 15px;
display: flex;
align-items: flex-end;
}
.post-overlay {
background: linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0.3) 40%, transparent 90%);
color: #fff;
width: 100%;
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: flex-end;
border-radius: 15px;
position: absolute;
top: 0;
left: 0;
}
.post-meta .post-category {
background: rgba(255, 255, 255, 0.85);
color: #333;
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
display: inline-block;
margin-bottom: 10px;
text-transform: uppercase;
}
.post-title {
margin: 0 0 10px;
font-size: 20px;
line-height: 1.4;
font-weight: bold;
}
.post-title a {
color: #fff;
text-decoration: none;
}
.read-more {
align-self: flex-end;
font-weight: 500;
text-decoration: none;
color: #fff;
background: rgba(0, 0, 0, 0.4);
padding: 8px 14px;
border-radius: 25px;
font-size: 14px;
transition: all 0.3s ease;
}
.read-more:hover {
background: rgba(255, 255, 255, 0.9);
color: #000;
}
.read-more span {
margin-left: 5px;
}
—
## ✅ Summary
| Element | Style |
|——————|———————————————–|
| `post-thumbnail` | Full background image with dark overlay |
| `post-overlay` | Contains category, title, and "Lees verder" |
| `post-meta` | Rounded category tag |
| `read-more` | Button styled to bottom-right on hover |
—
Let me know if you want:
– Hover zoom effect on image
– Slide-up text animation
– Different gradient or dark theme
I'm happy to style it further to match your brand!
display: grid
;
grid-template-columns: repeat(2, 1fr);
gap:18px;
}
.post-item {
position: relative;
overflow: hidden;
border-radius: 15px;
height: 340px;
}
.post-thumbnail {
background-size: cover;
background-position: center;
height: 100%;
width: 100%;
position: relative;
border-radius: 15px;
display: flex;
align-items: flex-end;
}
.post-overlay {
background: #00252633;
color: #fff;
width: 100%;
height: 100%;
padding: 22px;
display: flex
;
flex-direction: column;
justify-content: space-between;
border-radius: 15px;
position: absolute;
top: 0;
left: 0;
}
.post-cat-title{
display: flex
;
flex-direction: column;
gap: 24px;
}
.post-meta .post-category {
background: rgba(255, 255, 255, 0.85);
color: #333;
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
display: inline-block;
text-transform: uppercase;
}
.post-title {
margin: 0 0 10px;
font-size: 20px;
line-height: 1.4;
font-weight: bold;
}
.post-title a {
color: #fff;
text-decoration: none;
}
.read-more {
align-self: flex-end;
font-weight: 600;
text-decoration: none;
color: #fff;
font-size: 17.5px;
}
.read-more span {
margin-left: 5px;
}
everythig works well , now we have to work on category , as you can see , there is category , which is coming on top is changed on all boxes , so I want 1 2 3 style and then it will get repeated mena , 4 , 5 , 6will have design like 1, 2 ,3
Card 1 → Style A
Card 2 → Style B
Card 3 → Style C
Card 4 → Style A (repeat)
Card 5 → Style B
Card 6 → Style C
...
—
## ✅ Step-by-Step Solution
### ✅ 1. Add Dynamic Class in Loop (PHP)
In your loop (inside `blog-posts-tmpl.php`), use an index counter:
<?php
$index = 0; // start counter
while ($initial_query->have_posts()): $initial_query->the_post();
$style_class = 'tag-style-' . (($index % 3) + 1); // tag-style-1, 2, 3
?>
<div class="post-item">
<div class="post-thumbnail" style="background-image: url('<?php echo get_the_post_thumbnail_url(get_the_ID(), 'medium') ?: '/wp-content/uploads/91b8b9287795f27f29956b7ebf80502ae809e8ad-scaled.jpg'; ?>');">
<div class="post-overlay">
<div class="post-cat-title">
<div class="post-meta">
<span class="post-category <?= $style_class ?>">
<?php
$categories = get_the_category();
if (!empty($categories)) {
echo esc_html($categories[0]->name);
}
?>
</span>
</div>
<h3 class="post-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
</div>
<a class="read-more" href="<?php the_permalink(); ?>">Less verder <span>→</span></a>
</div>
</div>
</div>
<?php
$index++; // increment counter
endwhile;
wp_reset_postdata();
?>
—
### ✅ 2. Add CSS for `tag-style-1`, `tag-style-2`, `tag-style-3`
You can style each one with unique colors like this:
.tag-style-1 {
background-color: #e6f2f2;
color: #008080;
}
.tag-style-2 {
background-color: #e3f0e8;
color: #207a45;
}
.tag-style-3 {
background-color: #e3ecf4;
color: #0e5b96;
}
> Feel free to change colors based on your brand palette (you can match `GIDS`, `RECENT`, `KENNISBANK` colors from the screenshot).
—
### ✅ Result
– Your grid cards will now cycle category styles every 3 items
– Matches the **design language** in your screenshot
– Easy to maintain or expand (just add `tag-style-4`, etc.)
—
Let me know if you want:
– Different icons or backgrounds per style
– Auto-style based on **category slug**
– Color-coded badges based on real taxonomy values
I can help automate that too!
—
## ✅ Final Code with `tag-style-1`, `tag-style-2`, `tag-style-3` classes cycling automatically:
<div class="post-list">
<?php
$index = 0; // Counter to cycle styles
while ($initial_query->have_posts()): $initial_query->the_post();
$style_class = 'tag-style-' . (($index % 3) + 1); // Cycles through 1, 2, 3
?>
<div class="post-item">
<div class="post-thumbnail" style="background-image: url('<?php echo get_the_post_thumbnail_url(get_the_ID(), 'medium') ?: '/wp-content/uploads/91b8b9287795f27f29956b7ebf80502ae809e8ad-scaled.jpg'; ?>');">
<div class="post-overlay">
<div class="post-cat-title">
<div class="post-meta">
<span class="post-category <?= esc_attr($style_class); ?>">
<?php
$categories = get_the_category();
if (!empty($categories)) {
echo esc_html($categories[0]->name);
}
?>
</span>
</div>
<h3 class="post-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h3>
</div>
<a class="read-more" href="<?php the_permalink(); ?>">Lees verder <span>→</span></a>
</div>
</div>
</div>
<?php
$index++; // Increment after each post
endwhile;
wp_reset_postdata();
?>
</div>
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'format' => '',
'prev_text' => '',
'next_text' => '',
]);
?>
</div>
—
## ✅ CSS to Style the Cycling Badge Colors
.tag-style-1 {
background-color: #e6f2f2;
color: #008080;
}
.tag-style-2 {
background-color: #e3f0e8;
color: #207a45;
}
.tag-style-3 {
background-color: #e3ecf4;
color: #0e5b96;
}
—
### ✅ Result
Your grid now shows:
– The **category name** with different colors for each card
– The style **resets every 3 posts**
– You still have your custom layout/design fully intact
Let me know if you'd like to:
– Use category **slug-based color styles** (e.g., `category-kennisbank`, `category-gids`)
– Add icons to tags
– Animate card hover effects
I'm happy to help enhance the visual flow further!
background-color:#19BDC0;
color: #fff;
border:1px solid #19BDC0;
}
background opacity 10%
—
## ✅ Option 1: Use `rgba()` for 10% opacity
.tag-style-3 {
background-color: rgba(25, 189, 192, 0.1); /* 10% opacity */
color: #19BDC0;
border: 1px solid #19BDC0;
}
> This creates a **light tinted background** using the same color with 10% opacity and keeps the text and border color fully visible.
—
## ✅ Option 2: Use hex with alpha (if supported in your project)
Modern browsers support 8-digit hex where the last two digits are alpha:
.tag-style-3 {
background-color: #19BDC01A; /* 1A = approx 10% alpha */
color: #19BDC0;
border: 1px solid #19BDC0;
}
—
## ✅ Recommended Final Version
.tag-style-3 {
background-color: rgba(25, 189, 192, 0.1); /* 10% opacity */
color: #19BDC0;
border: 1px solid #19BDC0;
}
—
Let me know if you want this applied dynamically to all tag styles (`tag-style-1`, `-2`, `-3`) using SCSS or inline PHP!
do it!
– Appear **inline** like a horizontal nav
– Use a **bottom border for the active tab**
– Keep everything clean and responsive
—
## ✅ Final CSS
#post-category-tabs {
display: flex;
gap: 25px;
justify-content: center;
margin-bottom: 30px;
border-bottom: 1px solid #e2e2e2;
padding-bottom: 5px;
}
.category-tab {
background: none;
border: none;
font-size: 16px;
font-weight: 500;
color: #002526;
padding: 8px 0;
cursor: pointer;
position: relative;
transition: color 0.3s;
}
.category-tab:hover {
color: #000;
}
.category-tab.active-cat {
font-weight: 600;
color: #002526;
}
.category-tab.active-cat::after {
content: "";
display: block;
margin: 5px auto 0;
width: 20px;
height: 2px;
background-color: #002526;
border-radius: 2px;
}
—
## ✅ HTML Structure (your current code — no change needed)
<div id="post-category-tabs">
<?php foreach ($terms as $term):
$active = ($term->term_id == '1126') ? 'active-cat' : '';
?>
<button class="category-tab <?php echo $active ?>" data-cat="<?= esc_attr($term->term_id) ?>">
<?= esc_html($term->name) ?>
</button>
<?php endforeach; ?>
</div>
—
## ✅ Result
– Tabs display **horizontally centered**
– Active tab gets a **2px solid underline**
– Hover effects and clean spacing match your screenshot
—
Let me know if you want:
– **Animated underline on hover**
– Mobile responsive version (dropdown or scrollable tabs)
– Bold on hover or gradient active bar
Ready to style it further if you’d like!
plase dont do like this !
Let’s fix it **professionally**:
—
## ✅ Updated CSS (No Shift on Active Tab)
We'll **absolutely position** the underline so it doesn't affect layout flow.
#post-category-tabs {
display: flex;
gap: 25px;
justify-content: center;
margin-bottom: 30px;
border-bottom: 1px solid #e2e2e2;
padding-bottom: 5px;
}
.category-tab {
background: none;
border: none;
font-size: 16px;
font-weight: 500;
color: #002526;
padding: 8px 0;
cursor: pointer;
position: relative;
transition: color 0.3s;
}
.category-tab:hover {
color: #000;
}
.category-tab.active-cat {
font-weight: 600;
color: #002526;
}
.category-tab.active-cat::after {
content: "";
position: absolute;
bottom: -6px; /* sits below text */
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 2px;
background-color: #002526;
border-radius: 2px;
}
—
## 🔍 Why This Fix Works
– `position: absolute` removes the underline from normal layout flow.
– `bottom: -6px` keeps it **underneath** the text without shifting content up.
– `transform: translateX(-50%)` centers it perfectly.
—
Let me know if you'd like:
– A subtle animation on underline hover
– Different color or style (e.g. dot or gradient)
– Responsive scrollable tabs on mobile
Ready to help!
– Numbers centered
– Arrows inside **circular buttons**
– Active page highlighted
– Consistent spacing
—
## ✅ Step 1: Update `paginate_links()` to output as “
Replace your pagination block with this:
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'type' => 'list',
'prev_text' => '<span class="pag-arrow-icon">←</span>',
'next_text' => '<span class="pag-arrow-icon">→</span>',
]);
?>
</div>
> The `type => 'list'` generates “, which is easier to style like your design.
—
## ✅ Step 2: Add CSS for Styled Pagination
.ajax-pagination {
text-align: center;
margin-top: 30px;
}
.ajax-pagination ul {
list-style: none;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
padding: 0;
margin: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 15px;
font-weight: 500;
text-decoration: none;
color: #146e71;
transition: background-color 0.3s, color 0.3s;
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
}
.ajax-pagination a:hover {
background-color: #eaf7f7;
}
.pag-arrow-icon {
font-weight: bold;
font-size: 18px;
}
—
## ✅ Result
– ⭕ Prev/Next arrows are **circular**
– 🔢 Page numbers are **uniformly styled**
– ✅ Current page is **filled with color**
– ✨ Responsive & clean layout
—
Let me know if you'd like:
– SVG icons for arrows
– Ellipsis (`…`) for long paginations
– Animated transitions on page change
Happy to refine it further!
add it as well ans make same like given image !
– A **Previous arrow button**
– **Page numbers**
– A **Next arrow button**
– All inside **circular, centered, evenly spaced layout**
Let’s now **do it properly and completely**, just like your image.
—
## ✅ Step 1: PHP – Full Pagination with Prev/Next
Update your `paginate_links()` block:
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'type' => 'list',
'prev_text' => '<span class="pag-arrow-icon">❮</span>',
'next_text' => '<span class="pag-arrow-icon">❯</span>',
'end_size' => 1,
'mid_size' => 1,
]);
?>
</div>
> We use `❮` and `❯` for clean arrow icons that resemble your screenshot. You can change them to “ if needed.
—
## ✅ Step 2: CSS – Exactly Like Screenshot
.ajax-pagination {
text-align: center;
margin-top: 30px;
}
.ajax-pagination ul {
list-style: none;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 0;
margin: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 14px;
font-weight: 600;
text-decoration: none;
color: #146e71;
background-color: transparent;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.ajax-pagination li a:hover {
background-color: rgba(20, 110, 113, 0.1);
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
border-color: #146e71;
}
.ajax-pagination .pag-arrow-icon {
font-size: 14px;
line-height: 1;
}
—
## ✅ Output
You now get:
| Button | Style |
|—————|—————————————-|
| ❮ (Previous) | Circular, colored border on active |
| 1, 2, 3 | Circular, center-aligned, active filled |
| ❯ (Next) | Circular, hover effect |
—
Let me know if you want:
– SVG or font icon arrows (instead of characters)
– Touch/swipe pagination support
– AJAX click handling for the numbered links
Happy to help you finish this beautifully.
do something
### ✅ The issue:
'current' => intval($_POST['page'] ?? 1),
This always defaults to 1 during normal page loads — so pagination thinks you’re always on the first page → no "Previous" link.
—
## ✅ Solution: Use `get_query_var('paged')` (or fallback)
Replace this line:
'current' => intval($_POST['page'] ?? 1),
With this:
'current' => max(1, get_query_var('paged') ?: 1),
### ✅ Final Fixed Pagination Code:
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => max(1, get_query_var('paged') ?: 1),
'type' => 'list',
'prev_text' => '<span class="pag-arrow-icon">❮</span>',
'next_text' => '<span class="pag-arrow-icon">❯</span>',
'end_size' => 1,
'mid_size' => 1,
]);
?>
</div>
—
## ✅ Bonus: Keep CSS as Previously Shared
.ajax-pagination ul {
display: flex;
justify-content: center;
gap: 10px;
list-style: none;
padding: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
width: 32px;
height: 32px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
color: #146e71;
text-decoration: none;
border: 2px solid transparent;
background: transparent;
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
border-color: #146e71;
}
.ajax-pagination a:hover {
background-color: rgba(20, 110, 113, 0.1);
}
.pag-arrow-icon {
font-size: 14px;
}
—
Let me know if you need to hook this into your AJAX logic too. I can help sync pagination clicks and ensure prev/next work dynamically.
—
## ✅ 1. Final Working PHP Pagination Code (With Arrows)
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => max(1, get_query_var('paged')),
'type' => 'list',
'prev_text' => '<span class="pag-arrow-icon">❮</span>',
'next_text' => '<span class="pag-arrow-icon">❯</span>',
'end_size' => 1,
'mid_size' => 1,
'before_page_number' => '',
'after_page_number' => '',
]);
?>
</div>
> ✅ This uses `get_query_var('paged')` to work **both in normal loads and AJAX** if handled properly.
—
## ✅ 2. CSS to Style the Arrows and Pagination Like Your Screenshot
.ajax-pagination {
text-align: center;
margin-top: 30px;
}
.ajax-pagination ul {
list-style: none;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 0;
margin: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 14px;
font-weight: 600;
text-decoration: none;
color: #146e71;
background-color: transparent;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
border-color: #146e71;
}
.ajax-pagination a:hover {
background-color: rgba(20, 110, 113, 0.1);
}
.pag-arrow-icon {
font-size: 14px;
line-height: 1;
display: inline-block;
transform: translateY(-1px);
}
—
### ✅ Result You’ll Get
| Element | Style |
|————-|———————————————————————–|
| ❮ Previous | Circular button with teal border & hover effect |
| Page 1-2-3 | Styled numbers, current page highlighted with filled circle |
| ❯ Next | Circular arrow button on right, same style as previous |
—
Let me know if you want:
– Pagination to work via AJAX (dynamic)
– Dot-style (•••) when there are many pages
– Mobile responsive tweaks (e.g. collapse or swipe nav)
Happy to enhance it further!
please do!
– A **Previous arrow** (❮)
– **Page numbers**
– A **Next arrow** (❯)
– All styled like circular buttons (as in your screenshot)
—
## ✅ ✅ Updated Pagination Block (with arrows + numbers)
Replace your “ block with this:
<div class="ajax-pagination">
<?php
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => intval($_POST['page'] ?? 1),
'type' => 'list', // outputs <ul><li> for styling
'prev_text' => '<span class="pag-arrow-icon">❮</span>',
'next_text' => '<span class="pag-arrow-icon">❯</span>',
'end_size' => 1,
'mid_size' => 1,
'before_page_number' => '',
'after_page_number' => '',
]);
?>
</div>
—
## ✅ CSS for Styling (matches circular layout)
.ajax-pagination {
text-align: center;
margin-top: 30px;
}
.ajax-pagination ul {
list-style: none;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 0;
margin: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 14px;
font-weight: 600;
text-decoration: none;
color: #146e71;
background-color: transparent;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
border-color: #146e71;
}
.ajax-pagination a:hover {
background-color: rgba(20, 110, 113, 0.1);
}
.pag-arrow-icon {
font-size: 14px;
display: inline-block;
transform: translateY(-1px);
}
—
## ✅ Summary
This guarantees:
– ✅ Always shows **Previous → Page Numbers → Next**
– ✅ Works with both normal and AJAX requests
– ✅ Clean styling with hover and active state
– ✅ Fully matches your UI screenshot
Let me know if you'd like:
– Responsive tweaks
– Loading animation on click
– AJAX integration for seamless transitions
I'm here to help make it perfect!
The reason is this line in your code:
'current' => intval($_POST['page'] ?? 1),
That always defaults to `1` if no `$_POST['page']` exists — meaning you are always on **page 1**, and WordPress **hides the Previous button** automatically.
—
## ✅ Full Fix (for both AJAX and frontend)
### ✅ 1. Replace your pagination code with this:
<div class="ajax-pagination">
<?php
// Determine current page from either $_POST (AJAX) or get_query_var (frontend)
$current_page = isset($_POST['page']) ? intval($_POST['page']) : max(1, get_query_var('paged'));
echo paginate_links([
'total' => $initial_query->max_num_pages,
'current' => $current_page,
'type' => 'list',
'prev_text' => '<span class="pag-arrow-icon">❮</span>',
'next_text' => '<span class="pag-arrow-icon">❯</span>',
'end_size' => 1,
'mid_size' => 1,
]);
?>
</div>
—
### ✅ 2. CSS (no change if you've already added this, else include):
.ajax-pagination {
text-align: center;
margin-top: 30px;
}
.ajax-pagination ul {
list-style: none;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 0;
margin: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 14px;
font-weight: 600;
text-decoration: none;
color: #146e71;
background-color: transparent;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
border-color: #146e71;
}
.ajax-pagination a:hover {
background-color: rgba(20, 110, 113, 0.1);
}
.pag-arrow-icon {
font-size: 14px;
display: inline-block;
transform: translateY(-1px);
}
—
### ✅ Done!
This ensures:
– ✅ Proper detection of current page (frontend and AJAX)
– ✅ Prev/Next arrows **always show** appropriately
– ✅ Matches your circular design perfectly
Let me know if you're using this in a custom AJAX handler — I can also update that accordingly.
just add it for all cases !"
as its given i my gfigma !
> 🔁 **You want the Previous and Next arrows to always be shown**, **even when they're disabled** (like on the first or last page) — exactly how your **Figma design** shows.
WordPress's default `paginate_links()` **hides** the Previous/Next buttons if you're on the first or last page. So we’ll **manually render the pagination**, giving full control over:
– Always showing the arrows
– Disabling them visually when not clickable
– Keeping the same circular style for everything
—
## ✅ Step-by-Step: Custom Pagination Markup
### ✅ 1. PHP (Custom Pagination Rendering)
Replace your current pagination block with this:
<?php
$current_page = isset($_POST['page']) ? intval($_POST['page']) : max(1, get_query_var('paged'));
$total_pages = $initial_query->max_num_pages;
?>
<div class="ajax-pagination">
<ul>
<!-- Prev -->
<li>
<?php if ($current_page > 1): ?>
<a href="#" class="pagination-link" data-page="<?= $current_page - 1 ?>"><span class="pag-arrow-icon">❮</span></a>
<?php else: ?>
<span class="pag-arrow-icon disabled">❮</span>
<?php endif; ?>
</li>
<!-- Page Numbers -->
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li>
<?php if ($i == $current_page): ?>
<span class="current"><?= $i ?></span>
<?php else: ?>
<a href="#" class="pagination-link" data-page="<?= $i ?>"><?= $i ?></a>
<?php endif; ?>
</li>
<?php endfor; ?>
<!-- Next -->
<li>
<?php if ($current_page < $total_pages): ?>
<a href="#" class="pagination-link" data-page="<?= $current_page + 1 ?>"><span class="pag-arrow-icon">❯</span></a>
<?php else: ?>
<span class="pag-arrow-icon disabled">❯</span>
<?php endif; ?>
</li>
</ul>
</div>
—
### ✅ 2. CSS (Make Arrows Always Visible + Disabled State)
.ajax-pagination {
text-align: center;
margin-top: 30px;
}
.ajax-pagination ul {
list-style: none;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 0;
margin: 0;
}
.ajax-pagination li {
display: inline-block;
}
.ajax-pagination a,
.ajax-pagination span {
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 50%;
font-size: 14px;
font-weight: 600;
text-decoration: none;
color: #146e71;
background-color: transparent;
border: 2px solid transparent;
transition: all 0.3s ease;
}
.ajax-pagination .current {
background-color: #146e71;
color: #fff;
border-color: #146e71;
}
.ajax-pagination a:hover {
background-color: rgba(20, 110, 113, 0.1);
}
.ajax-pagination .disabled {
color: #ccc;
cursor: not-allowed;
border: 2px solid #eee;
background-color: #f5f5f5;
}
—
## ✅ Bonus: JavaScript (For AJAX Pagination – Optional)
If you're handling pagination via AJAX:
$(document).on('click', '.pagination-link', function (e) {
e.preventDefault();
const page = $(this).data('page');
const catId = $('#post-results-container').data('category');
const search = $('#post-search-input').val();
loadPosts(catId, page, search); // Your existing AJAX function
});
—
## ✅ Summary
| Feature | Result ✔️ |
|—————-|———–|
| Arrows always shown | ✅ |
| Disabled state on first/last page | ✅ |
| Fully styled like your Figma | ✅ |
| Works in AJAX and normal | ✅ |
Let me know if you want:
– Arrow icons via SVG instead of characters
– Pagination to collapse or scroll on mobile
– Ellipsis (…) for long page lists
I’m here to tailor it exactly how you need!
This means:
– ❮ Previous on page 1 should still **trigger page 1 again** (harmless)
– ❯ Next on the last page should still **trigger the last page again**
– No "disabled" visual or class
—
## ✅ Updated PHP Pagination Code (No disabled state):
<?php
$current_page = isset($_POST['page']) ? intval($_POST['page']) : max(1, get_query_var('paged'));
$total_pages = $initial_query->max_num_pages;
?>
<div class="ajax-pagination">
<ul>
<!-- Always-visible Prev -->
<li>
<a href="javascript:void(0)" class="pagination-link" data-page="<?= max(1, $current_page - 1) ?>">
<span class="pag-arrow-icon">❮</span>
</a>
</li>
<!-- Page Numbers -->
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li>
<?php if ($i == $current_page): ?>
<span class="current"><?= $i ?></span>
<?php else: ?>
<a href="javascript:void(0)" class="pagination-link" data-page="<?= $i ?>"><?= $i ?></a>
<?php endif; ?>
</li>
<?php endfor; ?>
<!-- Always-visible Next -->
<li>
<a href="javascript:void(0)" class="pagination-link" data-page="<?= min($total_pages, $current_page + 1) ?>">
<span class="pag-arrow-icon">❯</span>
</a>
</li>
</ul>
</div>
—
## ✅ Notes
| Button | Behavior |
|————|——————-|
| ❮ Prev | Goes to `current – 1`, never < 1 |
| ❯ Next | Goes to `current + 1`, never > max |
| Always shown | ✅ |
| Never disabled | ✅ |
—
Let me know if you'd like to:
– Loop back (Next from last → page 1)
– Add sliding animation on pagination change
– Scroll to top on pagination click
I'll support that too!
### ❌ Problem:
You’re likely setting a global variable or not properly scoping the default value of `columns`.
—
## ✅ Goal:
– When you pass `[my_shortcode columns=3]` → it uses 3
– When you just write `[my_shortcode]` elsewhere → it uses the **default** (e.g. 2 or 1)
– No interference between different shortcode uses
—
## ✅ ✅ Fix with Proper Attribute Handling
Update your shortcode function like this:
add_shortcode('my_shortcode', 'render_my_shortcode');
function render_my_shortcode($atts) {
// Define defaults
$atts = shortcode_atts([
'columns' => 2 // Default value if not passed
], $atts, 'my_shortcode');
$columns = intval($atts['columns']); // Now it's scoped per instance
ob_start();
// Use $columns in your layout logic
echo '<div class="post-list columns-' . $columns . '">';
// ... your loop or layout
echo '</div>';
return ob_get_clean();
}
—
## ✅ Then in CSS, you can target based on class:
.post-list.columns-2 {
grid-template-columns: repeat(2, 1fr);
}
.post-list.columns-3 {
grid-template-columns: repeat(3, 1fr);
}
—
## ✅ Example Shortcode Usage:
[my_shortcode columns=3] ← renders 3 columns
[my_shortcode] ← renders default 2 columns
Each use is now **fully isolated** and won't interfere with the others.
—
Let me know:
– What your shortcode name is?
– Do you want to allow other layout controls via attributes (e.g. `layout=grid`)?
Happy to help expand this pattern cleanly!