A chart of accounts or any outline with columns. Branches open and close, siblings sort at every level, a filter keeps the ancestors of its matches, a tick carries down a subtree, and a branch can load its children on open. aria-label or aria-labelledby on the host names it. Use sui-tree for rows that are labels, not records.
columns and nodes are properties; a node carries its record in data, which the columns read by key. The first column indents by level and carries the twisty, and a branch row is drawn heavier than the rows under it. aria-label or aria-labelledby on the host names the treegrid.
<sui-tree-table aria-label="Chart of accounts"></sui-tree-table>
Ticking a branch ticks everything under it, and a branch whose children are only partly ticked draws a mixed box. selection holds only the keys the user ticked, because every ancestor is derived from the tree on each render, and suiSelectionChange carries it.
A sortable header orders siblings at every level and leaves the tree's shape alone; sort-indicator all draws every glyph at rest. filter-display row puts a box under each header; a filter keeps and opens the ancestors of its matches, and one that matches nothing shows empty-filter-message.
A node marked leaf false draws the twisty with nothing under it and emits suiLoad the first time it opens. The host answers by setting that node loading, which turns the twisty into a spinner, and then its children.
expandAll and collapseAll are methods, and expanded is written before suiExpandedChange fires. It does not bubble, so listen on the tree table itself. Writing it from outside opens a branch without asking the host for anything, since the caller already knows which.
loading draws four shimmer rows in place of the tree, which is the whole tree waiting rather than one branch. With no nodes the table shows empty-message; a filter that matches none shows empty-filter-message instead, since an empty tree and a search that found nothing are two states.
<div style="display:grid;gap:16px">
<sui-tree-table id="tt-load" loading></sui-tree-table>
<sui-tree-table id="tt-empty" empty-message="No accounts in this range."></sui-tree-table>
</div>
Enters the table at the focused row, else the first. The rows are one tab stop.
ArrowDownArrowUp
Moves the focused row to the next or previous visible row.
ArrowRight
Opens a closed branch. On a branch that is already open, moves to its first child. Under dir="rtl" the pair swaps, so the arrow that points along the reading direction is always the forward one.
ArrowLeft
Closes an open branch. Otherwise moves to the parent row.
HomeEnd
Focuses the first or the last visible row.
PageUpPageDown
Moves the focused row by one viewport.
Space
Ticks or clears the focused row, unless the selection mode is none.
Shift+click
On a header, adds a sort term when sort-mode is multiple.
Escape
Closes the filter menu and returns focus to its funnel button.
API
<sui-tree-table>
Properties
Property
Attribute
Type
Default
Description
columns
property only
SuiDataTableColumn[]
[]
Column definitions, exactly as sui-data-table takes them: key is the field read from a node's data, and the first column carries the indent and the twisty. Property, not attribute.
Text for a tree whose nodes the filter row, the filter menu or globalFilter narrowed to none. An empty list and a search that found nothing are two states, so this is a second prop; an empty string silences it the same way.
emptyMessage
empty-message
stringstring | undefined
Text for an empty tree. An empty string draws the empty row without words, for a host that says it once above the table.
expanded
property only
SuiKey[]
[]
Keys of the open branches. Mutable; updated before suiExpandedChange fires. While a filter is on, the ancestors of its matches open as well without being written here. Property, not attribute.
filterDisplay
filter-display
"menu" | "none" | "row"
'none'
Where the filter controls live: nowhere, in a row under the headers, or behind a funnel button on each header.
filterMode
filter-mode
SuiGridTreeFilterMode"lenient" | "strict"
'lenient'
What a filter does below a match. lenient keeps a matching branch's whole subtree, so opening it shows the branch as it really is; strict keeps only the descendants that match in their own right. An ancestor of a match is kept either way, or the match is unreachable.
filterModel
property only
SuiGridFilterModelSuiGridFilterModel | undefined
The filter model: one rule per column plus an optional global term, as sui-data-table takes it. Mutable; the filter row and the filter menu rewrite it before suiFilter fires. Property, not attribute.
globalFilter
global-filter
stringstring | undefined
One term searched across several columns at once. Set it from a search box of your own; it is merged into filterModel as the global term.
globalFilterFields
property only
string[]string[] | undefined
Columns the global term searches. Defaults to every column. Property, not attribute.
gridlines
gridlines
boolean
false
Draws a rule between columns as well as between rows.
loading
loading
boolean
false
Renders four shimmer rows in place of the tree. A single branch waiting on its children uses that node's own loading instead. Reflected, so a page can style the wait.
nodes
property only
SuiGridTreeNode[]
[]
The node tree (key, data, children). data is the record the columns read, so a column written for a flat table works here unchanged; leaf: false marks a branch whose children have not arrived. Property, not attribute.
nullOrder
null-order
SuiGridNullOrder"first" | "last"
'last'
Where blanks land among their siblings, in both directions.
selection
property only
SuiKey[]
[]
The keys the user has ticked. Under checkbox a branch is stored only when it was ticked whole: the mixed and fully ticked states of every ancestor are derived from the tree on each render, so there is no second copy to go stale when the tree changes shape. Mutable; updated before suiSelectionChange fires. Property, not attribute.
selectionMode
selection-mode
"checkbox" | "none" | "single"
'none'
none, single (one highlighted row) or checkbox (a box per row, a select-all box in the header, and a tick that carries down a whole subtree).
sortIndicator
sort-indicator
"all" | "sorted"
'sorted'
Which sortable headers draw a glyph at rest, exactly as sui-data-table takes it: sorted draws the arrow on the sorted column only and the rest appear on hover and focus; all draws a glyph on every sortable header.
sortMode
sort-mode
"multiple" | "single"
'single'
Whether a header click replaces the sort or adds to it. Under multiple a Shift+click adds a term; under either, a third click on a column drops it.
sorts
property only
SuiGridSort[]
[]
Sort terms, applied in order. Siblings are ordered at every level and the shape of the tree is left alone, so a sorted branch keeps its children. Mutable; a header click rewrites it before suiSort fires. Property, not attribute.
striped
striped
boolean
false
Tints every second row.
Events
Event
Detail
React
Vue
Description
suiExpandedChange
SuiKey[]
onSuiExpandedChange
@sui-expanded-change
Fires when a branch opens or closes; detail is the array of open keys. Does not bubble.
suiFilter
SuiGridFilterModel
onSuiFilter
@sui-filter
Fires when the filter row or the filter menu changes the filter; detail is the whole model. Does not bubble.
suiLoad
SuiGridTreeNode
onSuiLoad
@sui-load
Asks the host for a branch's children: fires when a node marked leaf: false that has none yet and is not already loading opens. Detail is the node. Answer by setting its loading, then its children, and assigning a new nodes array. Writing expanded from outside opens a branch without asking, since the caller already knows which.
suiSelectionChange
SuiKey[]
onSuiSelectionChange
@sui-selection-change
Fires when the selection changes; detail is the new selection, the ticked keys. Does not bubble.
suiSort
SuiGridSort[]
onSuiSort
@sui-sort
Fires when a header click changes the sort; detail is the new ordered term list. Does not bubble.
Methods
Method
Signature
Description
collapseAll()
collapseAll() => Promise<void>
Closes every branch.
expandAll()
expandAll() => Promise<void>
Opens every branch, including branches nothing has looked inside yet.
Applies a whole filter model, as the table inside does, and emits suiFilter. What sui-filter-builder calls.
setFocus()
setFocus() => Promise<void>
Focuses the row that holds the tree's one tab stop: the row focused last, or the first row in view. el.focus() on the host does the same.
Types
SuiDataTableColumninterfaceA column, plus the three things a table does that a grid does not: filter it from the header, edit it in place, and total it in the footer. Everything else comes from SuiGridColumn.
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.
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.
SuiGridDensitytypeRow height and cell padding. The three stops are the library's control heights, so a grid row lines up with the inputs beside it.
No members.
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.
SuiGridNullOrdertypeWhere nulls and blanks land, independent of direction: last keeps empties out of the way in both directions, which is what a ledger wants.
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.
SuiGridSortinterfaceOne sort term. The elements sort by an ordered list of these, so single-column sort is a list of one and no second code path exists for it.
key
string
dir
SuiGridSortDir
SuiGridSortDirtype
No members.
SuiGridTreeFilterModetypeHow a filter treats a branch whose own row does not match but whose descendants do.
No members.
SuiGridTreeNodeinterfaceOne node. data is the record the columns read, exactly as a flat row is, so a column definition written for a table works unchanged over a tree.
key
SuiKey
data
SuiGridRow
children?
SuiGridTreeNode[]
leaf?
boolean
false marks a branch whose children have not arrived: it draws the twisty and asks for them the first time it opens. Leave it unset for a node that is settled either way.
loading?
boolean
Shows the branch is fetching its children.
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.