Title: Zen Blogger
Author: Guram Zhgamadze
Published: <strong>August 22, 2026</strong>
Last modified: August 22, 2026

---

Search plugins

![](https://ps.w.org/zen-blogger/assets/banner-772x250.png?rev=3659950)

![](https://ps.w.org/zen-blogger/assets/icon-256x256.png?rev=3659950)

# Zen Blogger

 By [Guram Zhgamadze](https://profiles.wordpress.org/guramzhgamadze/)

[Download](https://downloads.wordpress.org/plugin/zen-blogger.1.8.5.zip)

 * [Details](https://wordpress.org/plugins/zen-blogger/#description)
 * [Reviews](https://wordpress.org/plugins/zen-blogger/#reviews)
 *  [Installation](https://wordpress.org/plugins/zen-blogger/#installation)
 * [Development](https://wordpress.org/plugins/zen-blogger/#developers)

 [Support](https://wordpress.org/support/plugin/zen-blogger/)

## Description

Zen Blogger adds two widgets to Elementor: **Blog Carousel** and **Posts**. Both
draw the same card and share the same query builder, so a layout you design once
works either way.

#### Accessibility

Turning autoplay on adds a play/pause button — you can move it or make it appear
on hover, but you cannot delete it, because moving content with no way to stop it
fails WCAG 2.2.2. Arrows and the play/pause control are real buttons at a 44px target.
Slides carry proper carousel semantics, keyboard control is on by default, and visitors
whose system asks for reduced motion get no autoplay and instant transitions.

#### Performance

The carousel runs on the Swiper library Elementor already loads, so no additional
slider script is added to the page. Nothing is enqueued on pages that do not use
the widget. Images in the first visible row load eagerly with high fetch priority
and everything after them loads lazily, and the image box reserves its aspect ratio
up front so the carousel does not shift your layout while it loads. If the script
never runs, the markup stays a scroll-snapping row instead of collapsing.

#### Query builder

Filter by post type, taxonomy (include _and_ exclude, combined with AND or OR), 
author, date range, and custom field ordering. Show posts related to the post being
viewed, hand-pick them, or reuse the archive’s own query. Exclude the current post,
skip posts already shown by another carousel on the page, or require a featured 
image.

#### The Posts widget

Grid, masonry, list, or a feature layout where the first post spans wider. Pagination
is page numbers, previous/next, a Load More button, or infinite scroll — and every
one of them starts life as a real link, so the widget works with JavaScript disabled
and every page stays crawlable.

When a page of results is fetched in the background:

 * The new result count is announced politely — “Showing 6 of 8 posts”.
 * The grid is marked `aria-busy` while it loads.
 * Focus moves to the first newly loaded post, so keyboard users continue from the
   new content. On an automatic infinite-scroll load, focus is deliberately left
   alone.
 * Infinite scroll loads a set number of pages automatically before the button must
   be pressed again, so the footer stays reachable.
 * The address bar keeps up with filters, so Back works and a filtered view can 
   be shared.

The optional search-and-filter bar is a real form. It offers only the terms present
in the posts the widget is showing, and reflects the current selection with `aria-
current`.

#### Setting up a widget

 1. Edit a page, or a Theme Builder template, with Elementor.
 2. Search the widget panel for “Zen” and drag **Blog Carousel** or **Posts** onto 
    the canvas.
 3. **Content -> Query** picks what to show: the Source (latest posts, related to the
    post being viewed, hand-picked, or the current page’s own query), then post type,
    taxonomy include and exclude, author, date range and ordering.
 4. **Content -> Layout** picks how a card looks: the Skin, and for the Posts widget
    the layout (grid, masonry, list or feature) and the number of columns per device.
 5. **Content -> Pagination**, on the Posts widget, offers page numbers, previous/next,
    a Load More button, infinite scroll, or none.
 6. **Content -> Search & Filter**, on the Posts widget, switches on the filter bar.
    Choose which taxonomies it offers, and whether to include a search box and a sort
    control.
 7. **Content -> Accessibility**, on the carousel, controls autoplay, where the play/
    pause button sits and when it is visible, and whether reduced-motion settings are
    respected.
 8. The **Style** tab has its own section for the card, image, terms, title, meta, 
    excerpt, read more, filter bar and pagination.

Every text label the widget renders is an editable field, and every colour, font,
size, spacing, border, radius and shadow is an Elementor control.

#### Skins

 * Classic — image above the text
 * Overlay — text over the image
 * Overlap — the text block lifted over the image
 * Side by side — image left or right
 * Minimal — no image
 * Editorial — oversized running numbers

#### Motion

Slide, fade, coverflow, cards and creative effects; free scroll and a continuous
ticker mode; multiple rows; centred slides; dots, dynamic dots, a fraction counter,
a progress bar or a draggable scrollbar.

#### Styling

Every colour, font, size, spacing, border, radius and shadow is an Elementor control—
including hover and focus states. Nothing is hard-coded, and no colour is applied
as a default you cannot switch off.

#### SEO and AI (GEO)

Every slide is rendered server-side as a real `<a href>` inside an `<article>`, 
with a selectable heading tag and a machine-readable `<time datetime>`. Search engines
and AI crawlers read the entire list from the page source — nothing is fetched by
JavaScript, and nothing is hidden behind an interaction.

The carousel also describes itself as a schema.org **ItemList** in JSON-LD, either
as positions and URLs or with full post details (headline, image, dates, author)
under BlogPosting, Article, NewsArticle, CreativeWork or Product. That is what lets
a search engine treat the section as an ordered list of articles rather than a cluster
of links, and gives generative engines an unambiguous reading of what the section
contains and in what order. Set it to None if your SEO plugin already outputs an
ItemList for the same section.

#### Works with Zen GEO

If [Zen GEO](https://wordpress.org/plugins/zen-geo/) is installed, the two plugins
share their structured data:

 * Zen GEO prints its page graph in `<head>`; Zen Blogger prints its list where 
   the widget renders. They never overwrite each other.
 * Zen Blogger uses **Zen GEO’s own node identity** (`permalink#article`), so a 
   post described in a carousel and the same post described by Zen GEO on its own
   page resolve to one node instead of two competing descriptions of the same URL.
 * The carousel’s list attaches to Zen GEO’s page node with `isPartOf`, so the output
   reads as one connected graph.
 * Detection is at runtime, so load order never matters. Turn the integration off
   with the `zenblog_zengeo_integration` filter.

Neither plugin requires the other.

#### Developer notes

 * `zenblog_query_args` — modify the WP_Query arguments before the query runs.
 * `zenblog_schema_graph` — alter the JSON-LD graph, or return an empty array to
   suppress it.
 * `zenblog_zengeo_integration` — disable the Zen GEO linkage.
 * `zenblog_reading_speed` — words per minute for the reading-time estimate (default
   200).
 * `zenblog_filter_scope_limit` — how many posts the filter bar inspects to decide
   which terms to offer (default 1000).
 * `zenblog_term_choices_limit` / `zenblog_post_choices_limit` — how many terms 
   and posts the panel offers.

## Installation

 1. Install and activate Elementor.
 2. Upload the `zen-blogger` folder to `/wp-content/plugins/`, or install the zip through
    Plugins  Add New.
 3. Activate Zen Blogger.
 4. Edit a page with Elementor, search the widget panel for “Zen”, and drag **Blog 
    Carousel** or **Posts** from the _Zen Blogger_ category onto the canvas.
 5. See “Setting up a widget” above for what each panel does.

## FAQ

### Does it need Elementor Pro?

No. Zen Blogger works with free Elementor.

### Why can’t I remove the play/pause button?

Because autoplay is on. WCAG 2.2.2 requires a way to stop content that moves automatically,
so the button is rendered whenever autoplay is enabled. You can position it in any
corner and set it to appear on hover and keyboard focus. Turning autoplay off removes
it.

### Does it work with custom post types?

Yes. Any public post type with a UI can be used as the source, and its public taxonomies
appear as filters.

### Will it slow my site down?

The CSS and JS are only enqueued on pages that actually contain the widget, and 
the carousel reuses the Swiper library Elementor already loads rather than adding
another one.

### Does it support RTL?

Yes. The layout uses logical properties throughout and the carousel direction follows
the site’s text direction.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Zen Blogger” is open source software. The following people have contributed to 
this plugin.

Contributors

 *   [ Guram Zhgamadze ](https://profiles.wordpress.org/guramzhgamadze/)

[Translate “Zen Blogger” into your language.](https://translate.wordpress.org/projects/wp-plugins/zen-blogger)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/zen-blogger/), check
out the [SVN repository](https://plugins.svn.wordpress.org/zen-blogger/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/zen-blogger/) by
[RSS](https://plugins.trac.wordpress.org/log/zen-blogger/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.8.5

 * Corrected the Contributors username and the author profile link, which were both
   missing a letter.
 * Rewrote the description to describe this plugin on its own terms, without comparisons
   to other post widgets, and added a step-by-step guide to configuring a widget.

#### 1.8.4

 * Fixed: a widget nested inside a container that does not keep its children in 
   a plain list could not be found by the background request, so pagination and 
   filtering failed on exactly those layouts. Elementor’s own recursive search is
   now used, which descends through the filter such containers use to expose their
   children.

#### 1.8.3

 * Fixed: “Avoid duplicates” stopped working past the first page. It reads a list
   of what the page has already shown, and that list lives for one request — a Load
   More or infinite-scroll fetch is a new request, where it starts empty. So page
   two was drawn from a different result set than page one, and posts could repeat
   or be skipped between them. The set is now carried across.

#### 1.8.2

 * Fixed: loading the dynamic tags warned on every page if glob() failed — an unreadable
   directory or an open_basedir restriction — because the result was iterated without
   checking it was a list.
 * Fixed: the REST filter sanitiser could return null rather than a string if the
   regex engine gave up, so a value that was neither cleaned nor a string could 
   travel on. A failure now drops the filter.
 * Removed dead code: an unset() of an array key that was never set, and an is_array()
   guard on a value that is always an array.

#### 1.8.1

 * Changed: the excerpt is a paragraph rather than a div. Text-processing plugins—
   hyphenation, typography filters, translation — look for `<p>`, and a div quietly
   opted the excerpt out of every one of them. The theme’s paragraph margin is reset
   with it, so card spacing stays owned by the card.

#### 1.8.0

 * Changed: with infinite scroll the feed loads itself, so it no longer shows a 
   button telling you to do what it is already doing. A small spinner appears while
   a page is fetched, and the button returns — visible — once auto-loading reaches
   its limit and there is genuinely something to press. It stays reachable by Tab
   throughout, and remains a real link for visitors without JavaScript.
 * Fixed: the loading spinner was styled but never rendered, so no pagination mode
   has ever shown one.
 * Removed: the outline the paginator drew around Load More and the page numbers.
   The Border, Background and Radius controls are still there for anyone who wants
   one.

#### 1.7.4

 * Changed: Clear and Apply now take the Selected Text Color and Selected Background—
   the same pair that marks a chosen term — so an action reads as a button using
   a colour the bar already defines. The borders 1.7.3 put on every pill are gone
   with them.

#### 1.7.3

 * Fixed: the filter pills, Clear and Apply had no border or background of their
   own, so the whole bar read as a row of stray words rather than controls. They
   now carry the same neutral outline the paginator got, which any Border, Background
   or Radius control still overrides.

#### 1.7.2

 * Fixed: the Clear button took the theme’s button colour on hover and kept it on
   focus after being clicked, while the filter controls beside it did not. Themes
   style bare `button:hover` and `button:focus` at a specificity a single class 
   cannot beat — Astra does exactly this. Those states are now held to the filter
   bar’s own design, and every filter style control reaches them too, so a colour
   or background set for the bar carries into hover and focus instead of being dropped.

#### 1.7.1

 * Fixed: the filter bar’s two spacing sliders shipped with no default, so the panel
   showed them at zero and saved zero the moment either was touched — leaving the
   search box and the controls beneath it in contact. Both now carry a real default.
   An existing widget already saved at zero keeps it; set Space Between Rows to 
   restore the gap.

#### 1.7.0

 * Fixed: the filter bar did nothing at all whenever AJAX was off — with pagination
   set to None, with the Update Without Reloading switch off, or on the Current 
   Query source. Its Apply button only exists for visitors without JavaScript, so
   with scripting on there was no way to submit it. Changing a control now reloads
   the page with the chosen filters, the same round trip the no-JavaScript path 
   makes.
 * New: Current Query works with Load More and infinite scroll on archives. The 
   widget now tells the endpoint which listing it is on — this term, this author,
   this search — and the endpoint checks that identity against real objects before
   rebuilding it, so a caller still cannot define a query of its own.
 * Changed: a taxonomy left offering a single term is dropped from the filter bar.
   On a category archive every post already has that category, so the control was
   a button that could not change anything; search and sort remain.

#### 1.6.0

 * Fixed: the filter bar listed every term on the site with its site-wide count,
   whatever the widget was actually showing. On an author or category archive that
   meant offering terms with nothing in the archive at all, and counts that did 
   not match the results underneath. It now lists only the terms present in the 
   widget’s own posts, counted against them.
 * New: `zenblog_filter_scope_limit` filter, for how many posts the bar may inspect
   when working out which terms are present (default 1000).

#### 1.5.2

 * Fixed: with the Current Query source, the widget stayed on page one while the
   rest of the archive moved. It read its own page number instead of the archive’s,
   so /page/2/ showed page one’s posts. It now follows the page the visitor is on,
   and its paging links drive the archive rather than a private query argument.
 * Fixed: the result count read “0 posts” whenever pagination was switched off, 
   because the query was told not to count rows.

#### 1.5.1

 * New: drop shadow controls for the filter bar — one for the bar itself, one for
   the controls inside it.
 * New: drop shadow control for the paginator, which had every other style control
   but this one.

#### 1.5.0

 * Fixed: pagination did nothing at all when the widget was placed in a Theme Builder
   template. The widget reported the post being viewed, but its settings live in
   the template, so every background request looked for the widget in the wrong 
   post and was refused. It now reports the document it was placed in, and passes
   the displayed post separately.
 * Fixed: “Related to the current post” and “Exclude current post” were ignored 
   from page two onwards. A background request has no current post, so the second
   page was built from a different query than the first.
 * Fixed: the Load More button stayed on screen after the last page. Pressing it
   asked for a page that does not exist and appended the “no posts found” notice
   below the results — the stray line of unstyled text under the grid.
 * Fixed: a card template’s dynamic background image never appeared on the front
   end, though it showed in the editor. Elementor supplies that kind of value once
   per page under one shared selector, which cannot describe nine different posts;
   each card now carries its own scoped rule.
 * Fixed: carousel autoplay could come up dead on an optimised site. Swiper refuses
   to schedule anything while the carousel measures zero, and never tries again —
   so a stylesheet arriving late left autoplay permanently off. It now starts as
   soon as the carousel has a width.
 * Fixed: Load More and the page numbers rendered as bare text, with no border or
   background of their own.
 * New: alignment and padding controls for the paginator, plus a full-width option
   for the Load More button.
 * New: the Load More button’s text can be edited when Infinite Scroll is selected,
   not only Load More.
 * New: alignment control for the excerpt, including justified.
 * New: typography, colour, alignment and spacing controls for the result count.
 * Changed: Clear Filters now takes the same style controls as the rest of the filter
   bar.
 * Changed: with the “Current page query” source, pagination stays as real page 
   links. That query only exists on the page itself and cannot be rebuilt for a 
   background request.

#### 1.4.1

 * Fixed: infinite scroll stopped after the first automatic load. It watched the
   paginator, which is replaced on every page, so the observer was left watching
   a node that was no longer in the document. It now watches a dedicated element
   that is never re-rendered.
 * Fixed: paging links inside an AJAX response pointed at the REST endpoint instead
   of the page, because they were built from the current request. Opening one in
   a new tab produced raw JSON.
 * Fixed: infinite scroll could stall on a tall screen, where the trigger stayed
   on-screen after a page was appended and so never crossed the threshold again.
 * New: the infinite-scroll auto-load limit is a control rather than a fixed value.

#### 1.4.0

 * Fixed: with many pages, the numbered paginator was a trap — the window stayed
   on its first range, so pages beyond it could never be reached. It now shows first
   and last with an ellipsis, and the server re-renders the window on every AJAX
   page change.
 * Fixed: the search box shared the top row with the filters on the front end even
   though it looked right in the editor. It now owns its own full-width row at any
   container width.
 * Fixed: the meta divider stopped where the text stopped, because Content Alignment
   shrink-wraps every child of the card body. It now spans the card.
 * Fixed: Read More sits at the foot of the card, directly above the meta divider,
   instead of wherever the excerpt happened to end.
 * Removed: the Apply button, for anyone with JavaScript. It now exists only inside,
   so it is genuinely absent rather than hidden.
 * New: the search, filters and sort share one container with its own background,
   border, radius, padding and row spacing.
 * New: custom text for the result count, reading time and comment count, each with
   a %s placeholder.
 * New: meta alignment — left, centre, right or spread.
 * Changed: Clear filters is a button, matching the filter controls rather than 
   reading as a stray link.
 * Changed: the carousel’s play/pause button is out of sight by default and appears
   when a keyboard user reaches it. It still cannot be removed while autoplay is
   on.

#### 1.3.0

 * New: sort options are now a repeater. Choose which orders to offer, rename each
   one, and drag to reorder — the first row is the default.
 * Changed: the Apply button is hidden once the script takes over, because results
   already update as a filter changes. It is still rendered for visitors without
   JavaScript, and a new “Apply Automatically” switch turns auto-updating off if
   you prefer an explicit Apply.
 * Fixed: the sort repeater’s row label used a triple brace, which injects user-
   typed text raw into the editor panel.

#### 1.2.2

 * New: excerpts are built from paragraphs only, so headings, list items, captions
   and table cells no longer end up in the card text. Choose “all content” if you
   preferred the old behaviour.
 * New: meta can sit under the title or at the foot of the card, with an optional
   divider you can colour, thicken and space.
 * Changed: headings no longer gain an underline on hover. The Title hover colour
   control is there if you want an affordance.
 * Fixed: the focus ring used currentColor, so on a focused pill or button — where
   the text is white — it was invisible against a light card. It now draws a two-
   tone ring that contrasts on any background, and uses the system Highlight colour
   in forced-colors mode.
 * Fixed: changing Columns in the editor did not re-render the widget, leaving the
   Feature layout’s column span stale until another control forced a redraw.

#### 1.2.1

 * Fixed: the Posts widget never applied the skin class, so Overlay, Overlap, Side,
   Editorial and Minimal all rendered as Classic.
 * Fixed: combining two taxonomy filters silently applied only the first. The AJAX
   endpoint sanitised the filter query with a function that turns “&” into “&”.
 * Fixed: a non-Latin search term was destroyed in transit for the same reason, 
   by a sanitiser that strips percent-encoding.
 * Fixed: theme link styling underlined card titles, term pills and the Read More
   button. Card link rules now out-specify the theme without !important.
 * Added a Respect “Reduce Motion” control to the Posts widget, matching the carousel.

#### 1.2.0

 * New: card templates. Pick any saved Elementor template and it is rendered once
   per post, with that post in context — a loop item without Elementor Pro.
 * New: nine dynamic tags (title, excerpt, date, terms, author, reading time, comment
   count, URL, featured image) so template cards can actually read the post. Free
   Elementor ships none of its own.
 * Rebuilt the Posts filter as a real search-and-filter form: keyword search, several
   taxonomies at once, buttons / checkboxes / dropdowns, a sort control, live result
   count and a Clear button. It is a GET form, so it works with JavaScript off and
   every view has a shareable URL.
 * Fixed: the masonry Row Gap control had no effect, because row-gap does nothing
   on a CSS-columns layout.
 * Fixed: a feature card could span every column, leaving nothing beside it. The
   span is now capped against the column count, per breakpoint.
 * Fixed: cards did not stretch to fill their grid cell, so rows of mixed-length
   posts did not line up.
 * Fixed: the address bar could accumulate duplicate filter parameters.
 * Fixed: the Clear button never appeared after an AJAX update.

#### 1.1.0

 * New **Posts** widget: grid, masonry, list and feature layouts with page numbers,
   previous/next, Load More or infinite scroll.
 * AJAX pagination and an optional taxonomy filter bar, both built on real links
   so they work without JavaScript.
 * Result counts are announced, the grid reports its busy state, and focus moves
   to the first new post after a visitor-initiated load.
 * Card, query and style code now lives in one shared implementation used by both
   widgets.
 * The Posts script is a separate file, so a page with only a carousel never downloads
   it.

#### 1.0.1

 * Fixed a fatal error in the Elementor editor. get_style_depends() read widget 
   settings, but Elementor also calls it on widget types, which have none — producing“
   sanitize_settings(): Argument #1 must be of type array, null given” and a white
   screen. The icon stylesheet is now enqueued at render time instead.

#### 1.0.0

 * Initial release: the Blog Carousel widget with six skins, a full query builder,
   Swiper-based motion, and WCAG 2.2 AA controls.
 * schema.org ItemList output in JSON-LD, with a Zen GEO integration that shares
   node identity instead of duplicating it.

## Meta

 *  Version **1.8.5**
 *  Last updated **1 day ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.5 or higher **
 *  Tested up to **7.0.4**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [blog](https://wordpress.org/plugins/tags/blog/)[carousel](https://wordpress.org/plugins/tags/carousel/)
   [elementor](https://wordpress.org/plugins/tags/elementor/)[posts](https://wordpress.org/plugins/tags/posts/)
   [slider](https://wordpress.org/plugins/tags/slider/)
 *  [Advanced View](https://wordpress.org/plugins/zen-blogger/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/zen-blogger/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/zen-blogger/reviews/)

## Contributors

 *   [ Guram Zhgamadze ](https://profiles.wordpress.org/guramzhgamadze/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/zen-blogger/)