Form
Filters
Usage#
import { WdsFiltersComponent } from '@wds/angular/filters';matchesFilters(), send them to the server, or put the bar in the toolbar slot of a Data Table, which filters its rows by itself.
Examples#
Default#
fields lists what can be filtered; type (text, number, select, date, boolean) decides the operators and the value editor. “Filter” opens a menu of the fields not used yet, and the new filter focuses its value. wds-change gives filters; matchesFilters(row, filters) checks a row against all of them (AND).
6 of 6 tasks
- Write release notes2 pt · 2026-10-06
- Fix login redirect3 pt · 2026-10-04
- Review pricing page1 pt · 2026-09-30
- Migrate billing API8 pt · 2026-10-20
- Update onboarding emails5 pt · 2026-10-12
- Audit color contrast3 pt · 2026-09-28
import { Component, computed, signal } from '@angular/core';
import { WdsFiltersComponent } from '@wds/angular/filters';
import { matchesFilters, type WdsFilter, type WdsFilterField } from '@wds/core/filters';
const TASKS = [
{ title: 'Write release notes', status: 'todo', points: 2, due: '2026-10-06', urgent: false },
{ title: 'Fix login redirect', status: 'in-progress', points: 3, due: '2026-10-04', urgent: true },
{ title: 'Review pricing page', status: 'done', points: 1, due: '2026-09-30', urgent: false },
{ title: 'Migrate billing API', status: 'in-progress', points: 8, due: '2026-10-20', urgent: false },
{ title: 'Update onboarding emails', status: 'todo', points: 5, due: '2026-10-12', urgent: true },
{ title: 'Audit color contrast', status: 'done', points: 3, due: '2026-09-28', urgent: false },
];
@Component({
selector: 'app-filters-default-demo',
host: { class: 'block w-full' },
imports: [WdsFiltersComponent],
template: `
<div class="flex w-full flex-col gap-4">
<wds-filters [fields]="fields" [filters]="filters()" (wdsChange)="filters.set($event.filters)" />
<p class="m-0 text-sm text-muted-foreground">{{ tasks().length }} of {{ total }} tasks</p>
<ul class="m-0 flex list-none flex-col gap-1 p-0 text-sm">
@for (task of tasks(); track task.title) {
<li class="flex justify-between gap-4 rounded-md border border-border px-3 py-2">
<span>{{ task.title }}</span><span class="text-muted-foreground">{{ task.points }} pt · {{ task.due }}</span>
</li>
}
</ul>
</div>
`,
})
export class FiltersDefaultDemo {
protected readonly total = TASKS.length;
protected readonly filters = signal<WdsFilter[]>([]);
// matchesFilters() checks a row against every filter (AND); a filter without a value passes.
protected readonly tasks = computed(() => TASKS.filter((task) => matchesFilters(task, this.filters())));
protected readonly fields: WdsFilterField[] = [
{ key: 'title', label: 'Title' },
{
key: 'status',
label: 'Status',
type: 'select',
options: [
{ value: 'todo', label: 'To do' },
{ value: 'in-progress', label: 'In progress' },
{ value: 'done', label: 'Done' },
],
},
{ key: 'points', label: 'Points', type: 'number' },
{ key: 'due', label: 'Due', type: 'date' },
{ key: 'urgent', label: 'Urgent', type: 'boolean' },
];
}Initial filters and operators#
filters sets the starting filters (such as from the URL); each is { field, operator, value }, and a range (between) has [from, to]. A field's operators limits and orders the operator menu; the first one is used for a new filter. add-label and clear-label change the button texts.
[
{
"field": "total",
"operator": "between",
"value": [
100,
500
]
},
{
"field": "placed",
"operator": "after",
"value": "2026-09-01"
}
]import { JsonPipe } from '@angular/common';
import { Component, signal } from '@angular/core';
import { WdsFiltersComponent } from '@wds/angular/filters';
import type { WdsFilter, WdsFilterField } from '@wds/core/filters';
@Component({
selector: 'app-filters-initial-demo',
host: { class: 'block w-full' },
imports: [WdsFiltersComponent, JsonPipe],
template: `
<div class="flex w-full flex-col gap-3">
<wds-filters add-label="Add filter" clear-label="Reset" [fields]="fields" [filters]="filters()" (wdsChange)="filters.set($event.filters)" />
<pre class="m-0 overflow-x-auto rounded-md bg-muted p-3 text-xs">{{ filters() | json }}</pre>
</div>
`,
})
export class FiltersInitialDemo {
protected readonly fields: WdsFilterField[] = [
{ key: 'customer', label: 'Customer', operators: ['contains', 'is'] },
{ key: 'total', label: 'Total', type: 'number', operators: ['gte', 'lte', 'between'] },
{ key: 'placed', label: 'Placed', type: 'date' },
];
// Filters to start with (such as read from the URL).
protected readonly filters = signal<WdsFilter[]>([
{ field: 'total', operator: 'between', value: [100, 500] },
{ field: 'placed', operator: 'after', value: '2026-09-01' },
]);
}API References#
wds-filters#
fields, and each filter is a chip with the field, an operator
menu, a value editor and a remove button. Filters are joined with AND. In a wds-data-table (slot toolbar)
the table filters its rows by itself; elsewhere use matchesFilters() or send filters to the server.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| label | string | 'Filters' | |
| add-label.addLabel | string | 'Filter' | |
| clear-label.clearLabel | string | 'Clear' | |
| disabled | boolean | false |
Properties
| Property | Description |
|---|---|
| fieldsWdsFilterField[] | WdsFilterField[]). |
| filtersWdsFilter[] | WdsFilter[]), one per field. |
Events
| Event | Description |
|---|---|
| wds-changeCustomEvent<WdsFiltersChangeDetail> |
CSS parts (::part)
| Part | Description |
|---|---|
| chip | |
| add | |
| clear |
Accessibility#
Screen reader#
label (default Filters); every filter is a group named after its field, such as “Status filter”.
select and boolean values are menu buttons; the button name says the current value (symbols such as ≥ are read as “greater than or equal to”). The text, number and date fields are named after the field, and range fields “from” and “to”.
Keyboard support#
| Key | Function |
|---|---|
select value menu stays open, so several options can be checked. | |