The advanced filter for sui-data-grid, sui-data-table and sui-tree-table: conditions on any column, joined by And or Or, in groups nested up to max-depth, moved by their grips and copied with Duplicate. Edits stay a draft until Apply. It sits outside the rows: in a grid's header slot, in a toolbar with target, or inline in a drawer.
import { Component } from "@angular/core";
import { SuiFilterBuilder } from "@shineui/angular";
@Component({
standalone: true,
imports: [SuiFilterBuilder],
template: `<sui-filter-builder size="sm" [(open)]="open" [columns]="columns" [value]="value" (suiChange)="onChange($event)"></sui-filter-builder>`,
})
export class Example {
open = false;
columns = []; // SuiGridColumn[] | undefined
value = []; // SuiGridFilterModel | undefined
onChange(e: CustomEvent) { console.log(e.detail); }
}
In a grid
In the header slot of sui-data-grid the builder filters the grid it sits in, beside the Columns button. The count on the button is every condition applied. Conditions one column can hold go back into the column filters; the rest, such as an Or across columns, stay in the model as tree.
// setup(root): properties and events
setup(root) {
const g = root.querySelector('#fb-grid');
g.columns = columns();
g.rows = deals(4000);
// The prop rather than setFilter(): a method does not exist before the grid loads, so only
// the prop reaches a grid rendered on a server.
g.filterModel = warning();
}
Pointed at a table
target takes the id of a grid or a table, so the builder can live in any toolbar on the page. It writes through the table's setFilter method, which emits suiFilter just as the table's own filter controls do.
inline draws the builder in place with its Apply bar. Below 560px of its own width each condition stacks its column, condition and value beside the joiner. Without a target it edits value and reports suiChange, so it can drive a server query.
Apply to see the model.
<div style="display: grid; gap: 12px; max-width: 420px">
<sui-filter-builder inline id="fb-inline"></sui-filter-builder>
<code id="fb-json" style="font: 460 12px/1.6 var(--font-sans); color: var(--ink-muted); word-break: break-word">Apply to see the model.</code>
</div>
On the Filters button, opens the builder and moves focus to the first column select.
Tab
Walks the grip, joiner, column, condition, value, duplicate and remove controls of each row, then Add condition, Add group, Clear all and Apply. Past the last control it closes the popover and carries on from the button.
Enter in a text or number field
Applies the draft.
SpaceEnter on a grip
Lifts the condition or group. Pressed again, drops it where it is now.
Up ArrowDown Arrow
Moves a lifted row one place, into a group and out of it as it passes. Home and End move it to the first or last place.
Escape
Puts a lifted row back where it was. Otherwise closes an open list or calendar first, then the popover, without applying; focus returns to the Filters button.
API
<sui-filter-builder>
Properties
Property
Attribute
Type
Default
Description
columns
property only
SuiGridColumn[]SuiGridColumn[] | undefined
The columns to offer. Unset, the target's columns that take filter: true, or all of them when none does. Property, not attribute.
inline
inline
boolean
false
Draws the builder in place, with its Apply bar, instead of a Filters button and a popover: for a drawer, a bottom sheet or a settings page. Below 560px of its own width it stacks each condition's fields.
label
label
stringstring | undefined
The trigger's text. Defaults to "Filters".
maxDepth
max-depth
number
2
How deep groups nest: 0 allows none, 2 (the default) allows a group inside a group, the three levels Notion and Airtable offer.
open
open
boolean
false
Shows the popover. Mutable; the element opens and closes itself, closing on Apply, outside pointer and Escape, each through the cancelable suiClose.
size
size
"md" | "sm"
'md'
The trigger's height: sm 28 beside a grid's Columns button, md 34 in a page toolbar.
target
target
HTMLElement | string | undefined
The grid or table this builder filters: the element, or its id. Unset, it filters the grid or table it sits in, which is how the toolbar slot of sui-data-grid uses it. Without either it edits value alone.
value
property only
SuiGridFilterModelSuiGridFilterModel | undefined
The applied filter when there is no target. With a target it mirrors the target's filterModel. Mutable: Apply and Clear all write it before suiChange fires. A write from the host at any time, v-model and ngModel included, redraws the builder's conditions from it, unless it brings back the filter the builder already holds. Property, not attribute.
Events
Event
Detail
React
Vue
Description
suiChange
SuiGridFilterModel
onSuiChange
@sui-change
Fires on Apply and on Clear all; detail is the whole filter model now applied.
Fires before the popover closes; detail is why. Cancel it to keep the popover open.
suiOpen
void
onSuiOpen
@sui-open
Fires when the popover opens.
Methods
Method
Signature
Description
refresh()
refresh() => Promise<void>
Reads the applied filter again, after the target's filter was set from script.
setFocus()
setFocus() => Promise<void>
Focuses the Filters button, or, when inline, the builder's first control. el.focus() on the host does the same.
Types
SuiCloseReasontypeWhy a dismissible surface is closing. Detail of the cancelable suiClose. A consumer expresses "a stray backdrop click must not close this" by calling preventDefault() on the reason it does not accept, which is why none of these needs a prop of its own.
No members.
SuiGridAligntypeCell text alignment. A side named here is a side of the screen in either reading direction. Left unset, a money or number column sits at the inline end and everything else at the inline start, so the default mirrors under dir="rtl".
No members.
SuiGridCellValuetypeWhat a cell renderer may return. A string is text; an element is appended to the cell. Shared with utils/render-content.ts, which does the mounting.
No members.
SuiGridColumninterfaceOne column. key is both the field read from the row and the column's identity, so it must be unique across the set; field overrides the field when the two need to differ (two columns over the same field, or a computed column with no field at all).
key
string
Unique identity, and the row field read unless field or value says otherwise.
header
string
Column heading.
headerSr?
string
Heading read by assistive technology when header is empty: an actions column shows no words and still names itself. Ignored when header has text.
field?
string
Row field to read, when it is not key.
value?
(row: SuiGridRow) => unknown
Computed value for sorting, filtering and export. Takes precedence over field; a column with only a render and no value sorts and exports by its raw field, which is usually wrong.
Cell content. A string renders as text; an element is mounted into the cell. Called only for cells inside the rendered window. Property, not attribute.
type?
SuiGridValueType
Drives the default comparator, the default filter match modes and the default alignment.
align?
SuiGridAlign
A side of the screen to align to, in either reading direction. Leave it unset for reading order: text at the inline start, numbers and money at the inline end.
money?
boolean
Aligned to the inline end, tabular numerals, weight 640. Implies type: 'number' for comparison unless the column says otherwise, because a money column holds an amount.
nowrap?
boolean
One line, clipped with an ellipsis, and the full value on the cell's title.
width?
number
Starting width in px. Omit to let flex share the space.
minWidth?
number
maxWidth?
number
flex?
number
Share of the space left after fixed widths. flex: 1 on two columns splits it evenly.
sortable?
boolean
resizable?
boolean
Draggable edge on the header. Off by default; a resized column drops its flex.
hidden?
boolean
Excluded from the render, from export and from the keyboard, but kept in the model so a column toggle can bring it back without the caller rebuilding the array.
pin?
SuiGridPin
Pins the column to an edge. Pinned columns are never windowed away.
compare?
(a: unknown, b: unknown) => number
Custom comparator; overrides type. Return negative, zero or positive.
format?
(value: unknown, row: SuiGridRow) => string
Formats the value for text: the cell when there is no render, the export, and the clipboard. Keep it cheap; it runs once per visible cell per render. Without it a number or money column shows a plain figure grouped and marked in the page's locale (1,234.5, 1.234,5), while the export, the clipboard and an edit keep the plain figure (1234.5).
muted?
boolean
Drawn in --ink-muted: a memo, an account type, anything secondary.
headerTitle?
string
Header tooltip, for a column whose heading had to be short.
editable?
boolean
Opts a column out of in-cell editing on a grid that has it on. A column that draws its own cell is never editable: there is no text for an editor to stand in for.
Extra classes for this column's cells, per row. Called once per visible cell per render, so keep it cheap: return a stable string rather than building one. The grid's own classes are always applied; this adds to them.
filter?
boolean
Offers this column a filter control, in the filter row or the filter menu. Ignored while the element's filterDisplay is none.
matchMode?
SuiGridMatchMode
Match mode the column's filter opens with. Defaults by type: contains for text, equals for numbers and booleans, dateIs for dates.
The values a status-like column holds. With them, the column's filter menu is a checklist of these values, writing an in rule, instead of a list of conditions. A bare string is its own value and label. The filter row ignores them and keeps its text box.
editor?
SuiGridEditor
The control this column opens for editing. A plain text input when it is absent. Named by value rather than by a string, so an application that edits only text never carries the date picker: see the note at the top of editing.ts.
validator?
SuiGridValidator
Checked when an edit commits. Returning a message refuses the commit and keeps the editor open with the message beside it.
SuiGridEditorinterfaceA control the grid mounts into a cell, focuses, and reads back when the edit commits. In sui-data-grid focus leaving the control commits, and focus in the list or calendar the control opened has not left it, so a ShineUI picker works as an editor.
mount
(ctx: SuiGridEditorContext) => HTMLElement
Builds the control, populated and ready. The grid mounts it in the cell and focuses it.
read
(el: HTMLElement) => string
The text to commit, read from the element mount returned.
handlesKey?
(e: KeyboardEvent, el: HTMLElement) => boolean
Optional: keys this editor consumes itself, so the grid does not also act on them.
SuiGridEditorContextinterfaceWhat an editor is given when it opens.
value
unknown
The raw value from the row.
text
string
What the cell was showing, which is what a text editor seeds with.
column
SuiGridColumn
row
SuiGridRow
seed?
string
Seed text when the editor was opened by a printable keystroke, else undefined.
SuiGridFilterConditioninterfaceOne condition of an advanced filter: a column, a mode and a value, the same test a constraint makes, with the column it reads named beside it.
key
string
matchMode
SuiGridMatchMode
value?
unknown
SuiGridFilterConstraintinterfaceOne test. value carries whatever the mode needs: a scalar, an array for in and notIn, a two-element [from, to] for between, and nothing at all for empty and notEmpty.
value?
unknown
matchMode
SuiGridMatchMode
SuiGridFilterGroupinterfaceConditions and groups joined by one operator, to any depth: "Owner is Davon, and any of (health is Critical, no next meeting, no interaction)". What sui-filter-builder edits, and the one shape in the model that can join two columns with or.
operator
'and' | 'or'
items
(SuiGridFilterCondition | SuiGridFilterGroup)[]
SuiGridFilterModelinterfaceThe whole filter state. Rules are keyed by column so a header can ask "is this column filtered" in one lookup; the global term is a sibling rather than a magic key inside the map, because it is a different shape and a map that holds two shapes cannot be typed. tree holds what only a tree can say, conditions across columns joined by or or nested; the three are joined by and.
rules?
Readonly<Record<string, SuiGridFilterRule>>
global?
SuiGridGlobalFilter
tree?
SuiGridFilterGroup
SuiGridFilterRuleinterfaceEvery constraint on one column, and how they combine. and is the default a filter menu opens with; or is what a user reaches for when one column can hold two acceptable answers.
key
string
The column this rule filters. It is repeated inside the rule, rather than being left to the map key in SuiGridFilterModel, because a rule also travels alone in an event detail and in saved state, where there is no map key to read. The evaluator reads this field, so the two can never quietly disagree about which column a rule applies to.
operator
'and' | 'or'
constraints
SuiGridFilterConstraint[]
SuiGridGlobalFilterinterfaceOne term searched across several columns at once. keys is required rather than defaulting to every column, for the same reason PrimeVue makes globalFilterFields explicit: a computed column's value function would otherwise be called once per row per keystroke without anyone having asked for it.
value
string
keys
readonly string[]
matchMode?
SuiGridMatchMode
Defaults to contains.
SuiGridMatchModetypeHow one constraint tests a value. Declared here rather than in filter.ts because a column names one and every element reads it, which is what this file is for; the evaluator that implements them is in filter.ts, which re-exports this so both can be imported together. The first sixteen are PrimeVue's FilterMatchMode verbatim. Three are additions, and they are additions rather than oversights: notIn because in without its negation is an odd half of a pair, and empty / notEmpty because "rows with no memo" is the filter a ledger reaches for most and PrimeVue cannot express it at all (an empty filter value means "no filter" there, so emptiness is exactly the one thing a value-carrying constraint can never ask about).
No members.
SuiGridPintypeWhich edge a column is pinned to, if any. The grid renders a pane per edge; the table uses position: sticky cells, because a native table cannot be split into panes.
No members.
SuiGridRowtypeA row is the host's own record. The elements read fields by column key, print them and compare them; they never assume a shape beyond that.
No members.
SuiGridValidatortypeReturns null when the value is acceptable, or the message to show when it is not.
No members.
SuiGridValueTypetypeHow a column's values are compared, formatted and filtered when the column does not say otherwise. text is the fallback for anything unrecognised.
No members.
SuiKeytypeA record's or an item's key wherever the library identifies one: a row, a tree node, a list item, a selected value. Numbers stay numbers, so a numeric id round-trips through selection, the events and a framework binding without turning into a string.
No members.
Theming
Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.