Filter - National Design System

A flexible filtering system for narrowing down content using search and auto-generated filter controls. Supports client-side card filtering, static or dynamic filter values, and AJAX form submission

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.

Search + Auto Checkbox Filter

Ahmed Al-Rashidi

Senior Developer
Engineering Senior Developer

Fatima Al-Harbi

UX Designer
Design UX Designer

Sara Al-Dosari

Marketing Lead
Marketing Marketing Lead

Layla Al-Qahtani

HR Specialist
Human Resources HR Specialist

Khalid Al-Otaibi

Project Manager
Engineering Project Manager

Omar Al-Shahrani

Data Analyst
Finance Data Analyst

Mohammed Al-Zahrani

Security Engineer
Engineering Security Engineer

Noura Al-Ghamdi

Content Strategist
Marketing Content Strategist

Abdulaziz Al-Shehri

Finance Manager
Finance Finance Manager

Hana Al-Mutairi

Frontend Developer
Engineering Frontend Developer

Turki Al-Subaie

Operations Lead
Human Resources Operations Lead

Reem Al-Tamimi

Visual Designer
Design Visual Designer

Auto-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)

Checkbox Filter (Multi-Select)

Task A

Active

Task B

Pending

Task C

Complete

Task D

Active
Radio Filter (Single-Select)

Feature Request

High

Bug Fix

Medium

Documentation

Low

Security Patch

High
Switch Filter (Toggle)

App Alpha

SSO API

App Beta

SSO

App Gamma

API Webhooks

App Delta

Webhooks

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.

Explicit Values (Radio)

AI Research

Technology

Brand Identity

Design

Market Analysis

Business

Cloud Migration

Technology

UX Audit

Design

Revenue Report

Business

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.

Populate from API

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.

Cascading Filters

Call populateFilter() again whenever a parent filter changes. The method clears the previous inputs and generates new ones from the updated values.

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.

AJAX Filter Form

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.

Custom AJAX Rendering (preventDefault)

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.

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.

Sort by Name or Price

Zakat Payment

75

Passport Renewal

300

Birth Certificate

25

Identity Verification

Free

Driver License

150

Business Registration

1200

Type auto-detect

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.

URL sync

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.

Trigger icon mirror

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.

External triggers

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

Auto-initialization

Activates when .nds-filter is on the page. Search, filter inputs, chips, and URL sync are set up automatically.

Auto-Generated Filters

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.

Shareable URL State

Filter selections and search terms sync to URL query parameters automatically, producing bookmarkable and shareable links that restore the exact filter state.

Applied Filter Chips

Active filters display as removable chips below the filter bar. Clicking a chip removes that filter and re-applies the remaining criteria.

No Results Alert

Shows a warning alert with a "Clear Filter" action when no cards match the current criteria. The alert dismisses automatically when results reappear.

Dynamic and Cascading Filters

Use populateFilter() to generate or replace filter inputs at runtime. Supports cascading filters where one selection drives another filter's options via API.

AJAX Form Submission

Supports server-side filtering via AJAX with automatic HTML response injection and JSON response events for custom rendering.

Custom Item Selectors

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.

Value and Label Mapping

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.

Programmatic Control

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. Use data-filter-values to supply explicit values when cards don't exist or values differ from card content. Use populateFilter() 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-applied container 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-legend value appears as the fieldset heading inside the dropmenu

Data Attributes

Filter Anchor (.nds-filter)

AttributeDescription
data-filter-targetID 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>)

AttributeDescription
data-filter-targetMust match the anchor's target id to activate form mode for that filter instance.
data-filter-submitMarks this form as the submission form (enables form mode instead of client-side filtering).
data-ajaxUse AJAX instead of page navigation (requires data-filter-submit).

Search Input Opt-Out

AttributeDescription
data-filter-ignorePlace 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

AttributeDescription
data-filter-itemsSet 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-countSet 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

AttributeDescription
data-filter-countPlace 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-queryPlace 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

AttributeDescription
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-typeAuto-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-labelOverride the auto-prepended "All" label on radio groups. Default: الكل in Arabic, All otherwise.
data-filter-no-allOpt out of the auto-prepended "All" option on radio groups (boolean attribute).
data-filter-valuesJSON 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-legendFieldset legend text for auto-generated filter groups
data-filter-variantCSS class to add to auto-generated input elements (e.g. nds-primary)
data-filter-valueSet 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-nameCustom display name used in applied filter chips instead of the raw value

Action Buttons

AttributeDescription
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

AttributeDescription
data-chip-classSet on .nds-filter-applied to customize chip styling. Default: nds-primary nds-lg

Search Box (AJAX mode)

AttributeDescription
data-urlSet on .nds-search-box. API endpoint for search autocomplete suggestions
data-nameJSON field name to display from autocomplete results
data-query-paramURL query parameter name for the search term

CSS Custom Properties

PropertyDefaultDescription
--dropmenu-min-width250pxMinimum 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.

// ── Get a filter instance ─────────────────────────── const filter = NDS.Filter.getInstance('.nds-filter'); const filter = NDS.Filter.getInstance(element); const filter = NDS.Filter.getByTarget('cardList'); // ── Wait for initialization (safe with deferred scripts) ── NDS.Filter.whenReady('.nds-filter', (instance) => { // instance is guaranteed ready // fires immediately if already initialized }); // ── Set filters and search programmatically ───────── filter.setFilterValues('department', ['Engineering', 'Design']); // Check/uncheck existing inputs filter.setSearchValue('Ahmed'); filter.removeFilterValue('department', 'Design'); // ── Generate filter inputs from values (no card scanning) ── filter.populateFilter('system', ['Identity', 'Transport', 'Healthcare']); // checkbox (default) filter.populateFilter('priority', ['High', 'Medium', 'Low'], 'radio'); // radio filter.populateFilter('system', ['New A', 'New B']); // re-calling replaces previous inputs // Note: populateFilter() owns its values — refresh() will not overwrite them // ── Query current state ───────────────────────────── const criteria = filter.getCriteria(); // Returns: { search: 'ahmed', filters: { department: ['Engineering'] } } const visible = filter.getVisibleItems(); // Array of visible card elements const hidden = filter.getHiddenItems(); // Array of hidden card elements // ── Reset and refresh ─────────────────────────────── filter.reset(); // Clear all filters and search, show all items filter.clear(); // Clear all inputs without re-showing items filter.refresh(); // Re-resolve target container, re-scan items, regenerate auto filters // ── Manual control ────────────────────────────────── filter.applyFilters(); // Trigger filtering logic manually filter.submitForm(); // Submit the form (form submission mode only) filter.destroy(); // Show all items and remove initialization flag // Re-apply URL params after dynamically adding filter inputs filter.reapplyUrlParamsForFilter('system'); // ── Static methods ────────────────────────────────── NDS.Filter.init(); // Initialize any new .nds-filter elements on page NDS.Filter.reinit(); // Same as init() NDS.Filter.create(containerEl); // Manually instantiate — returns an NDSFilter instance // ── Events ────────────────────────────────────────── // nds:filter:ready - Filter initialized filterEl.addEventListener('nds:filter:ready', (e) => { const instance = e.detail; // the NDSFilter instance }); // nds:filter:change - Fires after every applyFilters() call, including when // all criteria are cleared (visibleItems === totalItems in that case) filterEl.addEventListener('nds:filter:change', (e) => { const { criteria, totalItems, visibleItems, hiddenItems } = e.detail; }); // nds:filter:reset - All filters cleared via reset() filterEl.addEventListener('nds:filter:reset', (e) => { const { totalItems } = e.detail; }); // nds:filter:clear - Dropmenu clear button clicked filterEl.addEventListener('nds:filter:clear', (e) => { const { filter } = e.detail; }); // ── AJAX mode events ──────────────────────────────── // nds:filterFormSubmit - Before any form submission (cancelable) filterEl.addEventListener('nds:filterFormSubmit', (e) => { const { criteria, form } = e.detail; }); // nds:filterFormAjax - Before AJAX request (cancelable) // Call e.preventDefault() to handle the request yourself filterEl.addEventListener('nds:filterFormAjax', (e) => { const { criteria, form, hiddenInputsContainer } = e.detail; }); // nds:filterFormComplete - AJAX response received filterEl.addEventListener('nds:filterFormComplete', (e) => { const { success, isJson, data, html, form } = e.detail; }); // nds:filterFormError - AJAX request failed filterEl.addEventListener('nds:filterFormError', (e) => { const { error, form } = e.detail; });
Was this page useful?
60% of users said Yes from 2843 Feedbacks