Skip to main content
Urbicon UI

Sorting, Grouping & Summaries

Sort by clicking column headers, group rows by any groupable column, and aggregate numeric columns with summary rows.

Sorting & Grouping

Every data column sorts on a header click, cycling ascending, descending, unsorted — or descending first for a column that declares sortDescFirst: true: a date, a rating, a count. One column at a time: a click on another header moves the sort there. sortable: false takes that away, and a synthetic column (one with no accessor) has no value to sort by and never had it.

Start Sorted

viewDefaults.sort is the view's baseline: the header indicator shows it, and users can still change or clear it.
Name
Department
Salary
Sofia Martinez
Platform
165000
Alexander Novak
Data
148000
Emma Wilson
Platform
142000
Isabella Singh
Design
140000
Mia Zhang
Product
135000
<Table
  {items}
  {columns}
  viewDefaults={{ sort: { column: 'salary', direction: 'desc' }, pageSize: 5 }}
/>

A sort is view state: a shared link or a view restored from storage carries it, and either one overrides this baseline. URL State & Persistence sets out which of the three wins.

Grouping is opt-in: bucketing an email or a free-text note makes one group per row, so it is not offered to every column that holds a value. groupable: true says yes, groupable: false says no, and with neither set an explicit sortable: true grants it. A column that declares nothing at all sorts, but does not group. Both flags sit on the column, beside the rest of its properties.

A virtualized table never groups: the menu entry goes, and a grouping arriving from the view defaults, a URL or storage renders ungrouped — the value itself stays on the view.

Grouping with Custom Order

groupOrder names the groups that come first; every other group follows in the order its rows arrive, and a name with no rows is skipped. Rows with no value in that column land in a group called Unassigned.
Name
Role
Department
Location
Platform (4 items)
Emma Wilson
Staff Engineer
Platform
Berlin
Sofia Martinez
Engineering Manager
Platform
Munich
Noah Kim
DevOps Engineer
Platform
Hamburg
Ethan Müller
QA Engineer
Platform
Hamburg
Product (3 items)
James Park
Frontend Developer
Product
Remote
Lucas Weber
Backend Developer
Product
Berlin
Mia Zhang
Product Manager
Product
Remote
Design (3 items)
Liam Chen
Product Designer
Design
Hamburg
Olivia Brown
UX Researcher
Design
Munich
Isabella Singh
Design Lead
Design
Berlin
Data (2 items)
Aisha Patel
Data Scientist
Data
Berlin
Alexander Novak
ML Engineer
Data
Munich

Platform (4 items)

Product (3 items)

Design (3 items)

Data (2 items)

<Table
  {items}
  {columns}
  viewDefaults={{ groupBy: 'department' }}
  groupOrder={['Platform', 'Product', 'Design', 'Data']}
/>

Summaries

Summary rows aggregate a column across rows. When the table is grouped, each group gets its own summary row; without grouping, a single total row is appended below the data. Pass prefs={{ defaults: { summaries: […] } }} to enable summaries declaratively. Users can also add and remove them at runtime via the header menu or the SmartFilterBar's summary control.

Summaries are a preference, not a view setting. Sorting and grouping decide which rows a reader sees, so they live on the view and travel in a shared link. A summary row changes how the same rows are presented, so it belongs to this reader on this device and stays in web storage: viewDefaults for the former, prefs for the latter.

Per-Group Summaries

Group by department and sum the salary column. Each group renders its own summary row.
Name
Department
Salary
Platform (4 items)
Emma Wilson
Platform
142000
Sofia Martinez
Platform
165000
Noah Kim
Platform
115000
Ethan Müller
Platform
85000
507000
Design (3 items)
Liam Chen
Design
98000
Olivia Brown
Design
88000
Isabella Singh
Design
140000
326000
Product (3 items)
James Park
Product
92000
Lucas Weber
Product
105000
Mia Zhang
Product
135000
332000
Data (2 items)
Aisha Patel
Data
128000
Alexander Novak
Data
148000
276000

Platform (4 items)

Summary for Platform

Salary 507000

Design (3 items)

Summary for Design

Salary 326000

Product (3 items)

Summary for Product

Salary 332000

Data (2 items)

Summary for Data

Salary 276000
<Table
  {items}
  {columns}
  viewDefaults={{ groupBy: 'department', pageSize: 12 }}
  prefs={{ defaults: { summaries: [{ column: 'salary', type: 'sum' }] } }}
/>

A summary is { column, type } plus an optional formatter: (value: number) => string; the full shape is SummaryConfig. type is sum, avg, min, max or count, and count is the one that does not do arithmetic: it counts the rows that have a value at all. The four others skip rows whose value is not a number, and show a dash when none is.

A summary covers every row matching the current search and filters, not the page on screen. The pager moves under a total that stays put. A table that leaves the work to the server only holds the page it was handed, so there the total is that page's: Server Processing says what to do instead.

Which columns offer summaries is controlled per column: summable: true opts a column in, summable: false opts it out. When the flag is unset, columns with dataType: 'number' are summable automatically. storage sits beside defaults in the same object (prefs={{ storage: 'employees', defaults: { summaries } }}), and then summary choices survive reloads alongside column visibility and column order. What the reader last chose wins over your defaults, an empty choice included: What the table remembers has the rest of the channel.

A summary outlives its column being hidden, and where the figure can go depends on the layout. On the desktop table each summary sits in a cell under its own column, so hiding the column takes the figure with it. In the card layout (a container narrower than cardsBelow) the summaries are a band of label and value rows instead, so there the total stays on screen, under the column's title. The aggregation keeps running either way, and the desktop cell comes back with its column.

Header Menu

Every column header exposes a menu (visible on hover and keyboard focus) that bundles the per-column actions, with no SmartFilterBar required. The same column flags decide what each entry offers and what the toolbar's tool of that name offers:

EntryShown when
Sort ascending / descendinga data column, unless sortable: false
Remove filtera filter on this column is active
Group by column / Remove groupinggroupable: true, or sortable: true when groupable is unset (never while virtualized)
Summary → Sum / Average / Count / Minimum / Maximum / Nonesummable: true, or dataType: 'number' when summable is unset
Hide columnenableColumnVisibility and hideable ≠ false
Show "Salary"one entry per currently hidden column, named after it

Try It

Hover a column header and open the ⋮ menu: sort, group by department, summarize salary, or hide a column.
Name
Department
Salary
Emma Wilson
Platform
142000
Liam Chen
Design
98000
Sofia Martinez
Platform
165000
James Park
Product
92000
Aisha Patel
Data
128000
const columns: Column<Employee>[] = [
  { accessor: 'name', title: 'Name', sortable: true },
  { accessor: 'department', title: 'Department', sortable: true, groupable: true },
  {
    accessor: 'salary',
    title: 'Salary',
    sortable: true,
    summable: true,
    dataType: 'number',
    align: 'right'
  }
];

Hiding and restoring columns is covered in Column Configuration → Column Visibility.