Datagrid Component
DataGrid is a component that displays data in a grid format. It supports pagination, sorting, and filtering. The component can be used to display data from a GraphQL query.
View Types
The DataGrid supports three view types: table (default), collection, and list.
| View Type | Description |
|---|---|
table | Traditional table grid with rows and columns (default) |
collection | Responsive grid of cards, ideal for visual browsing |
list | Compact single-column list using MUI List components |
Table View (Default)
The standard table view displays data in rows and columns:
views:
- name: tableView
displayName:
en-US: Table View
viewType: table # or omit - table is default
columns:
- name: name
- name: email
- name: status
Collection View
Collection view displays data as a responsive grid of cards:
views:
- name: cardView
displayName:
en-US: Card View
viewType: collection
columns:
- name: name
- name: email
- name: status
collection:
itemSize:
xs: 12 # 1 card per row on mobile
sm: 6 # 2 cards per row on small screens
md: 4 # 3 cards per row on medium screens
lg: 3 # 4 cards per row on large screens
spacing: 3
dividers: false
children: # Optional custom template
- component: card
props:
elevation: 2
children:
- component: cardContent
children:
- component: text
props:
variant: h6
value: "{{item.name}}"
- component: text
props:
variant: body2
color: textSecondary
value: "{{item.email}}"
Collection Properties
| Property | Type | Default | Description |
|---|---|---|---|
children | array | auto-generated | Custom YAML template for each item |
itemName | string | 'item' | Variable name for current item |
itemSize | number | object | { xs: 12, sm: 6, md: 4, lg: 3 } | Grid column size |
spacing | number | 3 | Gap between items |
emptyMessage | string | 'No items to display' | Empty state message |
List View
List view displays data in a compact, single-column format:
views:
- name: listView
displayName:
en-US: List View
viewType: list
columns:
- name: name
- name: email
- name: department
- name: status
list:
dense: true
dividers: true
primaryField: name
secondaryField: email
avatarField: avatarUrl
List Properties
| Property | Type | Default | Description |
|---|---|---|---|
children | array | auto-generated | Custom YAML template for each item |
itemName | string | 'item' | Variable name for current item |
dividers | boolean | false | Show dividers between items |
dense | boolean | false | Use compact spacing |
disablePadding | boolean | false | Remove list padding |
emptyMessage | string | 'No items to display' | Empty state message |
primaryField | string | - | Field for primary text |
secondaryField | string | - | Field for secondary text |
avatarField | string | - | Field for avatar URL |
List View with Custom Template
views:
- name: customList
viewType: list
columns:
- name: title
- name: description
list:
dividers: true
children:
- component: listItemText
props:
primary: "{{item.title}}"
secondary: "{{item.description}}"
primaryTypographyProps:
fontWeight: bold
Multiple View Types
Allow users to switch between different view types:
component: dataGrid
name: productsGrid
props:
options:
query: products
entityKeys:
- productId
enableViews: true
views:
- name: table
displayName:
en-US: Table
viewType: table
columns:
- name: name
- name: sku
- name: price
- name: stock
- name: cards
displayName:
en-US: Cards
viewType: collection
columns:
- name: name
- name: sku
- name: price
collection:
itemSize:
xs: 12
sm: 6
md: 4
- name: list
displayName:
en-US: List
viewType: list
columns:
- name: name
- name: sku
- name: price
list:
dense: true
dividers: true
primaryField: name
secondaryField: sku
Example
component: dataGrid
name:
inputs:
props:
refreshHandler: "countries"
enableStore: true # enable store for the datagrid, Row data will be placed in the store
views:
- name: allCountries
viewType: "grid" # collection, grid, Default is grid
itemComponent: # render item for collection
component: card
props:
title: "{{ item.name }}"
description: "{{ item.countryCode }}"
image: "{{ item.image }}"
actions:
- component: button
props:
label: "View" # action to execute when the button is clicked
onClick:
- navigate: "countries/{{ item.countryCode }}"
displayName:
en-US: All Countries
enableEdit: true
enableSelect: "Multiple"
onRowClick: # action to execute when a row is clicked (override default action)"
columns:
- name: countryCode
label:
en-US: Country code
- name: name
label:
en-US: Name
editor:
type: text
showAs:
component: text
props:
value: "{{ name }}"
- name: created
label:
en-US: Created
- name: createdByUser.userName
label:
en-US: Created by
- name: lastModified
label:
en-US: Last modified
- name: lastModifiedByUser.userName
label:
en-US: Last modified by
- name: customField
value: "{{ item.customField }}"
filter:
orderBy: # default sorting for the datagrid
- name: countryCode
direction: ASC
childViews: # child views for the datagrid
- name: states # field name / resolver for GraphQL query
onRowClick: # action to execute when a row is clicked
columns:
- name: stateCode
label:
en-US: State code
options:
query: countries
rootEntityName: Country
enableToolbar: true # show toolbar in the datagrid, default is true
enableColumns: true # enable column selection in the datagrid, default is true
enableFilter: true # enable filtering in the datagrid, default is true
enableSearch: true # enable search in the datagrid, default is true
entityKeys:
- countryCode
enableDynamicGrid: true
navigationType: browser # browser or store,
editableOptions:
onNewRow: # mutation to create a new record
mutation:
onRowChange: # mutation to update a record
mutation:
onRowDelete: # mutation to delete a record
mutation:
itemTemplate: ?
countryCode: "{{ data.countryCode }}"
onDataLoad:
- name: "setCountryCode"
args:
countryCode: "{{ data[0].countryCode }}"
onEditClick:
navigate: countries/{{ countryCode }}
onRowClick:
dialog:
name: updateCountryDialog
props:
permission: System/Countries/Update
title:
en-US: "Update Country"
countryCode: "{{ countryCode }}"
organizationId: "{{ organizationId }}"
component:
layout:
component: layout
props:
children:
- component: Countries/UpdateCountry
defaultView: allCountries
toolbar:
- component: dropdown
props:
label:
en-US: Actions
name: actionscountries
icon: activity
options:
variant: secondary
items:
- label:
en-US: Import Countries
onClick: []
- label:
en-US: Export Countries
onClick: []
children:
Change Tracking & Row Highlights
The DataGrid supports automatic change tracking that detects new and updated rows across refreshes, highlighting them visually. Highlights accumulate across multiple refresh cycles, with each highlighted row having its own per-row TTL (Time-To-Live) measured in refresh cycles.
Enabling Change Tracking
Set enableChangeTracking: true in the DataGrid options along with a refreshHandler and refreshInterval:
component: dataGrid
name: pickupOrdersGrid
props:
refreshHandler: pickupOrders
options:
query: pickupOrders
entityKeys:
- orderId
enableChangeTracking: true
enableRefresh: true
refreshInterval: 30000
highlightNew: true
highlightUpdated: true
highlightForRefreshes: 5
Change Tracking Options
| Property | Type | Default | Description |
|---|---|---|---|
enableChangeTracking | boolean | true | Enable/disable change tracking |
highlightNew | boolean | true | Highlight newly added rows |
highlightUpdated | boolean | true | Highlight rows with updated values |
highlightForRefreshes | number | 1 | Per-row TTL — number of refresh cycles a highlight persists |
How It Works
- Snapshot: On each data fetch, the DataGrid takes a snapshot of the current data.
- Comparison: On the next refresh (auto-refresh or external), the new data is compared against the snapshot using
entityKeysto match rows. - Highlight accumulation: New and updated rows are added to a highlight map with a fresh TTL. Existing highlights have their TTL decremented by 1 each cycle. When a row's TTL reaches 0, its highlight is removed.
- Per-row independence: Each row's TTL is independent — older highlights expire first while newer ones persist.
Per-Row TTL Example
With highlightForRefreshes: 3:
Refresh #0 → Initial load, 20 rows, snapshot taken
Refresh #1 → 5 new rows arrived → A(3) B(3) C(3) D(3) E(3)
Refresh #2 → 1 new row arrived → A(2) B(2) C(2) D(2) E(2) F(3)
Refresh #3 → nothing new → A(1) B(1) C(1) D(1) E(1) F(2)
Refresh #4 → 2 new rows arrived → F(1) G(3) H(3) (A–E expired)
Refresh #5 → nothing new → G(2) H(2) (F expired)
External Refresh with Highlight Options
The refresh action now supports an optional options object to control highlighting behavior per-call. These options override the grid-level defaults for that specific refresh:
- refresh: "pickupOrders"
options:
highlightNew: true
highlightForRefreshes: 1
See Actions — refresh for details.
Refresh Options Resolution Order
Options are resolved with highest priority first:
- Refresh action
options— per-call override (external refresh only) - DataGrid
options— grid-level defaults (auto-refresh always uses these) - Global defaults —
highlightNew: true,highlightUpdated: true,highlightForRefreshes: 1
Behavior Details
| Scenario | Behavior |
|---|---|
| Auto-refresh (polling timer) | Grid-level defaults apply; highlights accumulate |
| External refresh action (no options) | Grid-level defaults apply |
| External refresh action (with options) | Action options override grid defaults |
| Row re-highlighted before TTL expires | TTL resets to fresh value |
| Pagination change | Comparison skipped; TTLs are not decremented |
| Filter/search/view change | All highlights cleared; snapshot reset |
| Cross-window refresh | Options propagate via postMessage; each window accumulates independently |
Disabling Highlights on a Specific Refresh
- refresh: "pickupOrders"
options:
highlightNew: false
highlightUpdated: false
Export Column Configuration
The DataGrid automatically exposes column export configuration (headers and column mappings) to the context store whenever the active view changes. This enables external components — such as export buttons — to access the current grid's visible column names and their human-readable labels.
How It Works
When a view's viewColumns change, the DataGrid writes to the context store:
| Store Key | Type | Description |
|---|---|---|
{gridName}.headers | string[] | Ordered list of column paths for the current view (export field keys) |
{gridName}.columnMappings | Record<string, string> | Map from column path → resolved display label |
Excluded columns: excludeFromQuery columns, value columns, hidden columns (isHidden: true), and invisible columns (isVisible: false). Template variables in isHidden/isVisible are resolved using current local state.
Label resolution: String/number → used directly; localized object → en-US preferred, fallback to first locale; null → falls back to column name.
# Export button reading column config from context
- name: exportButton
component: button
componentProps:
props:
label: Export CSV
action:
- export:
query: "orders"
headers: "{{store.ordersGrid.headers}}"
columnMappings: "{{store.ordersGrid.columnMappings}}"
Save View Dialog
The SaveViewDialog provides a UI for managing DataGrid views: saving/updating views, creating new views, reordering columns via drag-and-drop, renaming column display names, toggling column visibility, and deleting views.
Stability During Auto-Refresh
The SaveViewDialog only initializes its internal state when the dialog opens — not on every view change. This prevents auto-refresh cycles from resetting a user's in-progress edits. The useEffect that populates dialog state is gated on the open prop.
View Switching After Save
When a user saves or creates a view:
- A
justSavedViewRefflag prevents the view-selection effect from reverting to the old URL-parameter view - The views list refreshes with
skipCache: trueso the newly saved view appears immediately - The flag is cleared on the next render cycle
The same skipCache bypass is used when deleting a view.
Density Toggle
The DataGrid supports a density toggle in the toolbar that lets users switch between three row density modes: standard, comfortable, and compact. Each mode adjusts row height, column widths, and UI element sizes.
Density Modes
| Mode | Row Height | Min Column Width | Max Column Width | Best For |
|---|---|---|---|---|
standard | 50px | 150px | 300px | Normal use, readability |
comfortable | 40px | 120px | 250px | Moderate data density |
compact | 28px | 60px | 150px | Maximum data on screen |
In compact mode, checkboxes, expand icons, dots menu columns, and sticky column controls are all proportionally smaller.
Enabling the Density Toggle
The density toggle appears in the toolbar by default unless explicitly disabled. Control it at two levels:
Per-grid — via DataGrid options:
component: dataGrid
name: ordersGrid
props:
options:
enableDensity: true # default: true
Per-tenant (white-label) — via the dataGrid config in the white-label configuration:
// In white-label client config
export const config: WhiteLabelConfig = {
// ...
dataGrid: {
enableDensity: true, // Show/hide the toggle globally
defaultDensity: 'comfortable' // Default mode for all grids
}
};
White-Label DataGrid Configuration
| Property | Type | Default | Description |
|---|---|---|---|
dataGrid.enableDensity | boolean | false | Show the density toggle in grid toolbars |
dataGrid.defaultDensity | 'standard' | 'comfortable' | 'compact' | 'standard' | Default density mode for all grids |
Density Persistence
The selected density is persisted per grid in localStorage using the key dataGrid_density_{gridName}. This means:
- Each grid remembers its own density preference
- The preference survives page refreshes and browser restarts
- If no preference is stored, the white-label default is used (falling back to
'standard')
Resolution Order
- localStorage — per-grid user preference (highest priority)
- White-label config —
dataGrid.defaultDensity(tenant default) - Global default —
'standard'
Filter Persistence
By default, the DataGrid persists filter state (both filter column selections and filter values) across route navigation within the same browser session. This means that when a user applies filters on a grid, navigates away to another page, and then returns, their filters are automatically restored.