Guides

Client-Side Filtering and Search

MenoFilter is a client-side filtering library built into Meno. It works entirely with HTML data attributes -- no JavaScript required from you. Add attributes to your elements and MenoFilter handles filtering, searching, sorting, and pagination automatically on the static site.


Quick Setup

  1. Wrap a section of your page in a container element. In the Attributes panel, add the attribute data-meno-filter with a value matching your CMS collection name (e.g. "posts").

  2. Inside that container, mark the list wrapper with data-meno-list. This is typically a CMS List node whose output contains the items.

  3. Each rendered item should have data-{field} attributes carrying the item's values (e.g. data-category="tech"). When using a CMS List, set these in the template's attributes using {{item.category}}.

  4. Add filter buttons, search inputs, sort controls, and pagination elements using the data attributes described below.

  5. Build and deploy. MenoFilter initializes automatically on page load.


Container Attributes

These attributes go on the outermost wrapper element.

Attribute

Description

data-meno-filter="name"

Required. Scopes all controls inside to this filter instance. Value is the collection name.

data-meno-list

Marks the element that contains the filterable items.

data-meno-per-page="n"

Items per page. 0 disables pagination (show all).

data-meno-filter-match="and" or "or"

Logic between active filters. Default: "and".

data-meno-debounce="ms"

Debounce delay for search input. Default: 300.

data-meno-active-class="class"

CSS class added to active filter controls. Default: "active".

data-meno-types

JSON mapping field names to types for comparison. Example: '{"price":"number","date":"date"}'

data-meno-url-sync="true"

Sync filter state to URL query parameters.

data-meno-url-mode="push" or "replace"

URL history mode. Default: "replace".


Filter Controls

Buttons

Add data-meno-filter-field and data-meno-filter-value to any clickable element. Clicking toggles the filter on and off.

<button data-meno-filter-field="category" data-meno-filter-value="tech">
  Tech
</button>
<button data-meno-filter-field="category" data-meno-filter-value="design">
  Design
</button>

Filter Mode

Wrap filter buttons in an element with data-meno-filter-mode to control selection behavior:

Mode

Behavior

"single"

Only one value active at a time (radio-like). Clicking a new button deselects the previous one.

"multi"

Multiple values active simultaneously. Values combine with $in (OR within the field).

(default)

Toggle behavior. Clicking the active button deselects it.

<div data-meno-filter-mode="single">
  <button data-meno-filter-field="category" data-meno-filter-value="tech">Tech</button>
  <button data-meno-filter-field="category" data-meno-filter-value="design">Design</button>
</div>

Checkboxes

<label>
  <input type="checkbox"
         data-meno-filter-field="category"
         data-meno-filter-value="tech">
  Tech
</label>
<label>
  <input type="checkbox"
         data-meno-filter-field="category"
         data-meno-filter-value="design">
  Design
</label>

Multiple checked checkboxes for the same field combine with OR logic automatically.

Select Dropdowns

<select data-meno-filter-field="category">
  <option value="">All Categories</option>
  <option value="tech">Tech</option>
  <option value="design">Design</option>
</select>

Setting the value to "", "all", or "*" removes the filter for that field.

Radio Buttons

<input type="radio" name="category"
       data-meno-filter-field="category"
       data-meno-filter-value="*" checked> All
<input type="radio" name="category"
       data-meno-filter-field="category"
       data-meno-filter-value="tech"> Tech
<input type="radio" name="category"
       data-meno-filter-field="category"
       data-meno-filter-value="design"> Design

Range Inputs

For numeric or date ranges, use data-meno-range with data-meno-range-bound:

<input type="number" data-meno-range="price" data-meno-range-bound="min"
       placeholder="Min price">
<input type="number" data-meno-range="price" data-meno-range-bound="max"
       placeholder="Max price">

For date ranges:

<input type="date" data-meno-range="publishDate" data-meno-range-bound="min">
<input type="date" data-meno-range="publishDate" data-meno-range-bound="max">

Important: When using range filters on non-string fields, add type coercion via data-meno-types on the container so comparisons work correctly:

<div data-meno-filter="products"
     data-meno-types='{"price":"number","publishDate":"date"}'>

Search

Add data-meno-search to a text input. MenoFilter performs case-insensitive substring matching (or fuzzy matching if enabled) across item fields.

<input type="text" data-meno-search
       data-meno-search-fields="title,description"
       placeholder="Search...">

Attribute

Description

data-meno-search

Marks this input as a search control

data-meno-search-fields

Comma-separated list of fields to search. If omitted, searches all string fields.

data-meno-search-fuzzy

Enable fuzzy matching (tolerates typos)

data-meno-search-threshold

Fuzzy match sensitivity, 0 to 1. Lower = stricter. Default: 0.3.

The search input respects the data-meno-debounce value set on the container (default 300ms).


Sorting

Sort Buttons

<button data-meno-sort="title">Sort by Title</button>
<button data-meno-sort="publishDate" data-meno-sort-order="desc">
  Newest First
</button>

Clicking a sort button toggles its direction. Clicking again with the opposite direction clears the sort.

Sort Select

<select data-meno-sort>
  <option value="">Default Order</option>
  <option value="title:asc">Title A-Z</option>
  <option value="title:desc">Title Z-A</option>
  <option value="publishDate:desc">Newest First</option>
  <option value="price:asc">Price: Low to High</option>
</select>

The value format is field:order where order is asc or desc.


Pagination

Set data-meno-per-page on the container to enable pagination:

<div data-meno-filter="posts" data-meno-per-page="12">

Then add navigation controls:

<button data-meno-page="first">First</button>
<button data-meno-page="prev">Previous</button>
<span data-meno-page-current></span> / <span data-meno-page-total></span>
<button data-meno-page="next">Next</button>
<button data-meno-page="last">Last</button>

Attribute

Description

data-meno-page="prev"

Go to previous page

data-meno-page="next"

Go to next page

data-meno-page="first"

Go to first page

data-meno-page="last"

Go to last page

data-meno-page="3"

Go to a specific page number

data-meno-page-current

Displays the current page number

data-meno-page-total

Displays the total number of pages

data-meno-page-buttons

Container for auto-generated page number buttons

Per-Page Select

Let the user choose how many items to show per page:

<select data-meno-per-page-select>
  <option value="6">6 per page</option>
  <option value="12" selected>12 per page</option>
  <option value="24">24 per page</option>
</select>

Load More (Alternative to Pagination)

Instead of numbered pages, show a "Load More" button that appends items:

<div data-meno-filter="posts" data-meno-per-page="6">
  <div data-meno-list>...</div>
  <button data-meno-load-more>Load More</button>
  <span data-meno-remaining></span> items remaining
</div>

Attribute

Description

data-meno-load-more

Clicking loads more items. Optional value overrides the increment count.

data-meno-remaining

Displays the number of items not yet shown


Clear and Reset

Attribute

Description

data-meno-clear

Clears all active filters (keeps sort and search)

data-meno-reset

Resets everything: filters, search, sort, and pagination

<button data-meno-clear>Clear Filters</button>
<button data-meno-reset>Reset All</button>

UI Display Elements

Result Counts

<span data-meno-count="results"></span> results found
<span data-meno-count="total"></span> total items
<span data-meno-count="visible"></span> currently visible

Facet Counts

Show how many items exist for a specific field value:

<button data-meno-filter-field="category" data-meno-filter-value="tech">
  Tech (<span data-meno-facet="category:tech"></span>)
</button>

Empty State

An element with data-meno-empty is shown when no items match the current filters, and hidden otherwise:

<div data-meno-empty>
  No results found. Try adjusting your filters.
</div>

Type Coercion

By default, all item data attributes are strings. For correct comparison behavior with numeric or date fields, declare field types on the container:

<div data-meno-filter="products"
     data-meno-types='{"price":"number","stock":"number","createdAt":"date","active":"boolean"}'>

Supported types: string (default), number, date, boolean.


URL Sync

Enable URL parameter synchronization so filter state is preserved when users share links or navigate with the browser back button:

<div data-meno-filter="posts" data-meno-url-sync="true">

The URL updates as filters change. Parameters use the format:

  • filter.category=tech -- simple filter

  • filter.price.$gte=100 -- operator filter

  • sort=publishDate:desc -- sort

  • search=hello -- search query

  • page=2 -- page number


CMS Integration

To add filtering to a CMS List node, wrap it in a filter container:

  1. Add a parent element with data-meno-filter="collection-name".

  2. Inside, place your List node (which generates the items).

  3. On each item's template children, add data-{field} attributes using template expressions like data-category="{{item.category}}".

  4. Add filter controls, search, sort, and pagination elements as siblings of the list.

For client-side rendering beyond the SSR'd limit, enable emitTemplate: true on the List node. MenoFilter uses the emitted <template> to render additional items dynamically without a page reload.


JavaScript API

MenoFilter is also available as a programmatic JavaScript API:

const filter = MenoFilter.get('posts');

filter.filter({ category: 'tech', price: { $gt: 100 } });
filter.search('hello world', ['title', 'description']);
filter.sort('publishDate', 'desc');
filter.nextPage();

filter.on('afterFilter', (items) => console.log(items.length, 'items'));
filter.watch((state) => console.log(state.page.current, 'of', state.page.total));

filter.getFacets('category'); // { tech: 5, design: 3 }
filter.reset();

Next Steps

Building the future of digital experiences, one website at a time

© 2026 Company. All rights reserved.