DataGrid
DataGrid is an enhanced version of Table that enables users to organize data by searching, sorting, and filtering results.

Element | Purpose |
---|---|
Caption | Using a caption is always advised. It serves as a label for the component and helps screen readers interpret the content. Caption can be visually hidden from the UI using visuallyHidden if not desired. |
Search bar | The search bar enables users to search the data set for specific records. |
Primary action | An optional primary action can be used to guide users to a workflow for adding additional records. |
Selection details | When using bulk select, the “selected” area shows the number of selected rows and offers the ability to select/clear all selected records in the data set. Selections persist when additional filters and searches are applied and when paging through the data. |
Actions | This optional menu can be used to apply bulk actions to selected rows or table-specific actions like exporting data. |
Filters | The Filters button toggles a Modal that enables users to apply filters to the DataGrid. Once a Filter is added, the Filters icon shows an indicator dot to inform the user that the data is filtered. |
Header | Includes headers for each column, which can be sortable. A checkbox can be used in the Header to select all visible rows; the “select all” option within “Selection details” can be used to select the entire dataset. |
Selected action | When using bulk select, actions are displayed in the left-most cell in the DataGrid. |
Footer/pagination | Pagination lives in the Footer content area and enables users to customize the number of results per page and cycle through data page by page or jump to the first or last page. Pagination should always be used with Tables unless data is static (not populated by user input). |
- Refer to the Loaders in Table docs for general guidance on loaders
- Meaningful user feedback regarding states should be provided via the
emptyState
anderrorState
props - The states can be triggered by passing the boolean props
empty
,error
, orloading
- The error or empty state will not be shown unless the content has finished loading
- The loading state only prevents interaction with table body content
- Sorting table headers and pagination are still available
DataGrid complies with WAI-ARIA authoring guides. They are accessible to screen readers and operable from a keyboard using automatic activation.
Keys | Action |
---|---|
Arrow keys (when a cell is focused) | Cycle through cells in the DataGrid |
Arrow keys (when the Table is focused) | Scroll the Table |
Home and End | Move to the first and last cells in the focused row |
Page up and Page down | Move up and down 10 rows at a time |
ctrl + shift + home and ctrl + shift + end | Go to the first cell in the table and the last cell in the table |
cmd + a | Select all checkboxes |
Select a cell, then shift + Click on another cell | Select a continuous set of checkboxes |
Space (when focused on an actionable header cell) | Toggle sort control or form element (checkbox, radio) |
Tab and Shift + Tab | Navigate between elements |
Space or Return | Trigger the action on the focused element (varies by element type) |
Space or Return | Trigger the action on the focused element (varies by element type) |
Return and Escape | Activate or deactivate a focus trap on a cell that contains interactive content |
Name | Type | Default | Description |
---|---|---|---|
bulkActions | ReactNode | Menu items rendered in the “More actions” menu for taking action when a selection is made. Recommended to be an array or fragment of MenuItem or similar elements recommended for use in a Menu. Note: These are always visible and should have an interstitial experience if no selection is made to either cancel or select all items. | |
checkboxes | boolean | If true, selection checkboxes will be rendered as the first column. | |
columns Required | DataGridColumn<TRow>[] | Metadata to determine how each column is rendered
| |
emptyState | ReactNode | Content displayed over top of the table when no rows are present. Will not show until loading is complete. | |
error | boolean | If true, the errorState is displayed and the body content disappears. Will not show until loading is complete. | |
errorState | ReactNode | Content displayed over top of the table when an error occurs. Will not show until loading is complete. | |
filters | ReactNode | Content rendered for filtering the table. Recommended to be an IconButton with a Badge to indicate active filters and which controls a corresponding Dialog. | |
label Required | string | Accessible name for the DataGrid. Visually hidden. | |
loading | boolean | If true, a loading spinner is displayed and interaction with the body content of the DataGrid is prevented. Interaction with the header, toolbar, and pagination controls is still permitted. | |
moreActions | ReactNode | Menu items rendered in the “More actions” menu. Recommended to be an array or fragment of MenuItem or similar elements recommended for use in a Menu. | |
onColumnsChange | ((columns: DataGridColumn<TRow>[]) => void) | Update callback for columns changes such as reorder and resize. | |
onSelectedRowsChange | ((selectedRows: Set<TRow["id"]>) => void) | Called when row selection changes. Provided with an updated selectedRows . | |
onSelectionModeChange | ((newSelectionMode: DataGridSelectionMode) => void) | Called when selection mode changes. Provided with an updated selectionMode value. | |
onSortModelChange | ((sortModel: DataGridSortModel) => void) | Callback when sorting changes. Provided with the new sort model array. | |
pagination | boolean | If true, pagination controls are displayed underneath the table. | |
paginationProps | Omit<TablePaginationProps, "aria-label"> | Props for the TablePagination element:
| |
primaryAction | ReactNode | Content rendered for taking action on the table such as adding an entry. Recommended to be an IconButton with color="primary" . | |
ref | Ref<HTMLTableElement> | A ref to the table. | |
rows Required | TRow[] | Data rendered within the table. The table will show ALL of these rows so if the table is paginated, this must be a slice of the data. This data is any object which has a required id property of string or number. | |
search | ReactNode | Content rendered for searching the table. Typically a TextField. | |
selectedRows | Set<TRow["id"]> | A set of ids representing the rows in the table which are currently selected. This is unpaginated. | |
selectionMode | all manual | Determines if “all” or only some elements are selected. This is needed for performance reasons. | |
sortModel | DataGridSortModel | The current model used to sort the items in the grid. This is an array because sorting may be multi-level. Array of:
|
The following testing snippet(s) offer suggestions for testing the component using React Testing Library with occasional help from Jest.
Info:
Heads up!
Testing Table (and DataGrid) can be resource intensive and could potentially cause timeouts and flakes due to the number of DOM nodes generated. Consider breaking tests into smaller chunks to make testing more reliable.
render(<><TableSection aria-labelledby="employee-hours"><Table><TableCaption id="employee-hours">Billable hours</TableCaption><TableHeader><TableRow><TableHeaderCell align="start">Full name</TableHeaderCell><TableHeaderCellSortable sort="descending" align="end">Hours</TableHeaderCellSortable></TableRow></TableHeader><TableBody><TableRow><TableHeaderCell scope="row">Hannah Ardent</TableHeaderCell><TableDataCell align="end">40</TableDataCell></TableRow><TableRow><TableHeaderCell scope="row">Isaiah Berlin</TableHeaderCell><TableDataCell align="end">32</TableDataCell></TableRow></TableBody><TableFooter><TableRow><TableHeaderCell scope="row">Total billable hours</TableHeaderCell><TableDataCell align="end">72</TableDataCell></TableRow></TableFooter></Table></TableSection><TablePaginationaria-label="Basic pagination"pageSize={10}previousDisablednextDisabledonPageChange={jest.fn()}onPageSizeChange={jest.fn()}/></>,);// TableSectionconst section = screen.getByRole('region', {name: 'Billable hours',});// TableSection -> Tableconst table = within(section).getByRole('table', {name: 'Billable hours',});// TableSection -> Table -> TableRowconst [headerRow, hannahRow, isaiahRow, footerRow] = within(table).getAllByRole('row');// TableSection -> Table -> TableRow -> TableHeaderCellconst [fullNameHeaderCell, billableHoursHeaderCell] = within(headerRow).getAllByRole('columnheader',);expect(fullNameHeaderCell).toHaveAccessibleName('Full name');expect(billableHoursHeaderCell).toHaveAccessibleName('Hours');expect(billableHoursHeaderCell).toHaveAttribute('aria-sort', 'descending');// TableSection -> Table -> TableRow -> TableHeaderCellconst hannahFullNameCell = within(hannahRow).getByRole('rowheader', {name: 'Hannah Ardent',});// TableSection -> Table -> TableRow -> TableHeaderCellconst isaiahFullNameCell = within(isaiahRow).getByRole('rowheader', {name: 'Isaiah Berlin',});expect(hannahFullNameCell).toBeInTheDocument();expect(isaiahFullNameCell).toBeInTheDocument();// TableSection -> Table -> TableRow -> TableDataCellconst [hannahHoursCell] = within(hannahRow).getAllByRole('cell');expect(hannahHoursCell).toHaveAccessibleName('40');// TableSection -> Table -> TableRow -> TableDataCellconst [isaiahHoursCell] = within(isaiahRow).getAllByRole('cell');expect(isaiahHoursCell).toHaveAccessibleName('32');// TableSection -> Table -> TableRow -> TableHeaderCellconst totalBillableHoursHeaderCell = within(footerRow).getByRole('rowheader', {name: 'Total billable hours',});expect(totalBillableHoursHeaderCell).toBeInTheDocument();// TableSection -> Table -> TableRow -> TableDataCellconst [totalBillableHoursCell] = within(footerRow).getAllByRole('cell');expect(totalBillableHoursCell).toHaveAccessibleName('72');// TablePaginationconst tablePagination = screen.getByRole('navigation', {name: 'Basic pagination',});// TablePagination -> Selectconst pageSizeSelect = within(tablePagination).getByRole('combobox', {name: 'Per page:',});expect(pageSizeSelect).toHaveDisplayValue('10');// TablePagination -> Buttonconst firstPageButton = within(tablePagination).getByRole('button', { name: 'First page' });const previousPageButton = within(tablePagination).getByRole('button', {name: 'Previous page',});const nextPageButton = within(tablePagination).getByRole('button', { name: 'Next page' });const lastPageButton = within(tablePagination).getByRole('button', { name: 'Last page' });expect(firstPageButton).toBeDisabled();expect(previousPageButton).toBeDisabled();expect(nextPageButton).toBeDisabled();expect(lastPageButton).toBeDisabled();