Skip to content

DataGrid

DataGrid is an enhanced version of Table that enables users to organize data by searching, sorting, and filtering results.
Figma logo
Anatomy of DataGrid
Tabs and its sub-components
Anatomy of the Tabs component
ElementPurpose
CaptionUsing 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 barThe search bar enables users to search the data set for specific records.
Primary actionAn optional primary action can be used to guide users to a workflow for adding additional records.
Selection detailsWhen 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.
ActionsThis optional menu can be used to apply bulk actions to selected rows or table-specific actions like exporting data.
FiltersThe 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.
HeaderIncludes 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 actionWhen using bulk select, actions are displayed in the left-most cell in the DataGrid.
Footer/paginationPagination 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 and errorState props
  • The states can be triggered by passing the boolean props empty, error, or loading
  • 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-ARIAExternal link authoring guides. They are accessible to screen readers and operable from a keyboard using automatic activation.

Navigating DataGrid with a keyboard
KeysAction
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 EndMove to the first and last cells in the focused row
Page up and Page downMove up and down 10 rows at a time
ctrl + shift + home and ctrl + shift + endGo to the first cell in the table and the last cell in the table
cmd + aSelect all checkboxes
Select a cell, then shift + Click on another cellSelect a continuous set of checkboxes
Space (when focused on an actionable header cell)Toggle sort control or form element (checkbox, radio)
Tab and Shift + TabNavigate between elements
Space or ReturnTrigger the action on the focused element (varies by element type)
Space or ReturnTrigger the action on the focused element (varies by element type)
Return and EscapeActivate or deactivate a focus trap on a cell that contains interactive content
React props
NameTypeDefaultDescription
bulkActions  ReactNodeMenu 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  booleanIf true, selection checkboxes will be rendered as the first column.
columns  RequiredDataGridColumn<TRow>[]

Metadata to determine how each column is rendered

  • field (string): Key for this column. By default will be used as a key to access the data in each row as row[0][field]
  • headerName (string): Column header text
  • align (optional; 'start' | 'center' | 'end' | 'justify'): Default content alignment for the column. Can be overridden by the render function
  • sortable (optional; boolean): If true, renders the header as a button with arrows to indicate sort direction
  • hidden (optional; boolean): If true, this column will not be visible
  • getValue (optional; function): Determines the value of the content in this cell. Essentially a simplified render function. Provided things like row and columns as props.
  • renderDataCell (optional; function): Fn Custom renderer for the cell. Provided things like row and columns.
emptyState  ReactNodeContent displayed over top of the table when no rows are present. Will not show until loading is complete.
error  booleanIf true, the errorState is displayed and the body content disappears. Will not show until loading is complete.
errorState  ReactNodeContent displayed over top of the table when an error occurs. Will not show until loading is complete.
filters  ReactNodeContent rendered for filtering the table. Recommended to be an IconButton with a Badge to indicate active filters and which controls a corresponding Dialog.
label  RequiredstringAccessible name for the DataGrid. Visually hidden.
loading  booleanIf 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  ReactNodeMenu 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  booleanIf true, pagination controls are displayed underneath the table.
paginationProps  Omit<TablePaginationProps, "aria-label">

Props for the TablePagination element:

  • pageSize (number): Number of rows per page.
  • previousDisabled (boolean): If true, the previous and first page buttons will be disabled.
  • nextDisabled (boolean): If true, the next and last page buttons will be disabled.
  • onPageChange (function): Called when one of the pagination buttons is clicked. Provided with the mouse event and the type of change: 'first' | 'last' | 'previous' | 'next'.
  • onPageSizeChange (function): Called when the page size select is updated. Provided with the change event and the new size as a number.
primaryAction  ReactNodeContent 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  RequiredTRow[]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  ReactNodeContent 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  allmanualDetermines 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:

  • field (string): Field key
  • sort (optional: 'ascending' | 'descending' | 'none'): Sort direction

The following testing snippet(s) offer suggestions for testing the component using React Testing LibraryExternal link with occasional help from JestExternal link.

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>
<TablePagination
aria-label="Basic pagination"
pageSize={10}
previousDisabled
nextDisabled
onPageChange={jest.fn()}
onPageSizeChange={jest.fn()}
/>
</>,
);
// TableSection
const section = screen.getByRole('region', {
name: 'Billable hours',
});
// TableSection -> Table
const table = within(section).getByRole('table', {
name: 'Billable hours',
});
// TableSection -> Table -> TableRow
const [headerRow, hannahRow, isaiahRow, footerRow] = within(table).getAllByRole('row');
// TableSection -> Table -> TableRow -> TableHeaderCell
const [fullNameHeaderCell, billableHoursHeaderCell] = within(headerRow).getAllByRole(
'columnheader',
);
expect(fullNameHeaderCell).toHaveAccessibleName('Full name');
expect(billableHoursHeaderCell).toHaveAccessibleName('Hours');
expect(billableHoursHeaderCell).toHaveAttribute('aria-sort', 'descending');
// TableSection -> Table -> TableRow -> TableHeaderCell
const hannahFullNameCell = within(hannahRow).getByRole('rowheader', {
name: 'Hannah Ardent',
});
// TableSection -> Table -> TableRow -> TableHeaderCell
const isaiahFullNameCell = within(isaiahRow).getByRole('rowheader', {
name: 'Isaiah Berlin',
});
expect(hannahFullNameCell).toBeInTheDocument();
expect(isaiahFullNameCell).toBeInTheDocument();
// TableSection -> Table -> TableRow -> TableDataCell
const [hannahHoursCell] = within(hannahRow).getAllByRole('cell');
expect(hannahHoursCell).toHaveAccessibleName('40');
// TableSection -> Table -> TableRow -> TableDataCell
const [isaiahHoursCell] = within(isaiahRow).getAllByRole('cell');
expect(isaiahHoursCell).toHaveAccessibleName('32');
// TableSection -> Table -> TableRow -> TableHeaderCell
const totalBillableHoursHeaderCell = within(footerRow).getByRole('rowheader', {
name: 'Total billable hours',
});
expect(totalBillableHoursHeaderCell).toBeInTheDocument();
// TableSection -> Table -> TableRow -> TableDataCell
const [totalBillableHoursCell] = within(footerRow).getAllByRole('cell');
expect(totalBillableHoursCell).toHaveAccessibleName('72');
// TablePagination
const tablePagination = screen.getByRole('navigation', {
name: 'Basic pagination',
});
// TablePagination -> Select
const pageSizeSelect = within(tablePagination).getByRole('combobox', {
name: 'Per page:',
});
expect(pageSizeSelect).toHaveDisplayValue('10');
// TablePagination -> Button
const 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();