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
Wrap a section of your page in a container element. In the Attributes panel, add the attribute
data-meno-filterwith a value matching your CMS collection name (e.g."posts").Inside that container, mark the list wrapper with
data-meno-list. This is typically a CMS List node whose output contains the items.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}}.Add filter buttons, search inputs, sort controls, and pagination elements using the data attributes described below.
Build and deploy. MenoFilter initializes automatically on page load.
Container Attributes
These attributes go on the outermost wrapper element.
Attribute | Description |
|---|---|
| Required. Scopes all controls inside to this filter instance. Value is the collection name. |
| Marks the element that contains the filterable items. |
| Items per page. |
| Logic between active filters. Default: |
| Debounce delay for search input. Default: |
| CSS class added to active filter controls. Default: |
| JSON mapping field names to types for comparison. Example: |
| Sync filter state to URL query parameters. |
| URL history mode. Default: |
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 |
|---|---|
| Only one value active at a time (radio-like). Clicking a new button deselects the previous one. |
| Multiple values active simultaneously. Values combine with |
(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"> DesignRange 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 |
|---|---|
| Marks this input as a search control |
| Comma-separated list of fields to search. If omitted, searches all string fields. |
| Enable fuzzy matching (tolerates typos) |
| Fuzzy match sensitivity, |
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 |
|---|---|
| Go to previous page |
| Go to next page |
| Go to first page |
| Go to last page |
| Go to a specific page number |
| Displays the current page number |
| Displays the total number of pages |
| 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 |
|---|---|
| Clicking loads more items. Optional value overrides the increment count. |
| Displays the number of items not yet shown |
Clear and Reset
Attribute | Description |
|---|---|
| Clears all active filters (keeps sort and search) |
| 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 visibleFacet 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 filterfilter.price.$gte=100-- operator filtersort=publishDate:desc-- sortsearch=hello-- search querypage=2-- page number
CMS Integration
To add filtering to a CMS List node, wrap it in a filter container:
Add a parent element with
data-meno-filter="collection-name".Inside, place your List node (which generates the items).
On each item's template children, add
data-{field}attributes using template expressions likedata-category="{{item.category}}".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
CMS -- Set up collections to filter
Lists -- Display content with List nodes
Internationalization -- Localize filtered content
Deployment -- Build and deploy