- Guidelines
- React Implementation
- Blazor API Reference
Data Tables (DataGrids) organize and present structured information in rows and columns, allowing users to scan, analyze, compare, and take action on large sets of data efficiently.
Guidance
| Aspect | Best Practice |
|---|---|
| Usage | Use data tables for structured data that benefits from column-based organization, sorting, filtering, or other data manipulation operations. |
| Content | Include clear column headers. Present data consistently. Ensure proper data alignment (text left, numbers right). Provide appropriate data density. |
| Behavior | Support sorting, filtering, pagination for large datasets. Allow row selection when actions apply to rows. Enable responsive behaviors for small screens. |
| States | Clearly indicate hover, selected, and active states. Show loading states during data fetching. Provide empty and error states. |
| Feedback | Highlight sorted columns. Provide visual feedback for selection, sorting operations, and filtering. Indicate loading states during data operations. |
| Examples | Financial data, inventory lists, user management, analytics, or any structured tabular information that requires analysis. |
Best Practices
- Appropriate Column Selection: Include only necessary columns to avoid overwhelming users with data.
- Meaningful Organization: Arrange columns in a logical order, with most important information first.
- Responsive Handling: Consider how tables adapt to different screen sizes, potentially collapsing or reorganizing on small screens.
- Action Accessibility: Make row and bulk actions easily accessible while maintaining a clean interface.
- Performance Optimization: Implement virtualization or pagination for large datasets to maintain performance.
Do’s and Don’ts
Do
- Provide clear column headers with sorting indicators when applicable.
- Align content consistently (text left, numbers right).
- Include pagination, filtering, or virtualization for large datasets.
- Maintain consistent row heights and data formatting.
Don't
- Overload tables with too many columns.
- Mix different types of information within a single column.
- Rely solely on color to indicate state or meaning.
- Omit empty states or loading indicators.
Additional Guidance
- Psychological Impact:
- Cognitive Organization: Tables leverage our innate ability to scan and compare data in grid formats.
- Information Hierarchy: Well-designed tables help users prioritize which data to focus on first.
- Cognitive Load: Sorting, filtering, and pagination reduce the mental effort required to find specific information.
The Data Table is implemented using TanStack Table (formerly React Table) and our base Table component. This powerful combination allows you to create complex, feature-rich data tables with sorting, filtering, pagination, and row selection.For more examples and advanced implementations, refer to the TanStack Table documentation.
Getting Started
Install Dependencies
Copy
Ask AI
npm install @tanstack/react-table
Basic Implementation
A data table implementation consists of several key parts:- Column definitions
- Data source
- Table configuration
- UI components
Copy
Ask AI
import { useState, useEffect } from 'react'
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from '@strongtie/design-system/table'
import { Checkbox } from '@strongtie/design-system/checkbox'
import { Input } from '@strongtie/design-system/input'
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
PaginationEllipsis,
} from '@strongtie/design-system/pagination'
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '@strongtie/design-system/select'
import { MdSearch, MdChevronLeft } from 'react-icons/md'
import {
useReactTable,
getCoreRowModel,
flexRender,
SortingState,
getSortedRowModel,
RowSelectionState,
getFilteredRowModel,
getPaginationRowModel,
} from '@tanstack/react-table'
Data and Column Definitions
First, define your data type and column configuration:Copy
Ask AI
// Define your data type
type Person = {
id: number
name: string
email: string
role: string
status: 'active' | 'inactive'
}
// Define your columns
const columns = [
{
id: 'select',
header: ({ table }) => {
const selectedRows = table.getSelectedRowModel().rows.length
const totalRows = table.getRowModel().rows.length
const checked =
selectedRows === totalRows
? true
: selectedRows === 0
? false
: 'indeterminate'
return (
<Checkbox
checked={checked}
onCheckedChange={(value) => {
if (value === 'indeterminate') {
table.toggleAllPageRowsSelected(false)
} else {
table.toggleAllPageRowsSelected(!!value)
}
}}
aria-label='Select all rows'
/>
)
},
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
aria-label={`Select row ${row.id}`}
/>
),
},
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
{row.getValue('name')}
</div>
),
},
// Add more columns as needed
]
Table Component
Now, implement your table component:Copy
Ask AI
export function DataTable() {
const [sorting, setSorting] = useState<SortingState>([])
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
const [globalFilter, setGlobalFilter] = useState('')
// Set up the table
const table = useReactTable({
data: yourDataArray,
columns,
getCoreRowModel: getCoreRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onRowSelectionChange: setRowSelection,
enableRowSelection: true,
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
globalFilterFn: 'includesString',
state: {
sorting,
rowSelection,
globalFilter,
},
})
return (
<div className='data-table'>
{/* Search and controls */}
<div className='table-controls'>
<div className='search-box'>
<div className='search-wrapper'>
<Input
type='text'
value={globalFilter}
onChange={(event) => setGlobalFilter(event.target.value)}
placeholder='Search all columns...'
className='search-input main-search'
/>
<div className='search-icon'>
<MdSearch aria-hidden='true' />
</div>
</div>
</div>
<div className='selected-count'>
{`${table.getSelectedRowModel().rows.length} of ${table.getRowModel().rows.length} row(s) selected`}
</div>
</div>
{/* Table */}
<div className='table-container'>
<Table size="md">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
onClick={header.column.getToggleSortingHandler()}
style={{
cursor:
header.column.id !== 'select' ? 'pointer' : 'default',
position: 'relative',
}}
>
<div className='header-content'>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.id !== 'select' &&
(header.column.getIsSorted() === 'asc' ? (
<MdChevronLeft className='sort-icon rotate-90' />
) : header.column.getIsSorted() === 'desc' ? (
<MdChevronLeft className='sort-icon -rotate-90' />
) : (
<div className='sort-icon-group'>
<MdChevronLeft className='sort-icon rotate-90' />
<MdChevronLeft className='sort-icon -rotate-90' />
</div>
))}
</div>
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() ? 'selected' : undefined}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className='pagination-wrapper'>
<div className='pagination-info'>
<span>
Page {table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</span>
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(Number(value))
}}
>
<SelectTrigger style={{ width: '120px' }}>
<SelectValue placeholder='Show...' />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem
key={pageSize}
value={pageSize.toString()}
>
Show {pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
/>
</PaginationItem>
{/* Logic for showing page numbers */}
<PaginationItem>
<PaginationNext
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
</div>
)
}
Features and Customization
The Data Table implementation provides several powerful features:Sorting
Clicking on column headers toggles sorting. Add sorting to columns withgetSortedRowModel().Filtering
The global search filters across all columns. You can also add column-specific filters:Copy
Ask AI
// Column-specific filter
table.getColumn('columnName')?.setFilterValue(filterValue)
Pagination
Navigate through large datasets with page controls. Customize page sizes with the Select component.Row Selection
Select individual rows or all rows with checkboxes. Access selected rows with:Copy
Ask AI
const selectedRows = table.getSelectedRowModel().rows
Custom Cell Rendering
Define custom cell rendering for any column using thecell property:Copy
Ask AI
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => (
<span className={`status ${row.original.status}`}>
{row.original.status}
</span>
),
}
Custom Styling
Apply custom styling to your data table using CSS. Create a separate SCSS file for table-specific styles:Copy
Ask AI
.data-table {
.table-controls {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
.search-wrapper {
position: relative;
}
.search-icon {
position: absolute;
top: 50%;
right: 0.75rem;
transform: translateY(-50%);
}
.sort-icon {
width: 1rem;
height: 1rem;
margin-left: 0.25rem;
&.rotate-90 {
transform: rotate(90deg);
}
&.-rotate-90 {
transform: rotate(-90deg);
}
}
.pagination-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
}
.status {
&.active {
color: green;
}
&.inactive {
color: red;
}
}
}
Example
Copy
Ask AI
<StudsDataGrid @ref="grid"
Data="@employees"
AllowRowSelectOnRowClick="true"
AllowSorting="true"
AllowPaging="true"
PageSize="4"
ColumnWidth="200px"
SelectionMode="DataGridSelectionMode.Multiple"
@bind-Value="@selectedEmployees">
<Columns>
<StudsDataGridColumn Width="60px" Sortable="false" Filterable="false">
<HeaderTemplate>
<StudsCheckBox TabIndex="-1" TriState="false" TValue="bool?" Value="@(selectedEmployees == null || selectedEmployees?.Any() != true ? false : !employees.All(i => selectedEmployees.Contains(i)) ? null : employees.Any(i => selectedEmployees.Contains(i)))" Change="@(args => selectedEmployees = args == true ? employees.ToList() : null)" />
</HeaderTemplate>
<Template Context="data">
<StudsCheckBox TabIndex="-1" TriState="false" Value="@(selectedEmployees != null && selectedEmployees.Contains(data))" TValue="bool" Change=@(args => { if(!allowRowSelectOnRowClick) { grid.SelectRow(data); }}) />
</Template>
</StudsDataGridColumn>
<StudsDataGridColumn Property="@nameof(Employee.Photo)" Title="Employee" Sortable="false" Filterable="false">
<Template Context="data">
@data.FirstName @data.LastName
</Template>
</StudsDataGridColumn>
<StudsDataGridColumn Property="@nameof(Employee.Title)" Title="Title" />
<StudsDataGridColumn Property="@nameof(Employee.EmployeeID)" Title="Employee ID" />
<StudsDataGridColumn Property="@nameof(Employee.HireDate)" Title="Hire Date" FormatString="{0:d}" />
<StudsDataGridColumn Property="@nameof(Employee.City)" Title="City" />
<StudsDataGridColumn Property="@nameof(Employee.Country)" Title="Country" />
</Columns>
</StudsDataGrid>
<StudsDataGrid> Parameters
Gets or sets the data source.
If true, columns can be sorted by clicking on the column header.
If true, the grid will display pagination controls.
The number of rows to display per page when paging is enabled.
Gets or sets the page numbers count.
Gets or sets the total count of items.
If true, shows the empty message when there are no records.
If true, shows the grid headers.
Default width for all columns.
Caption text for the grid.
Text to display when there are no records.
If true, displays a loading indicator.
Specifies the selection mode behavior (Single or Multiple).
If true, clicking a row will select it.
Specifies the inline edit mode behavior (Single or Multiple).
If true, the grid will go to the first page when sorting is changed.
Gets or sets the key property.
Gets or sets the selected rows.
Callback fired when the selected rows change.
Callback used to load child data for hierarchical grids.
Callback fired when a column is sorted.
Callback fired when a column is grouped.
Callback fired when the visible columns change.
Callback fired when a row is selected.
Callback fired when a row is deselected.
Callback fired when a row is clicked.
Callback fired when a row is double-clicked.
Callback fired when a cell is clicked.
Callback fired when a cell is double-clicked.
Callback fired when a cell context menu is triggered.
Callback fired when a key is pressed in the grid.
Callback fired when a row is edited.
Callback fired when a row is updated.
Callback fired when a row is created.
Action to customize row rendering attributes.
Action to customize cell rendering attributes.
Action to customize header cell rendering attributes.
Action to customize footer cell rendering attributes.
Async callback for render customization.
Action for render customization.
Action for loading settings.
Callback fired when the page size changes.
Callback for custom data loading.
Gets or sets DataGrid settings.
Callback fired when settings change.
Gets or sets the columns template.
Custom header template.
Custom footer template.
Template to display when there are no records.
Template to display while loading.
Template for row edit mode.
Template for row display.
Specifies additional custom attributes that will be rendered by the component.
Gets or sets the callback that is invoked when the mouse enters an item.
Gets or sets the callback that is invoked when the mouse leaves an item.
A callback that will be invoked when the user right-clicks the component. Commonly used to display a context menu.
Gets or sets the culture used to display localizable data (numbers, dates). Set by default to “CultureInfo.CurrentCulture”.
Gets or sets the inline CSS style.
Gets or sets a value indicating whether this “T:Studs.StudsComponent” is visible. Invisible components are not rendered.
<StudsDataGridColumn> Parameters
Child columns for grouping or composite columns.
Whether to allow checkbox list virtualization.
Unique identifier for the column.
Order index for column positioning.
Sort order for the column.
Gets or sets a value indicating whether this column is visible.
Gets or sets the header tooltip.
Gets or sets the column title.
Gets or sets the property name.
Gets or sets the sort property name (if different from Property).
Gets or sets the column width.
Gets or sets the minimum column width.
Format string to apply to the column values.
CSS class to apply to data cells in this column.
Function that returns a CSS class based on the item and column.
CSS class to apply to the header cell of this column.
CSS class to apply to the footer cell of this column.
If false, this column cannot be sorted.
The text alignment in this column.
Custom cell template.
Custom edit template.
Function that determines if this column is in edit mode.
Custom header template.
Custom footer template.
Gets or sets the data type.
Gets or sets the IFormatProvider used for FormatString.
Gets or sets the filter operator.
Styling Classes
The following CSS classes are applied by the DataGrid components:Grid Container Classes
data-grid– Main data grid containerdata-grid -data– Data area wrapper containing the table
Column & Header Classes
column -icon– Icon columns for expand/collapse functionalitycolumn -title– Column header title wrappercolumn -title-content– Actual column title contentcolumn -sortable -icon– Sort indicator icons-sortable-column– Applied to sortable column headers
Row & Cell Classes
row -expanded-content– Expanded row content wrapperrow -expanded-template– Expanded row template containercell -data– Data cell content wrapperdatatable -emptymessage-row– Empty state message rowdatatable -emptymessage– Empty state message celldatatable -tfoot– Table footer section
Filter Classes
cell -filter– Filter cell wrappercell -filter-content– Filter cell content containercell -filter-label– Filter input/label wrappercell -filter-clear– Clear filter buttonfilter -button– Filter toggle buttongrid -filter-active– Active filter state indicatorgrid -filter– Filter form wrappergrid -filter-label– Filter form labelsgrid -filter-buttons– Filter action buttons containergrid -date-filter– Date-specific filter popupoverlaypanel -wrapper– Filter popup wrapperoverlaypanel -content– Filter popup contentlistbox -list– Filter options listlistbox -list-wrapper– Filter list containerfilter -menu-symbol– Filter operator symbolsmultiselect -item– Filter menu list items-highlight– Selected/highlighted filter optioncurrent-filter– Active filter value displayclear-filter– Clear filter action buttonapply-filter– Apply filter action button