ClickorKeyboard shortcut Alt + L

Dragto resize

Nord Design System SearchDeveloperDeveloperGetting startedUsing Web ComponentsWebfontsCDNLocalizationCSS Framework Legacy ESLint PluginWorking with AIFAQDesignDesignGetting startedFoundationsAccessibilityColor SystemColor UtilitiesPrinciplesFigma ToolkitGridIconographyNamingNordhealth BrandTypographyThemesBrand assetsToolsComponentsComponentsAccordion New Aside New Autocomplete New AvatarBadgeBanner Updated ButtonButton GroupCalendar Updated Card Updated Chart New Checkbox Updated Collapsible New Combobox New Command MenuData Table New Date Picker Updated Date Range Picker New DividerDrawer Updated DropdownEmpty StateField New FieldsetFilter Bar New FooterHeaderIconInput Updated Input Group New Item New Kbd New LayoutMessageMeter New Modal Updated NavigationNotificationNumber Field New Otp Field New Outline New Overflow List New Pagination New Popout Updated ProgressProgress BarQrcodeRadio Updated Range Updated Resizable New Rich Text Editor New Scroll Area New Segmented ControlSelect Updated SkeletonSpinnerStackTabTableTagTextarea Updated Time Picker New Timestamp New ToastToggle Updated TooltipTop BarTruncate New Visually HiddenDesign TokensDesign TokensTailwind CSSTailwind CSSBlocksBlocksTemplatesTemplatesIconsIconsPlaygroundPlaygroundLatest UpdatesLatest UpdatesChangelogChangelogMigration guidesMigration guides
GitHub

Clickto expandKeyboard shortcut Alt + L

Data Table Filter Bar New Alpha

Open in Storybook

A declarative, config-driven filter toolbar for the Data Table — describe your filters as data and Nord renders the matching controls.

This component is considered alpha. We're still finalising the API, so it may include a breaking change at any time — use it at your own risk.
Loading...

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-change event whenever any control commits, with a detail of { values, changed }values is the full current value map and changed is the key of 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

FieldDescription
keyUnique key — the property this filter maps to in the value map.
labelVisible label on the control / chip.
defaultValueInitial value applied before the consumer supplies values.
optionalThe filter starts hidden and is offered via the add-filter menu.
removableThe filter can be removed from the bar (removal clears its value).
tooltipOptional, non-essential help connected to the control with aria-describedby.

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" }

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

PropertyDescription
filtersThe FilterDescriptor[] to render.
valuesA controlled FilterValues map. When omitted, the bar tracks values internally; when set, your app owns the values.
alignCross-axis alignment forwarded to the inner nord-filter-bar (start / center / end).
forId of the <nord-data-table> this bar controls when used standalone.
collapseBreakpointInline-size in pixels below which segmented and tag-group filters collapse to dropdowns (default 640; attribute collapse-breakpoint).

Events

EventdetailFires 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-resetThe reset affordance is used.
filter-addkeyAn 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 values map (or by leaving the bar uncontrolled) so the current selection is announced on each chip.
  • The underlying Filter Bar controls wire aria-controls / aria-expanded between 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

PropertyAttribute Description TypeDefault
alignalignCross-axis alignment, forwarded to nord-filter-bar.'start' | 'center' | 'end''center'
forforId of the <nord-data-table> this bar controls when used standalone.string | undefined
collapseBreakpointcollapse-breakpointInline-size below which segmented and tag filters use dropdown presentation.number640
EventDetail TypeDescription
search-changeCustomEventAn async dropdown search changed; detail contains its filter key and value.
load-moreCustomEventAn async dropdown requested another options page; detail contains its filter key.
filters-changeNordDataTableFiltersChangeEventA filter committed a change.
filter-resetNordDataTableFilterResetEventThe reset affordance was used.
filter-addNordDataTableFilterAddEventAn optional filter was added from the add-filter menu.