Demos
DataGrid (Advanced)
The DataGrid provides advanced table functionality including filtering, sorting, pagination, row selection, and CSV export.
import { DataGrid, Badge, ButtonIcon, Text, Button, Stack, Tooltip, Notification, spacing } from 'sxa-ui'
import { createColumnHelper } from '@tanstack/react-table'
import {
RiArrowRightSLine,
RiDeleteBinLine,
RiDownloadCloudLine,
RiInformationLine,
RiImageLine,
} from '@remixicon/react'
import { useState } from 'react'
// Generate sample data
const generateSampleData = (count = 100) => {
const statuses = ['ACTIVE', 'PENDING', 'BLOCKED', 'INACTIVE']
const firstNames = ['John', 'Jane', 'Bob', 'Alice', 'Charlie', 'Diana', 'Frank', 'Grace']
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis']
const departments = ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance']
const categories = ['Frontend', 'Backend', 'DevOps', 'Design', 'Product']
const thumbnails = [
'https://placehold.co/150x150/4F46E5/FFFFFF?text=User+1',
'https://placehold.co/150x150/059669/FFFFFF?text=User+2',
'https://placehold.co/150x150/DC2626/FFFFFF?text=User+3',
'https://placehold.co/150x150/EA580C/FFFFFF?text=User+4',
]
return Array.from({ length: count }, (_, index) => ({
id: index + 1,
thumbnail: thumbnails[Math.floor(Math.random() * thumbnails.length)],
firstName: firstNames[Math.floor(Math.random() * firstNames.length)],
lastName: lastNames[Math.floor(Math.random() * lastNames.length)],
email: `user${index + 1}@example.com`,
status: statuses[Math.floor(Math.random() * statuses.length)],
department: departments[Math.floor(Math.random() * departments.length)],
categories: categories.filter(() => Math.random() > 0.5).slice(0, 3),
age: Math.floor(Math.random() * 40) + 22,
salary: Math.floor(Math.random() * 80000) + 40000,
isActive: Math.random() > 0.3,
joinedDate: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000),
isPriority: Math.random() > 0.7,
skills: Math.floor(Math.random() * 10) + 1,
}))
}
function statusToBadgeState(status) {
switch (status) {
case 'ACTIVE':
return Badge.states.success
case 'PENDING':
return Badge.states.info
case 'BLOCKED':
return Badge.states.error
case 'INACTIVE':
return Badge.states.disabled
default:
return Badge.states.disabled
}
}
export default function DataGridDefault() {
const data = generateSampleData(50)
const columnHelper = createColumnHelper()
const [downloadLoading, setDownloadLoading] = useState(false)
const [downloadNotification, setDownloadNotification] = useState(false)
const columns = [
// Thumbnail column with responsive images
columnHelper.accessor('thumbnail', {
id: 'thumbnail',
header: 'Avatar',
meta: {
cellType: DataGrid.cellTypes.thumbnail,
cardType: DataGrid.cardTypes.thumbnail,
},
enableSorting: false,
enableColumnFilter: false,
cell: ({ getValue, table }) => {
const value = getValue()
const layout = table.getState().layout
let imageSize = 48
if (layout === DataGrid.layouts.grid) {
imageSize = 120
}
return (
<Stack justifyContent={Stack.justifyContent.center} utilClassNames='sxa-w-full'>
<img
loading='lazy'
src={value || 'https://via.placeholder.com/150x150/6B7280/FFFFFF?text=No+Image'}
style={{
width: imageSize,
height: imageSize,
aspectRatio: '1/1',
objectFit: 'cover',
borderRadius: '50%',
}}
alt='User avatar'
/>
</Stack>
)
},
}),
// Full name with title card type
columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
id: 'fullName',
header: 'Full Name',
meta: {
csvHeader: 'Full Name',
cardType: DataGrid.cardTypes.title,
},
cell: ({ row }) => `${row.original.firstName} ${row.original.lastName}`,
}),
columnHelper.accessor('email', {
header: 'Email',
meta: {
csvHeader: 'Email Address',
filterVariant: 'stringlist',
filterPlaceholder: 'Enter emails separated by commas',
},
filterFn: 'arrIncludesSome',
}),
// Status with badges card type
columnHelper.accessor('status', {
header: 'Status',
cell: ({ getValue }) => <Badge label={getValue()} state={statusToBadgeState(getValue())} />,
meta: {
filterVariant: 'multiselect',
csvHeader: 'Status',
csvValueFn: (value) => value,
cardType: DataGrid.cardTypes.badges,
},
enableGlobalFilter: false,
}),
// Priority with boolean filter
columnHelper.accessor('isPriority', {
header: 'Priority',
cell: ({ getValue }) => (getValue() ? <Badge label='High Priority' state={Badge.states.error} /> : <></>),
meta: {
filterVariant: 'boolean',
csvHeader: 'Is Priority',
csvValueFn: (value) => (value ? 'Yes' : 'No'),
cardType: DataGrid.cardTypes.badges,
},
enableGlobalFilter: false,
}),
// Categories with multiselect filter
columnHelper.accessor('categories', {
header: 'Categories',
cell: ({ getValue, table }) => {
const categories = getValue() || []
const layout = table.getState().layout
const text = categories.join(', ')
if (layout === DataGrid.layouts.grid) {
return <>{text}</>
}
return (
<Text
title={text}
style={{
maxWidth: 150,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{text}
</Text>
)
},
meta: {
filterVariant: 'multiselect',
csvHeader: 'Categories',
csvValueFn: (value) => (value || []).join(', '),
},
filterFn: 'arrIncludesSome',
enableGlobalFilter: false,
}),
columnHelper.accessor('department', {
header: 'Department',
meta: {
filterVariant: 'select',
csvHeader: 'Department',
},
}),
// Date column with custom formatting
columnHelper.accessor('joinedDate', {
header: 'Joined Date',
sortingFn: 'datetime',
enableColumnFilter: false,
enableGlobalFilter: false,
meta: {
csvHeader: 'Joined Date',
csvValueFn: (value) => new Date(value).toLocaleDateString(),
},
cell: ({ getValue }) => <DataGrid.CellText>{new Date(getValue()).toLocaleDateString()}</DataGrid.CellText>,
}),
columnHelper.accessor('age', {
header: 'Age',
meta: { csvHeader: 'Age' },
}),
// Actions column with complex header including tooltip
columnHelper.display({
id: 'actions',
header: () => (
<Stack alignItems={Stack.alignItems.center} gap={spacing.xxs}>
<DataGrid.CellText>Actions</DataGrid.CellText>
<Tooltip
trigger={<ButtonIcon tiny variant={ButtonIcon.variants.ghost} icon={RiInformationLine} />}
alignment={Tooltip.alignments.top}
>
Available actions for each user
</Tooltip>
</Stack>
),
cell: ({ row, table }) => {
const layout = table.getState().layout
if (layout === DataGrid.layouts.grid) {
return (
<Button
label='View Profile'
href={`#user/${row.original.id}`}
iconEnd={RiArrowRightSLine}
variant={Button.variants.secondary}
utilClassNames='sxa-w-full'
/>
)
}
return (
<Stack direction={Stack.directions.horizontal} gap={spacing.xxs}>
<ButtonIcon
variant={ButtonIcon.variants.ghost}
icon={RiArrowRightSLine}
onClick={() => alert(`Edit user ${row.original.firstName}`)}
title='Edit user'
/>
<ButtonIcon
variant={ButtonIcon.variants.ghost}
icon={RiDownloadCloudLine}
onClick={() => alert(`Download data for ${row.original.firstName}`)}
title='Download user data'
/>
</Stack>
)
},
meta: {
cellType: DataGrid.cellTypes.alignRight,
cardType: DataGrid.cardTypes.item,
style: {
width: 'var(--column-width-actions)',
minWidth: 'var(--column-width-actions)',
},
},
}),
]
const handleSelectionChange = ({ selectedRows }) => {
console.log('Selected rows:', selectedRows)
}
const handleBulkDownload = async (type, table) => {
try {
setDownloadLoading(type)
const selectedRows = table.getSelectedRowModel().rows
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000))
setDownloadNotification('success')
console.log(`Bulk ${type} download for ${selectedRows.length} items`)
} catch (error) {
setDownloadNotification('error')
console.error('Download failed:', error)
} finally {
setDownloadLoading(false)
}
}
const bulkActions = (table) => (
<>
<Button
label='Export CSV'
iconEnd={RiDownloadCloudLine}
variant={Button.variants.secondary}
disabled={downloadLoading}
loading={downloadLoading === 'csv'}
onClick={() => handleBulkDownload('csv', table)}
/>
<Button
label='Bulk Email'
iconEnd={RiDownloadCloudLine}
variant={Button.variants.secondary}
disabled={downloadLoading}
loading={downloadLoading === 'email'}
onClick={() => handleBulkDownload('email', table)}
/>
<ButtonIcon
variant={ButtonIcon.variants.destructive}
icon={RiDeleteBinLine}
onClick={() => {
const selectedRows = table.getSelectedRowModel().rows
alert(`Delete ${selectedRows.length} users?`)
}}
title='Delete selected users'
/>
</>
)
return (
<>
{downloadNotification && (
<>
{downloadNotification === 'success' && (
<Notification
state={Notification.states.info}
title='Download Successful'
closable
onClose={() => setDownloadNotification(false)}
style={{ marginBottom: 16 }}
>
<Text>Your download has been completed successfully.</Text>
</Notification>
)}
{downloadNotification === 'error' && (
<Notification
state={Notification.states.danger}
title='Download Failed'
closable
onClose={() => setDownloadNotification(false)}
style={{ marginBottom: 16 }}
>
<Text>An error occurred during download. Please try again later.</Text>
</Notification>
)}
</>
)}
<div style={{ height: '600px' }}>
<DataGrid
data={data}
columns={columns}
t={
typeof window !== 'undefined'
? window.t
: (key) => {
const translations = {
'table.filter.filter_cta': 'Filter',
'search.placeholder': 'Search...',
'table.download.label': 'Download',
'form.cancel': 'Cancel',
'table.csv_download.select_all': 'Select All',
'table.filter.placeholder': 'Select filter',
'table.filter.boolean_true': 'Yes',
'table.filter.boolean_false': 'No',
'table.filter.stringlist_csv_cta': 'Upload CSV',
'table.filter.stringlist_csv_example_cta': 'Download Example',
'table.filter.add_filter_cta': 'Add Filter',
'table.filter.close_filter_cta': 'Close',
'table.selection.select_all_cta': 'Select All',
'table.sort.placeholder': 'Sort by...',
'table.sorting.asc': '(ascending)',
'table.sorting.desc': '(descending)',
'table.empty.title': 'No data available',
'table.empty.copy': 'Try adjusting your filters.',
'table.selection.rows_selected': 'rows selected',
'table.pagination.page_size_label': 'Show',
'table.pagination.page_size_all_label': 'All',
'table.pagination.rows_off_total_rows': 'of',
'table.change_view': 'Change View',
}
return translations[key] || key
}
}
style={{
'--table-body-row-height': '64px',
'--column-width-actions': 'calc(120px + var(--space-xxs) * 2)',
}}
filters={true}
csvDownload='users-comprehensive-export.csv'
rowSelection={true}
layoutChangeable={true}
onSelectionChange={handleSelectionChange}
bulkActions={bulkActions}
paginationConfig={{
pageIndex: 0,
pageSize: 10,
pageSizeOptions: [5, 10, 20, 50],
}}
/>
</div>
</>
)
}
DataGrid Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | Array | [] | Array of row data objects |
columns | Array | - | TanStack Table column definitions |
t | Function | - | Required Translation function for internationalization |
filters | Boolean | false | Enable advanced filtering UI |
csvDownload | Boolean|String | false | Enable CSV download. If string, used as filename |
rowSelection | Boolean | false | Enable row selection with checkboxes |
layoutChangeable | Boolean | false | Allow switching between table and grid layouts |
onSelectionChange | Function | - | Callback when row selection changes |
bulkActions | Function | - | Function returning bulk action components |
actions | Function | - | Function returning action components |
paginationConfig | Object | - | Pagination configuration |
Advanced Features
The DataGrid component supports a comprehensive set of features for enterprise-level data management:
Column Types and Styling
- Thumbnail columns: Display images with responsive sizing using
cellType: DataGrid.cellTypes.thumbnail - Custom cell styling: Apply specific width and styling via
meta.styleproperties - Cell alignment: Right-align cells using
cellType: DataGrid.cellTypes.alignRight - Text overflow handling: Automatic ellipsis for long content with full text in tooltips
Card Layout Types
Configure how columns appear in grid layout using cardType:
DataGrid.cardTypes.title: Primary title display in grid cardsDataGrid.cardTypes.thumbnail: Image display in grid cardsDataGrid.cardTypes.badges: Badge collection displayDataGrid.cardTypes.item: Action items in grid cards
Filter Types
select: Single-select dropdown filtermultiselect: Multi-select dropdown filter with array supportstringlist: Text input for comma-separated values + CSV upload capabilityboolean: True/false dropdown filter- Default: Text input filter for global search
Advanced Column Features
- Custom filter functions: Use
filterFn: 'arrIncludesSome'for array-based filtering - Date sorting: Special date sorting with
sortingFn: 'datetime' - Complex headers: Headers with tooltips and additional information
- Responsive cell rendering: Different content based on table/grid layout
- CSV customization: Custom headers and value functions for exports
Bulk Actions and State Management
- Multiple bulk actions: Support for multiple action buttons with individual loading states
- Async operations: Built-in loading states and error handling
- Notification integration: Success/error notifications for user feedback
- Selection tracking: Comprehensive row selection with callbacks
Styling and Layout
- Custom CSS properties: Override default styling with CSS custom properties
- Responsive design: Automatic layout adjustments for mobile/desktop
- Theme integration: Full integration with design system tokens
Code Examples
Thumbnail Column with Responsive Images
columnHelper.accessor('thumbnail', {
id: 'thumbnail',
header: 'Avatar',
meta: {
cellType: DataGrid.cellTypes.thumbnail,
cardType: DataGrid.cardTypes.thumbnail,
},
enableSorting: false,
enableColumnFilter: false,
cell: ({ getValue, table }) => {
const layout = table.getState().layout
const imageSize = layout === DataGrid.layouts.grid ? 120 : 48
return (
<img
src={getValue()}
style={{
width: imageSize,
height: imageSize,
aspectRatio: '1/1',
objectFit: 'cover',
borderRadius: '50%'
}}
alt='User avatar'
/>
)
},
})
StringList Filter with CSV Upload
columnHelper.accessor('email', {
header: 'Email',
meta: {
filterVariant: 'stringlist',
filterPlaceholder: 'Enter emails separated by commas',
filterExamplePath: '/path/to/example.csv'
},
filterFn: 'arrIncludesSome',
})
Complex Header with Tooltip
columnHelper.display({
id: 'actions',
header: () => (
<Stack alignItems={Stack.alignItems.center} gap={spacing.xxs}>
<DataGrid.CellText>Actions</DataGrid.CellText>
<Tooltip
trigger={<ButtonIcon icon={RiInformationLine} />}
alignment={Tooltip.alignments.top}
>
Available actions for each item
</Tooltip>
</Stack>
),
// ... cell configuration
})
Responsive Layout-Aware Cell Rendering
cell: ({ row, table }) => {
const layout = table.getState().layout
if (layout === DataGrid.layouts.grid) {
return (
<Button
label='View Details'
href={`#item/${row.original.id}`}
utilClassNames='sxa-w-full'
/>
)
}
return (
<ButtonIcon
icon={RiArrowRightSLine}
onClick={() => handleEdit(row.original)}
/>
)
}
Multiple Bulk Actions with Loading States
const bulkActions = (table) => (
<>
<Button
label='Export CSV'
iconEnd={RiDownloadCloudLine}
loading={downloadLoading === 'csv'}
onClick={() => handleBulkDownload('csv', table)}
/>
<Button
label='Send Email'
iconEnd={RiMailLine}
loading={downloadLoading === 'email'}
onClick={() => handleBulkDownload('email', table)}
/>
<ButtonIcon
variant={ButtonIcon.variants.destructive}
icon={RiDeleteBinLine}
onClick={() => handleBulkDelete(table)}
/>
</>
)
Translation Keys
Required translation keys for the t function:
const translations = {
'table.filter.filter_cta': 'Filter',
'search.placeholder': 'Search...',
'table.download.label': 'Download',
'form.cancel': 'Cancel',
'table.csv_download.select_all': 'Select All',
'table.filter.placeholder': 'Select filter',
'table.filter.boolean_true': 'Yes',
'table.filter.boolean_false': 'No',
'table.filter.stringlist_csv_cta': 'Upload CSV',
'table.filter.add_filter_cta': 'Add Filter',
'table.filter.close_filter_cta': 'Close',
'table.selection.select_all_cta': 'Select All',
'table.sort.placeholder': 'Sort by...',
'table.sorting.asc': '(ascending)',
'table.sorting.desc': '(descending)',
'table.empty.title': 'No data available',
'table.empty.copy': 'Try adjusting your filters.',
'table.selection.rows_selected': 'rows selected',
'table.pagination.page_size_label': 'Show',
'table.pagination.page_size_all_label': 'All',
'table.pagination.rows_off_total_rows': 'of',
'table.change_view': 'Change View'
}