Search and Filter Cards
Combine a search box with auto-generated checkbox and radio filters. The component scans card content and builds filter options automatically from data attributes.
Ahmed Al-Rashidi
Senior DeveloperFatima Al-Harbi
UX DesignerSara Al-Dosari
Marketing LeadLayla Al-Qahtani
HR SpecialistKhalid Al-Otaibi
Project ManagerOmar Al-Shahrani
Data AnalystMohammed Al-Zahrani
Security EngineerNoura Al-Ghamdi
Content StrategistAbdulaziz Al-Shehri
Finance ManagerHana Al-Mutairi
Frontend DeveloperTurki Al-Subaie
Operations LeadReem Al-Tamimi
Visual DesignerAuto-Generated Filter Types
Three filter input types that auto-generate from card content: checkbox (multi-select, OR logic), radio (single-select), and switch (toggle, OR logic)
Task A
Task B
Task C
Task D
Feature Request
Bug Fix
Documentation
Security Patch
App Alpha
App Beta
App Gamma
App Delta
Explicit Values and Label Mapping
Define filter options upfront with data-filter-values instead of scanning card content. Pass a JSON object {"value":"label"} to map machine values to display labels, keeping internal identifiers separate from what users see.
AI Research
Brand Identity
Market Analysis
Cloud Migration
UX Audit
Revenue Report
Dynamic Values (populateFilter API)
Use populateFilter() to generate filter inputs from values fetched at runtime. Supports cascading filters where one filter's selection determines another filter's options.
Place an empty data-filter placeholder in the dropmenu, then call populateFilter() after fetching values. The method generates the same auto-generated inputs as data-filter-type and binds all listeners automatically.
<!-- Empty placeholder — JS will generate the inputs -->
<div class="nds-dropmenu nds-filter" id="apiFilter"
data-filter-target="results">
<button class="nds-btn nds-neutral nds-filter-btn nds-dropmenu-trigger">
<i class="hgi hgi-stroke hgi-filter"></i>
<span class="nds-label">Filter</span>
</button>
<div class="nds-dropmenu-menu" hidden>
<div class="nds-dropmenu-scroll">
<div data-filter="system"
data-filter-type="checkbox"
data-filter-legend="System"
data-no-auto-close>
</div>
</div>
<div class="nds-dropmenu-footer">
<hr class="nds-divider">
<div class="nds-dropmenu-action nds-grid">
<button class="nds-btn nds-secondary nds-dropmenu-item"
type="button" data-filter-action="clear"
data-no-auto-close>
<span class="nds-label">Reset</span>
</button>
<button class="nds-btn nds-primary nds-dropmenu-item"
type="button" data-filter-action="apply">
<span class="nds-label">Apply</span>
</button>
</div>
</div>
</div>
</div>
// Fetch values from API, then populate the filter
NDS.Filter.whenReady('#apiFilter', (filter) => {
fetch('/api/systems')
.then(res => res.json())
.then(data => {
filter.populateFilter('system', data.map(d => d.Title));
});
});
Call populateFilter() again whenever a parent filter changes. The method clears the previous inputs and generates new ones from the updated values.
// Cascading: when beneficiary changes, re-populate system filter
NDS.Filter.whenReady('#apiFilter', (filter) => {
const beneficiaryInputs = document.querySelectorAll(
'input[name="beneficiary"]'
);
beneficiaryInputs.forEach(radio => {
radio.addEventListener('change', () => {
fetch('/api/systems?userIds=' + radio.value)
.then(res => res.json())
.then(data => {
filter.populateFilter(
'system',
data.map(d => d.Title)
);
});
});
});
});
AJAX Form Submission
Send filter criteria to a server endpoint via AJAX. HTML responses are auto-injected into the target container. JSON responses dispatch raw data via event for developer rendering.
Add a separate <form> element with data-filter-target linking it to the filter anchor, plus data-filter-submit and data-ajax attributes. Set the action attribute to the API endpoint URL.
.nds-filter stays a pure anchor — the form drives submission. HTML responses are automatically injected into the target container. For JSON responses, listen for the nds:filterFormComplete event and render the data yourself.
Use preventDefault() on the nds:filterFormAjax event to fully control the AJAX request and rendering. The filter component still handles UI updates (chips, count, URL params) before dispatching the event.
All filter actions (apply, chip removal, reset, clear) fire through nds:filterFormAjax, so you only need one event listener.
// Intercept AJAX and handle fetching yourself
// Covers: apply, chip removal, reset, and clear
filterForm.addEventListener('nds:filterFormAjax', (e) => {
e.preventDefault();
// Build your own params from form inputs
const params = {};
const search = filterForm.querySelector('input[name="search"]');
if (search && search.value) params.q = search.value;
// Fetch from your API
fetch('/api/search', { method: 'POST', body: new URLSearchParams(params) })
.then(res => res.json())
.then(data => renderResults(data.Records));
});
Sort
Drop sort buttons anywhere inside the filter (typically a dropmenu). Each button carries data-sort="{key}" and data-sort-dir="asc|desc"; items expose the sortable value via data-sort-{key}. An empty data-sort resets to the original DOM order.
Zakat Payment
75
Passport Renewal
300
Birth Certificate
25
Identity Verification
Free
Driver License
150
Business Registration
1200
Values are sampled at sort time. Pure numbers (including formatted strings like "9,375") sort numerically; dates in DD/MM/YYYY, YYYY-MM-DD, and ISO 8601 sort chronologically; everything else uses localeCompare.
Sort state persists as ?sort=name&dir=desc. Reload the page or share the URL to restore the same order. Ascending is the default, so dir is only written when descending.
The dropmenu trigger icon automatically mirrors the active sort button's icon. Swap icon sets (NDS inline, HGI font, custom) without any JS changes — the component copies the className at runtime.
Sort buttons can live outside .nds-filter — add data-filter-target="{id}" on each trigger (or on their wrapper) to bind them to a filter whose target matches.
See the Sort component page for the underlying JavaScript API, including NDS.Sort, the nds:sort:change event, and the pure comparator helpers.
Built-in Features
Activates when .nds-filter is on the page. Search, filter inputs, chips, and URL sync are set up automatically.
Builds checkbox, radio, or switch inputs automatically. Values come from card content, a JSON attribute (data-filter-values), or the populateFilter() API — no manual HTML required.
Filter selections and search terms sync to URL query parameters automatically, producing bookmarkable and shareable links that restore the exact filter state.
Active filters display as removable chips below the filter bar. Clicking a chip removes that filter and re-applies the remaining criteria.
Shows a warning alert with a "Clear Filter" action when no cards match the current criteria. The alert dismisses automatically when results reappear.
Use populateFilter() to generate or replace filter inputs at runtime. Supports cascading filters where one selection drives another filter's options via API.
Supports server-side filtering via AJAX with automatic HTML response injection and JSON response events for custom rendering.
Filter any element type by setting data-filter-items on the target container. Works with list items, table rows, drawers, or any custom structure beyond the default .nds-card.
Separate machine values from display labels using data-filter-value on items or the object form of data-filter-values on filter groups. Labels are derived automatically from visible text content.
Set filters, search terms, and reset state through the NDS.Filter API. Access instances by selector, target ID, or the whenReady helper.
Usage Guidelines
Best Practices
- Use client-side filtering when all items are already on the page and the dataset is small enough to load at once (under a few hundred cards)
- Use AJAX form submission mode (
data-filter-submit+data-ajax) for large datasets or when results come from an API endpoint - Use auto-generated filters (
data-filter-type) for quick setup when filter values come directly from card content. Usedata-filter-valuesto supply explicit values when cards don't exist or values differ from card content. UsepopulateFilter()for dynamic or cascading values fetched at runtime - Do not use Filter for navigation menus or hierarchical browsing. Use Side Nav or Tabs instead
- Do not use Filter for single-field search without filter controls. Use the search box from Forms directly
- Choose checkbox for multi-select with OR logic, radio for mutually exclusive single-select, and switch for feature toggles where each option is independent
- Combine a search box with filter controls for the best experience. Search narrows by text while filters narrow by category
- Always include a Reset/Clear button inside the dropmenu footer so users can undo selections before applying
- Add the
.nds-filter-appliedcontainer to show applied filter chips. This gives users visibility into active filters and a quick way to remove individual ones - Keep filter group names short and descriptive. The
data-filter-legendvalue appears as the fieldset heading inside the dropmenu
Data Attributes
Filter Anchor (.nds-filter)
| Attribute | Description |
|---|---|
data-filter-target | ID of the container holding filterable items. Also used to link the anchor to its submission form, search box, applied-chips row, query/count slots, and filter controls. |
Submission Form (separate <form data-filter-target>)
| Attribute | Description |
|---|---|
data-filter-target | Must match the anchor's target id to activate form mode for that filter instance. |
data-filter-submit | Marks this form as the submission form (enables form mode instead of client-side filtering). |
data-ajax | Use AJAX instead of page navigation (requires data-filter-submit). |
Search Input Opt-Out
| Attribute | Description |
|---|---|
data-filter-ignore | Place on a search input (or its ancestor) to prevent the filter from auto-detecting and hijacking it. Useful when a server-side search input lives inside the filter scope but should not be used for client-side text filtering. |
Target Container
| Attribute | Description |
|---|---|
data-filter-items | Set on the target container (the element referenced by data-filter-target) to specify a custom CSS selector for filterable items. Default: .nds-card. Example: data-filter-items=".search-result" to filter non-card elements like list items or table rows. |
data-total-count | Set on the target container by server-side rendering or inside a nds:filterFormComplete handler to provide a server-authoritative result count. When present, overrides the DOM-enumerated count written to [data-filter-count] slots. |
Result Count and Query Slots
| Attribute | Description |
|---|---|
data-filter-count | Place on any element linked via data-filter-target. The filter writes the number of visible items into this element's textContent after every filter pass. Pair with .nds-results-count for the standard styling. |
data-filter-query | Place on any element linked via data-filter-target. The filter writes the active search keyword (wrapped in curly quotes) into this element's textContent. When present, the search term is routed here instead of appearing as an applied-chip. |
Filter Groups
| Attribute | Description |
|---|---|
data-filter="name" | Filter group name. On filter controls, groups inputs together. On item elements, marks filterable content. Can be placed on child elements inside items or on the item itself. |
data-filter-type | Auto-generate inputs. Values: checkbox, radio, or switch. Scans cards for values unless data-filter-values is set. Radio groups auto-prepend an "All" option (selected by default) so the filter can be cleared. |
data-filter-all-label | Override the auto-prepended "All" label on radio groups. Default: الكل in Arabic, All otherwise. |
data-filter-no-all | Opt out of the auto-prepended "All" option on radio groups (boolean attribute). |
data-filter-values | JSON object mapping machine values to display labels, e.g. '{"A":"Label A","B":"Label B"}'. Keys become checkbox/radio values, values become visible text. Also accepts a JSON array ('["A","B"]') which uses raw values as labels. Skips card scanning. Static: not affected by refresh(). Use populateFilter() if values need to change at runtime. Requires data-filter-type. |
data-filter-legend | Fieldset legend text for auto-generated filter groups |
data-filter-variant | CSS class to add to auto-generated input elements (e.g. nds-primary) |
data-filter-value | Set on a [data-filter] element to provide a machine-readable filter value separate from the visible text. The display label is derived from the element's text content automatically. Example: <span data-filter="type" data-filter-value="Announcement">Translated Label</span> |
data-filter-name | Custom display name used in applied filter chips instead of the raw value |
Action Buttons
| Attribute | Description |
|---|---|
data-filter-action="apply" | Apply current filter selections and close the dropmenu |
data-filter-action="clear" | Reset all filter inputs in the dropmenu without closing it |
data-filter-action="reset" | Clear all filters, search, and chips, and show all items |
Applied Filters Container
| Attribute | Description |
|---|---|
data-chip-class | Set on .nds-filter-applied to customize chip styling. Default: nds-primary nds-lg |
Search Box (AJAX mode)
| Attribute | Description |
|---|---|
data-url | Set on .nds-search-box. API endpoint for search autocomplete suggestions |
data-name | JSON field name to display from autocomplete results |
data-query-param | URL query parameter name for the search term |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--dropmenu-min-width | 250px | Minimum width of the filter dropmenu |
JavaScript API
The NDS.Filter API provides methods to create, query, and control filter instances programmatically. For dynamically added filter forms, call NDS.Filter.init() to initialize new instances.