DataGrid
DataGrid is similar to Table, paired with Control Layout and Pagination to allow for bulk actions, searching, sorting, and filtering of data.

Item | Name | Description |
---|---|---|
A | Control Layout | The Control Layout allows the user to search for results, filter, manipulate display options as well as select and deselect rows to perform actions. The <ControlLayout /> helper can be used to get the correct spacing and layout of this section. |
B | <DataGridHeader /> | The <DataGridHeader /> displays the columns title and allows them to sort. <DataGridHeader /> may also contain <DataGridSelectRowHeaderCell /> and <DataGridHeaderCell /> components |
C | <DataGridRow /> | The <DataGridRow /> displays particular data from column to column. <DataGridRow /> may also contain <DataGridSelectRowCell /> , <DataGridHeaderCell span="row" /> , and <DataGridDataCell /> components. |
D | <Pagination /> | The <Pagination /> controls how many items are displayed per page and is also used as navigation from page to page. |
E | <DataGrid /> | The <DataGrid /> contains the data of the DataGrid. |



Select row | Job Title | Email address | ||
---|---|---|---|---|
Liana Varo | Marketing | Contractor | ||
Henry Cote | Human Resources | Engineer | ||
Maria Stephens | Marketing | Chief Editor | ||
Mark Brouwer | Accounting and Finance | People Operations | ||
Tatiana Robu | R&D | Designer | ||
Ruby Nguyen | Sales | Sr. Engineer | ||
Sergio Guevara | Sales | Account Manager | ||
Rafe Walker | Accounting and Finance | Information Security Specialist | ||
Fabian Lupea | Legal | Office Operations | ||
Sienna Brown | Operations | CTO | ||
Zoe Mordin | Marketing | Recruiter | ||
Vinay Katwal | Purchasing | Account Director | ||
Charlie King | IT | Project Manager | ||
Maria Mancini | Marketing | Client Support Manager | ||
Dorothy Bishop | IT | Marketing Director | ||
Victor Dubois | Sales | Recruiter | ||
Phillip Carroll | Sales | Client Support Director | ||
Willie Morrison | Security | Level 1 Tech Suport | ||
Liam Jones | Employee | Associate Creative Director | ||
Felix Roth | Human Resources | Contractor | ||
Noah Anderson | Accounting and Finance | Engineer | ||
Sienna Smith | Operations | Sr. Designer | ||
Craig Ellis | Legal | Office Administrator | ||
Rosa Amador | Sales | Client Support Manager | ||
Alexis Muller | IT | IT Specialist | ||
Jasmine Jacob | Human Resources | IT Ops Lead | ||
Emily Lee | Legal | CEO |
Show code
import {
Box,
Button,
Checkbox,
CheckboxGroup,
ControlLayout,
DataGrid,
DataGridBody,
DataGridDataCell,
DataGridHeader,
DataGridHeaderCell,
DataGridRow,
DataGridSelectRowDataCell,
DataGridSelectRowHeaderCell,
Dialog,
DialogActions,
DialogBody,
DialogFooter,
DialogHeader,
IconButton,
Link,
Menu,
MenuItem,
Pagination,
PaginationNavigation,
PaginationNavigationButton,
PaginationPerPageSelectField,
TextField,
useBreakpoint,
useMenu,
} from '@gusto/workbench';
import React, { useState } from 'react';
import { CaretDown, Filter, More, Search } from '@gusto/workbench-icons';
import { employees } from './_employees';
export const Basic = () => {
const overSmallBreakpoint = useBreakpoint('sm');
// default "columns" checkbox set on page load
const defaultColumnsOptions = [
'name',
'department',
'jobTitle',
'emailAddress',
];
// default filters and their selection on page load
const defaultFilterNames = {
Department: [],
Title: [],
};
// Menu props for the actionsMenu and filterColumnsMenu in `sm` breakpoint
const [actionsMenuProps, actionsMenuButtonProps] = useMenu();
const [filterColumnsMenuProps, filterColumnsMenuButtonProps] = useMenu();
// filter and "columns" Dialog 'open' state
const [filterOpen, setFilterOpen] = useState(false);
const [columnsOpen, setColumnsOpen] = useState(false);
const [rowSelected, setRowSelected] = React.useState<{
[key: number]: boolean;
}>({});
// used to keep track of the onChange of the "columns" checkboxes in columnsDialog
const [columnsCheckboxChecked, setColumnsCheckboxChecked] = React.useState<
string[]
>(defaultColumnsOptions);
// used to make changes to the table on "Apply" of the filters dialog
const [columnsDisplayed, setColumnsDisplayed] = React.useState<string[]>(
defaultColumnsOptions,
);
// used to keep track of the onChange of the filter checkboxes
const [filterCheckboxChecked, setFilterCheckboxChecked] = React.useState<{
[key: string]: string[];
}>(defaultFilterNames);
// used to make changes to the table on "Apply" of the filters dialog
const [appliedFilterCheckboxes, setAppliedFilterCheckboxes] = React.useState<{
[key: string]: string[];
}>(defaultFilterNames);
// used to determine filter count, if filters are applied, and filters data
const filterCount =
appliedFilterCheckboxes.Department.length +
appliedFilterCheckboxes.Title.length;
const isDataFiltered =
appliedFilterCheckboxes.Department.length > 0 ||
appliedFilterCheckboxes.Title.length > 0;
const filteredEmployees = isDataFiltered
? employees.filter(employee => {
return (
appliedFilterCheckboxes.Department.includes(employee.Department) ||
appliedFilterCheckboxes.Title.includes(employee.Title)
);
})
: employees;
// used to determine if the "select all" checkbox is checked, indeterminate, or unchecked
const rowSelectedCount = Object.values(rowSelected).filter(val => val).length;
const selectAllChecked = rowSelectedCount === filteredEmployees.length;
const selectAllIndeterminate = rowSelectedCount > 0 && !selectAllChecked;
// an example of keeping track of rows selected
const toggleGridRowSelection = (id: number) => {
setRowSelected(currSelected => ({
...currSelected,
[id]: !rowSelected[id],
}));
};
// an example of creating the "select all" functionality
const toggleAllRows = () => {
employees
.map(employee => employee.Digit)
.forEach(digit => {
setRowSelected(currRowsSelected => ({
...currRowsSelected,
[digit as number]: !selectAllChecked,
}));
});
};
// an example of keeping a state management of each columns checkbox being checked
const columnsCheckboxOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const checkboxChecked = e.target.checked;
if (checkboxChecked) {
setColumnsCheckboxChecked(currChecked => [
...currChecked,
e.target.value,
]);
} else {
setColumnsCheckboxChecked(currChecked =>
currChecked.filter(value => value !== e.target.value),
);
}
};
// an example of resetting columns options
const resetColumnsOptions = () => {
setColumnsCheckboxChecked(defaultColumnsOptions);
};
// an example of updating the visibility of columns
const updateColumnVisibility = () => {
setColumnsDisplayed(columnsCheckboxChecked);
};
// an example of keeping a state management of each filter checkbox being checked
const filterCheckboxOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// getting the group name from CheckboxGroup
const groupName = e.target.name;
const checkboxChecked = e.target.checked;
if (checkboxChecked) {
setFilterCheckboxChecked(currChecked => ({
...currChecked,
[groupName]: [...currChecked[groupName], e.target.value],
}));
} else {
setFilterCheckboxChecked(currChecked => ({
...currChecked,
[groupName]: currChecked[groupName].filter(
value => value !== e.target.value,
),
}));
}
};
// an example of applying filters on "apply" button submit within filters Dialog
const applyFilters = () => {
setAppliedFilterCheckboxes(filterCheckboxChecked);
setFilterOpen(false);
};
// an example of resetting filters within the filters Dialog
const resetFilters = () => {
setFilterCheckboxChecked(defaultFilterNames);
};
return (
<>
<ControlLayout
searchField={
<TextField
label="Search"
before={<Search />}
placeholder="Search"
visuallyHideLabel
/>
}
selectAllCheckbox={
<Checkbox
type="checkbox"
label={
rowSelectedCount > 0
? `${rowSelectedCount} Selected`
: `Select all (${filteredEmployees.length})`
}
checked={selectAllChecked}
indeterminate={selectAllIndeterminate}
onChange={toggleAllRows}
/>
}
selectAllActionsMenu={
selectAllChecked || selectAllIndeterminate ? (
<>
<Button
{...actionsMenuButtonProps}
after={<CaretDown />}
size="small"
variant="primary"
>
Actions
</Button>
<Menu
{...actionsMenuProps}
aria-label="Employee information actions menu"
>
<MenuItem>Lorem ipsum</MenuItem>
<MenuItem>Lorem ipsum</MenuItem>
</Menu>
</>
) : null
}
actions={
<>
{overSmallBreakpoint ? (
<>
<Button
aria-haspopup="dialog"
variant="tertiary"
size="small"
before={<Filter />}
onClick={() => setFilterOpen(true)}
>
Filter{filterCount > 0 ? ` (${filterCount})` : null}
</Button>
<Button
aria-haspopup="dialog"
variant="tertiary"
size="small"
onClick={() => setColumnsOpen(true)}
>
Columns
</Button>
</>
) : (
<>
<IconButton
{...filterColumnsMenuButtonProps}
title="More actions"
>
<More />
</IconButton>
<Menu
{...filterColumnsMenuProps}
aria-label="Employee information filters menu"
>
<MenuItem
aria-haspopup="dialog"
onClick={() => setFilterOpen(true)}
>
Filter{filterCount > 0 ? ` (${filterCount})` : null}
</MenuItem>
<MenuItem
aria-haspopup="dialog"
onClick={() => setColumnsOpen(true)}
>
Columns
</MenuItem>
</Menu>
</>
)}
<Dialog
open={filterOpen}
aria-label="Employee information filters"
onClose={() => setFilterOpen(false)}
>
<DialogHeader heading="Filters" />
<DialogBody>
<Box marginBottom={6}>
<CheckboxGroup name="Department" legend="Departments">
{Array.from(
new Set(employees.map(employee => employee.Department)),
).map(department => {
return (
<Checkbox
key={`emp-type-checkbox-${department}`}
value={department}
label={department}
checked={filterCheckboxChecked.Department.includes(
department,
)}
onChange={filterCheckboxOnChange}
/>
);
})}
</CheckboxGroup>
</Box>
<Box marginBottom={6}>
<CheckboxGroup name="Title" legend="Job title">
{Array.from(
new Set(employees.map(employee => employee.Title)),
).map(title => {
return (
<Checkbox
key={`emp-type-checkbox-${title}`}
value={title}
label={title}
checked={filterCheckboxChecked.Title.includes(title)}
onChange={filterCheckboxOnChange}
/>
);
})}
</CheckboxGroup>
</Box>
</DialogBody>
<DialogFooter>
<DialogActions>
<Button variant="primary" onClick={applyFilters}>
Apply
</Button>
<Button onClick={resetFilters}>Clear all filters</Button>
</DialogActions>
</DialogFooter>
</Dialog>
<Dialog
open={columnsOpen}
aria-label="Employee information visible columns"
onClose={() => setColumnsOpen(false)}
>
<DialogHeader heading="Columns" />
<DialogBody>
<CheckboxGroup
name="columnsOptions"
legend="Column options"
visuallyHideLabel
>
<Checkbox
value="name"
label="Name"
checked={columnsCheckboxChecked.includes('name')}
onChange={columnsCheckboxOnChange}
/>
<Checkbox
value="department"
label="Department"
checked={columnsCheckboxChecked.includes('department')}
onChange={columnsCheckboxOnChange}
/>
<Checkbox
value="jobTitle"
label="Job title"
checked={columnsCheckboxChecked.includes('jobTitle')}
onChange={columnsCheckboxOnChange}
/>
<Checkbox
value="emailAddress"
label="Email address"
checked={columnsCheckboxChecked.includes('emailAddress')}
onChange={columnsCheckboxOnChange}
/>
</CheckboxGroup>
</DialogBody>
<DialogFooter>
<DialogActions>
<Button
variant="primary"
onClick={() => {
updateColumnVisibility();
setColumnsOpen(false);
}}
>
Apply
</Button>
<Button onClick={resetColumnsOptions}>Reset</Button>
</DialogActions>
</DialogFooter>
</Dialog>
</>
}
/>
<DataGrid
aria-colcount={4}
aria-rowcount={filteredEmployees.length + 1}
aria-label="Employee information"
gridSectionStyles={{ maxHeight: '50rem' }}
>
<DataGridHeader>
<DataGridRow>
<DataGridSelectRowHeaderCell />
{columnsDisplayed.includes('name') && (
<DataGridHeaderCell sortable>Name</DataGridHeaderCell>
)}
{columnsDisplayed.includes('department') && (
<DataGridHeaderCell sortable>Department</DataGridHeaderCell>
)}
{columnsDisplayed.includes('jobTitle') && (
<DataGridHeaderCell>Job Title</DataGridHeaderCell>
)}
{columnsDisplayed.includes('emailAddress') && (
<DataGridHeaderCell>Email address</DataGridHeaderCell>
)}
</DataGridRow>
</DataGridHeader>
<DataGridBody>
{filteredEmployees.map(employee => {
return (
<DataGridRow
key={`emp-${employee.Digit}`}
rowSelected={rowSelected[employee.Digit]}
>
<DataGridSelectRowDataCell
label="Select row"
checked={rowSelected[employee.Digit] || false}
onChange={() => toggleGridRowSelection(employee.Digit)}
/>
{columnsDisplayed.includes('name') && (
<DataGridHeaderCell scope="row">
<Link href="https://gusto.com">{employee['Name-FL']}</Link>
</DataGridHeaderCell>
)}
{columnsDisplayed.includes('department') && (
<DataGridDataCell>{employee.Department}</DataGridDataCell>
)}
{columnsDisplayed.includes('jobTitle') && (
<DataGridDataCell>{employee.Title}</DataGridDataCell>
)}
{columnsDisplayed.includes('emailAddress') && (
<DataGridDataCell>
<TextField
label="Email address"
visuallyHideLabel
value={employee.Email}
readOnly
/>
</DataGridDataCell>
)}
</DataGridRow>
);
})}
</DataGridBody>
</DataGrid>
<Pagination
position="sticky"
aria-label="Employee information table"
variant="joined"
>
<PaginationPerPageSelectField />
<PaginationNavigation>
<PaginationNavigationButton variant="first" />
<PaginationNavigationButton variant="previous" />
<PaginationNavigationButton variant="next" />
<PaginationNavigationButton variant="last" />
</PaginationNavigation>
</Pagination>
</>
);
};
- NameLiana Varo
- DepartmentMarketing
- Job titleContractor
- Email address
- NameHenry Cote
- DepartmentHuman Resources
- Job titleEngineer
- Email address
- NameMaria Stephens
- DepartmentMarketing
- Job titleChief Editor
- Email address
- NameMark Brouwer
- DepartmentAccounting and Finance
- Job titlePeople Operations
- Email address
- NameTatiana Robu
- DepartmentR&D
- Job titleDesigner
- Email address
- NameRuby Nguyen
- DepartmentSales
- Job titleSr. Engineer
- Email address
- NameSergio Guevara
- DepartmentSales
- Job titleAccount Manager
- Email address
- NameRafe Walker
- DepartmentAccounting and Finance
- Job titleInformation Security Specialist
- Email address
- NameFabian Lupea
- DepartmentLegal
- Job titleOffice Operations
- Email address
- NameSienna Brown
- DepartmentOperations
- Job titleCTO
- Email address
- NameZoe Mordin
- DepartmentMarketing
- Job titleRecruiter
- Email address
- NameVinay Katwal
- DepartmentPurchasing
- Job titleAccount Director
- Email address
- NameCharlie King
- DepartmentIT
- Job titleProject Manager
- Email address
- NameMaria Mancini
- DepartmentMarketing
- Job titleClient Support Manager
- Email address
- NameDorothy Bishop
- DepartmentIT
- Job titleMarketing Director
- Email address
- NameVictor Dubois
- DepartmentSales
- Job titleRecruiter
- Email address
- NamePhillip Carroll
- DepartmentSales
- Job titleClient Support Director
- Email address
- NameWillie Morrison
- DepartmentSecurity
- Job titleLevel 1 Tech Suport
- Email address
- NameLiam Jones
- DepartmentEmployee
- Job titleAssociate Creative Director
- Email address
- NameFelix Roth
- DepartmentHuman Resources
- Job titleContractor
- Email address
- NameNoah Anderson
- DepartmentAccounting and Finance
- Job titleEngineer
- Email address
- NameSienna Smith
- DepartmentOperations
- Job titleSr. Designer
- Email address
- NameCraig Ellis
- DepartmentLegal
- Job titleOffice Administrator
- Email address
- NameRosa Amador
- DepartmentSales
- Job titleClient Support Manager
- Email address
- NameAlexis Muller
- DepartmentIT
- Job titleIT Specialist
- Email address
- NameJasmine Jacob
- DepartmentHuman Resources
- Job titleIT Ops Lead
- Email address
Show code
import {
Box,
Button,
Checkbox,
CheckboxGroup,
ControlLayout,
DataCard,
DataCardCheckbox,
DataCardList,
DataCardRecord,
Dialog,
DialogActions,
DialogBody,
DialogFooter,
DialogHeader,
IconButton,
Link,
Menu,
MenuItem,
Pagination,
PaginationNavigation,
PaginationNavigationButton,
PaginationPerPageSelectField,
TextField,
useBreakpoint,
useMenu,
} from '@gusto/workbench';
import React, { useState } from 'react';
import { CaretDown, Filter, More, Search } from '@gusto/workbench-icons';
import { employees } from './_employees';
export const WithDataCard = () => {
const overSmallBreakpoint = useBreakpoint('sm');
// default "columns" checkbox set on page load
const defaultColumnsOptions = [
'name',
'department',
'jobTitle',
'emailAddress',
];
// default filters and their selection on page load
const defaultFilterNames = {
Department: [],
Title: [],
};
// Menu props for the actionsMenu and filterColumnsMenu in `sm` breakpoint
const [actionsMenuProps, actionsMenuButtonProps] = useMenu();
const [filterColumnsMenuProps, filterColumnsMenuButtonProps] = useMenu();
// filter and "columns" Dialog 'open' state
const [filterOpen, setFilterOpen] = useState(false);
const [columnsOpen, setColumnsOpen] = useState(false);
const [rowSelected, setRowSelected] = React.useState<{
[key: number]: boolean;
}>({});
// used to keep track of the onChange of the "columns" checkboxes in columnsDialog
const [columnsCheckboxChecked, setColumnsCheckboxChecked] = React.useState<
string[]
>(defaultColumnsOptions);
// used to make changes to the table on "Apply" of the filters dialog
const [columnsDisplayed, setColumnsDisplayed] = React.useState<string[]>(
defaultColumnsOptions,
);
// used to keep track of the onChange of the filter checkboxes
const [filterCheckboxChecked, setFilterCheckboxChecked] = React.useState<{
[key: string]: string[];
}>(defaultFilterNames);
// used to make changes to the table on "Apply" of the filters dialog
const [appliedFilterCheckboxes, setAppliedFilterCheckboxes] = React.useState<{
[key: string]: string[];
}>(defaultFilterNames);
// used to determine filter count, if filters are applied, and filters data
const filterCount =
appliedFilterCheckboxes.Department.length +
appliedFilterCheckboxes.Title.length;
const isDataFiltered =
appliedFilterCheckboxes.Department.length > 0 ||
appliedFilterCheckboxes.Title.length > 0;
const filteredEmployees = isDataFiltered
? employees.filter(employee => {
return (
appliedFilterCheckboxes.Department.includes(employee.Department) ||
appliedFilterCheckboxes.Title.includes(employee.Title)
);
})
: employees;
// used to determine if the "select all" checkbox is checked, indeterminate, or unchecked
const rowSelectedCount = Object.values(rowSelected).filter(val => val).length;
const selectAllChecked = rowSelectedCount === filteredEmployees.length;
const selectAllIndeterminate = rowSelectedCount > 0 && !selectAllChecked;
// an example of keeping track of rows selected
const toggleSelected = (id: number) => {
setRowSelected(currSelected => ({
...currSelected,
[id]: !rowSelected[id],
}));
};
// an example of creating the "select all" functionality
const toggleAllRows = () => {
employees
.map(employee => employee.Digit)
.forEach(digit => {
setRowSelected(currRowsSelected => ({
...currRowsSelected,
[digit as number]: !selectAllChecked,
}));
});
};
// an example of keeping a state management of each columns checkbox being checked
const columnsCheckboxOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const checkboxChecked = e.target.checked;
if (checkboxChecked) {
setColumnsCheckboxChecked(currChecked => [
...currChecked,
e.target.value,
]);
} else {
setColumnsCheckboxChecked(currChecked =>
currChecked.filter(value => value !== e.target.value),
);
}
};
// an example of resetting columns options
const resetColumnsOptions = () => {
setColumnsCheckboxChecked(defaultColumnsOptions);
};
// an example of updating the visibility of columns
const updateColumnVisibility = () => {
setColumnsDisplayed(columnsCheckboxChecked);
};
// an example of keeping a state management of each filter checkbox being checked
const filterCheckboxOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// getting the group name from CheckboxGroup
const groupName = e.target.name;
const checkboxChecked = e.target.checked;
if (checkboxChecked) {
setFilterCheckboxChecked(currChecked => ({
...currChecked,
[groupName]: [...currChecked[groupName], e.target.value],
}));
} else {
setFilterCheckboxChecked(currChecked => ({
...currChecked,
[groupName]: currChecked[groupName].filter(
value => value !== e.target.value,
),
}));
}
};
// an example of applying filters on "apply" button submit within filters Dialog
const applyFilters = () => {
setAppliedFilterCheckboxes(filterCheckboxChecked);
setFilterOpen(false);
};
// an example of resetting filters within the filters Dialog
const resetFilters = () => {
setFilterCheckboxChecked(defaultFilterNames);
};
return (
<Box maxWidth={450} maxHeight={840} overflow="auto" margin="auto">
<ControlLayout
searchField={
<TextField
label="Search"
before={<Search />}
placeholder="Search"
visuallyHideLabel
/>
}
selectAllCheckbox={
<Checkbox
type="checkbox"
label={
rowSelectedCount > 0
? `${rowSelectedCount} Selected`
: `Select all (${filteredEmployees.length})`
}
checked={selectAllChecked}
indeterminate={selectAllIndeterminate}
onChange={toggleAllRows}
/>
}
selectAllActionsMenu={
selectAllChecked || selectAllIndeterminate ? (
<>
<Button
{...actionsMenuButtonProps}
after={<CaretDown />}
size="small"
variant="primary"
>
Actions
</Button>
<Menu
{...actionsMenuProps}
aria-label="Employee information actions menu"
>
<MenuItem>Lorem ipsum</MenuItem>
<MenuItem>Lorem ipsum</MenuItem>
</Menu>
</>
) : null
}
actions={
<>
{overSmallBreakpoint ? (
<>
<Button
aria-haspopup="dialog"
variant="tertiary"
size="small"
before={<Filter />}
onClick={() => setFilterOpen(true)}
>
Filter{filterCount > 0 ? ` (${filterCount})` : null}
</Button>{' '}
<Button
aria-haspopup="dialog"
variant="tertiary"
size="small"
onClick={() => setColumnsOpen(true)}
>
Columns
</Button>
</>
) : (
<>
<IconButton
{...filterColumnsMenuButtonProps}
title="More actions"
>
<More />
</IconButton>
<Menu
{...filterColumnsMenuProps}
aria-label="Employee information filters menu"
>
<MenuItem
aria-haspopup="dialog"
onClick={() => setFilterOpen(true)}
>
Filter{filterCount > 0 ? ` (${filterCount})` : null}
</MenuItem>
<MenuItem
aria-haspopup="dialog"
onClick={() => setColumnsOpen(true)}
>
Columns
</MenuItem>
</Menu>
</>
)}
<Dialog
open={filterOpen}
aria-label="Employee information filters"
onClose={() => setFilterOpen(false)}
>
<DialogHeader heading="Filters" />
<DialogBody>
<Box marginBottom={6}>
<CheckboxGroup name="Department" legend="Departments">
{Array.from(
new Set(employees.map(employee => employee.Department)),
).map(department => {
return (
<Checkbox
key={`emp-type-checkbox-${department}`}
value={department}
label={department}
checked={filterCheckboxChecked.Department.includes(
department,
)}
onChange={filterCheckboxOnChange}
/>
);
})}
</CheckboxGroup>
</Box>
<Box marginBottom={6}>
<CheckboxGroup name="Title" legend="Job title">
{Array.from(
new Set(employees.map(employee => employee.Title)),
).map(title => {
return (
<Checkbox
key={`emp-type-checkbox-${title}`}
value={title}
label={title}
checked={filterCheckboxChecked.Title.includes(title)}
onChange={filterCheckboxOnChange}
/>
);
})}
</CheckboxGroup>
</Box>
</DialogBody>
<DialogFooter>
<DialogActions>
<Button variant="primary" onClick={applyFilters}>
Apply
</Button>
<Button onClick={resetFilters}>Clear all filters</Button>
</DialogActions>
</DialogFooter>
</Dialog>
<Dialog
open={columnsOpen}
aria-label="Employee information visible columns"
onClose={() => setColumnsOpen(false)}
>
<DialogHeader heading="Columns" />
<DialogBody>
<CheckboxGroup
name="columnsOptions"
legend="Column options"
visuallyHideLabel
>
<Checkbox
value="name"
label="Name"
checked={columnsCheckboxChecked.includes('name')}
onChange={columnsCheckboxOnChange}
/>
<Checkbox
value="department"
label="Department"
checked={columnsCheckboxChecked.includes('department')}
onChange={columnsCheckboxOnChange}
/>
<Checkbox
value="jobTitle"
label="Job title"
checked={columnsCheckboxChecked.includes('jobTitle')}
onChange={columnsCheckboxOnChange}
/>
<Checkbox
value="emailAddress"
label="Email address"
checked={columnsCheckboxChecked.includes('emailAddress')}
onChange={columnsCheckboxOnChange}
/>
</CheckboxGroup>
</DialogBody>
<DialogFooter>
<DialogActions>
<Button
variant="primary"
onClick={() => {
updateColumnVisibility();
setColumnsOpen(false);
}}
>
Apply
</Button>
<Button onClick={resetColumnsOptions}>Reset</Button>
</DialogActions>
</DialogFooter>
</Dialog>
</>
}
/>
<DataCardList aria-label="Employee information">
{employees.map(employee => {
return (
<DataCard
aria-label={employee['Name-FL']}
key={`emp-${employee.Digit}`}
selected={rowSelected[employee.Digit]}
>
<DataCardCheckbox
checked={rowSelected[employee.Digit] || false}
onChange={() => toggleSelected(employee.Digit)}
label={`Select entry for ${employee['Name-FL']}`}
/>
{columnsDisplayed.includes('name') ? (
<DataCardRecord heading="Name">
<Link href="https://gusto.com/">{employee['Name-FL']}</Link>
</DataCardRecord>
) : null}
{columnsDisplayed.includes('department') ? (
<DataCardRecord heading="Department">
<Link href="https://gusto.com/">{employee.Department}</Link>
</DataCardRecord>
) : null}
{columnsDisplayed.includes('jobTitle') ? (
<DataCardRecord heading="Job title">
<Link href="https://gusto.com/">{employee.Title}</Link>
</DataCardRecord>
) : null}
{columnsDisplayed.includes('emailAddress') ? (
<DataCardRecord heading="Email address">
<TextField
label="Email address"
visuallyHideLabel
value={employee.Email}
readOnly
/>
</DataCardRecord>
) : null}
</DataCard>
);
})}
</DataCardList>
<Pagination
aria-label="Employee information list"
variant="joined"
position="sticky"
>
<PaginationPerPageSelectField />
<PaginationNavigation>
<PaginationNavigationButton variant="first" />
<PaginationNavigationButton variant="previous" />
<PaginationNavigationButton variant="next" />
<PaginationNavigationButton variant="last" />
</PaginationNavigation>
</Pagination>
</Box>
);
};
Name | Type | Default | Description |
---|---|---|---|
aria-colcount Required | number | - | The total number of columns in the grid, if a value cannot be determined, set to -1. |
aria-label | string | - | Set the `aria-label` attribute to the `<table>` element. |
aria-labelledby | string | - | Set the `aria-labelledby` attribute to the `<table>` element. |
aria-rowcount Required | number | - | The total number of rows in the grid, inclusive of header and footer rows. If a value cannot be determined, set to -1. |
children | ReactNode | - | The content of the component. |
gridSectionStyles | CSSProperties | - | The styles for the containing section to adjust height and width of the grid. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `tbody`. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `td` data cell. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `thead`. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `th` cell. |
scope | row rowgroup col colgroup | col | The `scope` attribute of the DataGrid `th` cell. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `thead`. |
rowSelected | row rowgroup col colgroup | false | Use to show that this row is selected. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `td` cell of the "select row" column. |
label Required | ReactNode | false | Label text for the associated `<label>` of the checkbox within the cell. |
Name | Type | Default | Description |
---|---|---|---|
children | ReactNode | - | The content of the DataGrid `th` cell of the "select row" column. |
label | string | Select row | The visually hidden content for the "select row" column. |
- DataGrid must function up to 320px at 100% or 1280px at 400%For more information, see footnote 1,
- All labels and actions must be clear and unambiguousFor more information, see footnote 2,
- All functionality of the DataGrid must be operable through a keyboard interfaceFor more information, see footnote 3,
- The tap target for all interactive elements are at least 44px X 44pxFor more information, see footnote 4,
- All text meets the minimum contrast requirement of 4.5:1For more information, see footnote 5,
- All non-text elements must meet the appropriate contrast level to ensure an AA contrast level is metFor more information, see footnote 6,
All keyboard navigation guidance is based off the W3C APG guidelines for data grid patterns.
Keys | Action |
---|---|
Right Arrow , Left Arrow , Up Arrow ,Down Arrow | Cycle through the cells of the grid. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
Tab | First Tab will focus into the last focused cell/interactive element. Any subsequent will either focus to the next interactive element within that cell or focus on the next interactive element outside the grid. |
Page Down | Moves focus to the current position in the bottom row in the currently visible. If focus is in the last row of the grid, focus does not move. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
Page Up | Moves focus to the top row in the currently visible set of rows becomes one of the last visible rows. If focus is in the first row of the grid, focus does not move. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
Home | Moves focus to the first cell in the row that contains focus. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
End | Moves focus to the last cell in the row that contains focus. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
Control + Home | Moves focus to the first cell in the first row. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
Control + End | Moves focus to the last cell in the last row. If the cell contains interactive element(s) and the first one does not require arrow navigation, that element will be focused instead of the cell. |
Keys | Action |
---|---|
Enter | If interactive elements are present in the cell, focus will be moved to the first interactive element. |
Escape | Move focus to the cell for grid navigation. |
Tab | If mutiple interactive elements are present in the cell, focus will be moved to the next interactive element. If there are no more interactive elements within the cell, focus will go to the next interactive element outside the grid. |
The following testing snippet(s) offer suggestions for testing the component using React Testing Library with occasional help from Jest.
Info:
Heads up!
Testing DataGrid (and table) 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.
import {DataGrid,DataGridBody,DataGridDataCell,DataGridHeader,DataGridHeaderCell,DataGridRow,DataGridSelectRowDataCell,DataGridSelectRowHeaderCell,} from '@gusto/workbench';it('tests for basic data grid render and functionality', async () => {render(<DataGridaria-colcount={3}aria-rowcount={3}aria-label="Employee Grid"><DataGridHeader><DataGridRow><DataGridSelectRowHeaderCell /><DataGridHeaderCell>Full Name</DataGridHeaderCell><DataGridHeaderCell sortable sort="descending">Hours</DataGridHeaderCell></DataGridRow></DataGridHeader><DataGridBody><DataGridRow><DataGridSelectRowCelllabel="Select row for Hannah Ardent"onChange={jest.fn()}/><DataGridHeaderCell scope="row">Hannah Ardent</DataGridDataCell><DataGridDataCell align="end">40</DataGridDataCell></DataGridRow><DataGridRow><DataGridSelectRowCelllabel="Select row for Isaiah Berlin"onChange={jest.fn()}/><DataGridHeaderCell scope="row">Isaiah Berlin</DataGridDataCell><DataGridDataCell align="end">32</DataGridDataCell></DataGridRow></DataGridBody></DataGrid>);/** DataGrid */// Ensure the data grid has the expected accessible name and readingconst dataGrid = screen.getByRole('grid', { name: 'Employee Grid' });expect(dataGrid).toBeInTheDocument();/** DataGridRow */// Ensure each row existsconst [headerRow, hannahRow, isaiahRow] = within(dataGrid).getAllByRole('row');expect(headerRow).toBeInTheDocument();expect(hannahRow).toBeInTheDocument();expect(isaiahRow).toBeInTheDocument();/** DataGridHeaderCell */// Ensure each header cell existsconst [selectRowHeaderCell,fullNameHeaderCell,hoursHeaderCell] = within(headerRow).getAllByRole('columnheader');expect(selectRowHeaderCell).toBeInTheDocument();expect(fullNameHeaderCell).toBeInTheDocument();expect(hoursHeaderCell).toBeInTheDocument();// Assert on sort clickconst hoursHeaderCellSortButton = getByRole(hoursHeaderCell, 'button');// Click the sort buttonawait userEvent.click(hoursHeaderCellSortButton);// Assert sort expectations/** DataGridSelectRowDataCell */// Assert on row selectionconst hannahSelectRowCellCheckbox = within(hannaRow).getByRole('checkbox', {name: 'Select row for Hannah Ardent',})// Click the row selection checkboxawait userEvent.click(hannahSelectRowCellCheckbox);// Assert row selection expectationsexpect(hannahSelectRowCellCheckbox).toBeChecked();});