Usage
Data Table Filter Bar renders a row of filter controls from a declarative filters config — it is the filter equivalent of a column model. Each descriptor in the array maps to the matching nord-filter-* control; the bar tracks the value map and emits a single filters-change event whenever a control commits.
Like the rest of the filter family it is headless: it owns no data fetching and no serialization. Your app reads the emitted value map, updates its query/URL state and fetches the matching rows. The bar just describes intent and renders the controls.
It is used embedded by Data Table (set .filters on the table and you get this bar for free), or standalone when you want the bar somewhere other than directly above a table — bind it to a table via the for attribute. If you'd rather compose the individual chips by hand, drop down to the lower-level Filter Bar family instead.
Import the component — this registers the custom element:
import "@nordhealth/components/lib/DataTableFilterBar"
Then describe your filters as an array and assign it as a property (filters is an array, so it can't be an HTML attribute):
<nord-data-table-filter-bar id="filters"></nord-data-table-filter-bar>
<script type="module">
const bar = document.querySelector("#filters")
bar.filters = [
{ type: "search", key: "q", label: "Search", placeholder: "Search patients" },
{
type: "dropdown",
key: "species",
label: "Species",
options: [
{ value: "dog", label: "Dog" },
{ value: "cat", label: "Cat" },
],
},
{ type: "date-range", key: "visited", label: "Last visit" },
]
bar.addEventListener("filters-change", e => {
console.log(e.detail.values) // { q: "bel", species: "dog" }
console.log(e.detail.changed) // "species"
})
</script>
Headless contract
The bar emits intent and nothing else:
- It fires a single
filters-changeevent whenever any control commits, with adetailof{ values, changed }—valuesis the full current value map andchangedis thekeyof the filter that triggered it ("*"on reset). - It owns no fetch, no URL serialization and no request lifecycle. Your app is the source of truth for what "active filters" mean: read
values, mirror it to your query/URL state, and reload your rows.
By default the bar keeps its own copy of the values (uncontrolled) so it just works. Pass a controlled values map to make your app the source of truth — then the bar renders exactly the values you give it and you update them from the event.
let values = { species: "dog" }
bar.values = values
bar.addEventListener("filters-change", e => {
values = e.detail.values
bar.values = values // reflect the new state
reload(values) // your fetch — the bar does not do this for you
})
bar.addEventListener("filter-reset", () => {
values = {}
bar.values = values
reload(values)
})
Composition
Data Table Filter Bar is a thin declarative layer over the Filter Bar family: it reads each descriptor and renders the matching nord-filter-* control inside a nord-filter-bar layout wrapper. Optional filters add a nord-filter-add-button, and a nord-filter-reset-button clears everything at once.
DataTableFilterBar
└── FilterBar
├── FilterInput (type: "search")
├── FilterDropdown (type: "dropdown" | "boolean"; compact choice filters)
├── SegmentedControl (type: "segmented")
├── TagGroup (type: "tag-group")
├── FilterDateRange (type: "date-range")
├── FilterAddButton (optional filters)
└── FilterResetButton
You never author these children yourself — the bar renders them from filters. If you need finer control over the individual chips, compose the Filter Bar family directly instead of this component.
Filter descriptors
The filters property is a FilterDescriptor[]. Every descriptor shares a few base fields and then adds type-specific options.
Shared fields
| Field | Description |
|---|---|
key | Unique key — the property this filter maps to in the value map. |
label | Visible label on the control / chip. |
defaultValue | Initial value applied before the consumer supplies values. |
optional | The filter starts hidden and is offered via the add-filter menu. |
removable | The filter can be removed from the bar (removal clears its value). |
tooltip | Optional, non-essential help connected to the control with aria-describedby. |
search
A free-text search input, rendered as a clearable Filter Input. Set placeholder to hint at what is searched.
{ type: "search", key: "q", label: "Search", placeholder: "Search patients" }
dropdown
A single- or multi-select Filter Dropdown. Pass options ({ value, label }), set multiple for multi-select, searchable for an in-popout search, noResultsMessage to customize its empty search result, and async for a server-driven option list.
{
type: "dropdown",
key: "species",
label: "Species",
searchable: true,
options: [
{ value: "dog", label: "Dog" },
{ value: "cat", label: "Cat" },
{ value: "rabbit", label: "Rabbit" },
],
}
For a server-driven dropdown, set async: the control emits a search-change event (forwarded up from the bar) and you assign fresh options in response, instead of filtering locally.
Dependent filters
Dependent-filter orchestration is application-owned. If a legacy configuration uses parentName, keep that relationship in the application rather than adding request semantics to the design-system descriptor. Listen for filters-change, clear or reload the dependent value when its parent changes, and replace the child descriptor's options with the allowed choices. For server-backed option search, use the forwarded search-change event in the same way.
This keeps the bar events-only: it renders the descriptors and value map it receives, while the application decides dependency rules, loading, caching and error handling.
date-range
A Filter Date Range. The value is an ISO-8601 interval (YYYY-MM-DD/YYYY-MM-DD). Constrain the calendar with min / max, and use presets to show the preset list.
{ type: "date-range", key: "admitted", label: "Admitted", presets: true }
boolean
A yes/no filter, rendered as a two-option Filter Dropdown. Override the option labels with trueLabel / falseLabel (the defaults are localized). The committed value is a real boolean.
{ type: "boolean", key: "insured", label: "Insured", trueLabel: "Yes", falseLabel: "No" }
segmented
A native Segmented Control for a short, single-choice option set. Pass options as { value, label }. Below the bar's collapseBreakpoint, it automatically uses a single-select Filter Dropdown so the choices fit narrow layouts.
{
type: "segmented",
key: "size",
label: "Size",
options: [
{ value: "s", label: "Small" },
{ value: "m", label: "Medium" },
{ value: "l", label: "Large" },
],
}
tag-group
A native selectable Tag Group for multiple choices. Pass options as { value, label }; the committed value is an array. Below the bar's collapseBreakpoint, it automatically uses a multi-select Filter Dropdown.
{
type: "tag-group",
key: "tags",
label: "Tags",
options: [
{ value: "vip", label: "VIP" },
{ value: "senior", label: "Senior" },
{ value: "new", label: "New" },
],
}
custom
A fully custom filter — you provide the control template via render. Use this when the built-in types don't cover your case.
{ type: "custom", key: "score", label: "Score", render: () => html`<my-range-filter></my-range-filter>` }
The TypeScript types — FilterDescriptor, SearchFilterDescriptor, DropdownFilterDescriptor, DateRangeFilterDescriptor, BooleanFilterDescriptor, SegmentedFilterDescriptor, TagGroupFilterDescriptor, CustomFilterDescriptor and FilterValues — are all exported from @nordhealth/components.
Properties
| Property | Description |
|---|---|
filters | The FilterDescriptor[] to render. |
values | A controlled FilterValues map. When omitted, the bar tracks values internally; when set, your app owns the values. |
align | Cross-axis alignment forwarded to the inner nord-filter-bar (start / center / end). |
for | Id of the <nord-data-table> this bar controls when used standalone. |
collapseBreakpoint | Inline-size in pixels below which segmented and tag-group filters collapse to dropdowns (default 640; attribute collapse-breakpoint). |
Events
| Event | detail | Fires when |
|---|---|---|
filters-change | { values, changed } | Any filter control commits a change. values is the full current value map; changed is the key of the filter that changed ("*" on reset). |
filter-reset | — | The reset affordance is used. |
filter-add | key | An optional filter is revealed from the add-filter menu. |
Examples
All filter types
Every descriptor the bar can render — search, single- and multi-select dropdowns, a boolean, a date range, a segmented control and a tag group — in one bar.
Optional filters
Mark a descriptor optional and it starts hidden, offered instead through the add-filter menu (a nord-filter-add-button). Adding one fires filter-add; the reset button clears every active filter and re-hides the optional ones.
Standalone with a table
Give the bar a for attribute pointing at a <nord-data-table> id and it forwards its events to that table even when it lives elsewhere in the DOM — so you can place the toolbar wherever the layout needs it.
Usage with plain JS (jQuery / Django)
Render the empty element from your template, assign filters from a script, and react to filters-change by fetching. This pairs naturally with a Django page that ships its filter options as JSON.
<nord-data-table-filter-bar id="filters"></nord-data-table-filter-bar>
<nord-data-table caption="Patients" id="patients" manual row-count="0"></nord-data-table>
<script type="module">
const bar = document.querySelector("#filters")
const table = document.querySelector("#patients")
bar.filters = [
{ type: "search", key: "q", label: "Search", placeholder: "Search patients" },
{
type: "dropdown",
key: "species",
label: "Species",
options: [
{ value: "dog", label: "Dog" },
{ value: "cat", label: "Cat" },
],
},
]
async function reload(values) {
const res = await fetch("/api/patients/?" + new URLSearchParams(values))
const page = await res.json()
table.data = page.results
table.rowCount = page.count
}
bar.addEventListener("filters-change", e => reload(e.detail.values))
bar.addEventListener("filter-reset", () => reload({}))
</script>
With jQuery the wiring is the same — reach the element and bind the event:
const bar = $("#filters")[0]
bar.filters = filterConfig
$(bar).on("filters-change", (e) => {
reload(e.originalEvent.detail.values)
})
Usage with Vue
Bind filters (and the optional controlled values) as properties and listen for the kebab-case events with @.
<script setup lang="ts">
import { ref } from "vue"
import type { FilterDescriptor, FilterValues } from "@nordhealth/components"
import "@nordhealth/components/lib/DataTableFilterBar"
const filters: FilterDescriptor[] = [
{ type: "search", key: "q", label: "Search", placeholder: "Search patients" },
{
type: "dropdown",
key: "species",
label: "Species",
searchable: true,
options: [
{ value: "dog", label: "Dog" },
{ value: "cat", label: "Cat" },
],
},
]
const values = ref<FilterValues>({})
function onChange(e: CustomEvent<{ values: FilterValues; changed: string }>) {
values.value = e.detail.values
reload(values.value)
}
function onReset() {
values.value = {}
reload(values.value)
}
async function reload(v: FilterValues) {
await fetch(`/api/patients/?${new URLSearchParams(v as Record<string, string>)}`)
// assign the results to your <nord-data-table>'s .data here
}
</script>
<template>
<nord-data-table-filter-bar
.filters="filters"
.values="values"
@filters-change="onChange"
@filter-reset="onReset"
/>
</template>
Accessibility
- Each control gets its accessible name from the descriptor's
label, so always provide one. - The bar emits intent only — make sure the active-filter state your app maintains in response is reflected back via the controlled
valuesmap (or by leaving the bar uncontrolled) so the current selection is announced on each chip. - The underlying Filter Bar controls wire
aria-controls/aria-expandedbetween each chip and its popout, and the popouts dismiss with Escape or an outside click. - The reset affordance clears every filter at once; it stays available so keyboard users can reset without reaching for each chip.
API reference
DataTableFilterBar
Data Table Filter Bar renders a row of filter controls from a declarative
filters config — the filter equivalent of a column model. Each descriptor
maps to the matching nord-filter-* control; the bar tracks the value map and
emits a single filters-change event. It owns no data fetching or
serialization. Used embedded by Data Table, or
standalone (bind to a table via for=).
<nord-data-table-filter-bar></nord-data-table-filter-bar>Props
| Property | Attribute | Description | Type | Default |
|---|---|---|---|---|
align | align | Cross-axis alignment, forwarded to nord-filter-bar. | 'start' | 'center' | 'end' | 'center' |
for | for | Id of the <nord-data-table> this bar controls when used standalone. | string | undefined | — |
collapseBreakpoint | collapse-breakpoint | Inline-size below which segmented and tag filters use dropdown presentation. | number | 640 |
| Event | Detail Type | Description |
|---|---|---|
search-change | CustomEvent | An async dropdown search changed; detail contains its filter key and value. |
load-more | CustomEvent | An async dropdown requested another options page; detail contains its filter key. |
filters-change | NordDataTableFiltersChangeEvent | A filter committed a change. |
filter-reset | NordDataTableFilterResetEvent | The reset affordance was used. |
filter-add | NordDataTableFilterAddEvent | An optional filter was added from the add-filter menu. |
Dependencies
This component is internally dependent on the following components:
- <nord-filter-bar>
FilterBar
Filter Bar is a layout wrapper for a row of filter controls. It lays its children out in a wrapping flex row with a consistent gap, so you can drop in Filter Field, Filter Input, Filter Add Button and Filter Reset Button (or any controls) and have them align without extra styling. Like Pagination, it is composition-first: it owns no filter state and emits no events. Your app owns the active filters and any URL or query-string syncing; the filter primitives only emit intent.
- <nord-input>
Input
Inputs are used to allow users to provide text input when the expected input is short. As well as plain text, Input supports various types of text, including passwords and numbers.
- <nord-segmented-control>
SegmentedControl
Segmented control is used to pick one choice from a set of closely related choices, and immediately apply that selection.
- <nord-tag>
Tag
Tags represent a set of keywords that help label, categorize, and organize objects. Commonly used to signify the attributes of an object.
- <nord-tooltip>
Tooltip
Tooltips are floating containers for displaying additional information for the currently focused element. A tooltip can be useful when you want to e.g. give a hint about an existing Command Menu shortcut.
- <nord-visually-hidden>
VisuallyHidden
Visually hidden is used when an element needs to be available to assistive technologies like screen readers, but be otherwise hidden.