Skip to content

DataGrid

DataGrid is similar to Table, paired with Control Layout and Pagination to allow for bulk actions, searching, sorting, and filtering of data.
A pointing to a section for searching and various data interactions; B pointing to the header row of the DataGrid; C pointing to the data rows of the DataGrid; D pointing to the pagination of the DataGrid; E pointing to the entire grid of the DataGrid
DataGrid with Control Layout and Pagination
Anatomy of the DataGrid component
ItemNameDescription
AControl LayoutThe 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.
Breakdown of DataGrid styles (padding, height, widths, etc.) at a viewport of 601px and above
DataGrid with Control Layout and Pagination at a breakpoint of 1280px
Breakdown of DataGrid styles (padding, height, widths, etc.) at a viewport of 600px and below
DataGrid with Control Layout and Pagination at a breakpoint of 390px
Breakdown of DataCard styles (padding, height, widths, etc.) at a viewport of 600px and below
DataGrid with Control Layout and Pagination at a breakpoint of 390px
Select rowJob TitleEmail address
Liana VaroMarketingContractor
Henry CoteHuman ResourcesEngineer
Maria StephensMarketingChief Editor
Mark BrouwerAccounting and FinancePeople Operations
Tatiana RobuR&DDesigner
Ruby NguyenSalesSr. Engineer
Sergio GuevaraSalesAccount Manager
Rafe WalkerAccounting and FinanceInformation Security Specialist
Fabian LupeaLegalOffice Operations
Sienna BrownOperationsCTO
Zoe MordinMarketingRecruiter
Vinay KatwalPurchasingAccount Director
Charlie KingITProject Manager
Maria ManciniMarketingClient Support Manager
Dorothy BishopITMarketing Director
Victor DuboisSalesRecruiter
Phillip CarrollSalesClient Support Director
Willie MorrisonSecurityLevel 1 Tech Suport
Liam JonesEmployeeAssociate Creative Director
Felix RothHuman ResourcesContractor
Noah AndersonAccounting and FinanceEngineer
Sienna SmithOperationsSr. Designer
Craig EllisLegalOffice Administrator
Rosa AmadorSalesClient Support Manager
Alexis MullerITIT Specialist
Jasmine JacobHuman ResourcesIT Ops Lead
Emily LeeLegalCEO
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>
    </>
  );
};
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>
  );
};
React props
NameTypeDefaultDescription
aria-colcount  Requirednumber-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  Requirednumber-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.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `tbody`.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `td` data cell.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `thead`.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `th` cell.
scope  rowrowgroupcolcolgroupcolThe `scope` attribute of the DataGrid `th` cell.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `thead`.
rowSelected  rowrowgroupcolcolgroupfalseUse to show that this row is selected.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `td` cell of the "select row" column.
label  RequiredReactNodefalseLabel text for the associated `<label>` of the checkbox within the cell.
React props
NameTypeDefaultDescription
children  ReactNode-The content of the DataGrid `th` cell of the "select row" column.
label  stringSelect rowThe visually hidden content for the "select row" column.

All keyboard navigation guidance is based off the W3C APG guidelines for data grid patternsExternal link.

Navigation moving across cells in DataGrid
KeysAction
Right Arrow, Left Arrow, Up Arrow,Down ArrowCycle 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.
TabFirst 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 DownMoves 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 UpMoves 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.
HomeMoves 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.
EndMoves 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 + HomeMoves 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 + EndMoves 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.
Navigation within a cell of DataGrid with interactive elements
KeysAction
EnterIf interactive elements are present in the cell, focus will be moved to the first interactive element.
EscapeMove 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 LibraryExternal link with occasional help from JestExternal link.

import {
DataGrid,
DataGridBody,
DataGridDataCell,
DataGridHeader,
DataGridHeaderCell,
DataGridRow,
DataGridSelectRowDataCell,
DataGridSelectRowHeaderCell,
} from '@gusto/workbench';
it('tests for basic data grid render and functionality', async () => {
render(
<DataGrid
aria-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>
<DataGridSelectRowCell
label="Select row for Hannah Ardent"
onChange={jest.fn()}
/>
<DataGridHeaderCell scope="row">
Hannah Ardent
</DataGridDataCell>
<DataGridDataCell align="end">40</DataGridDataCell>
</DataGridRow>
<DataGridRow>
<DataGridSelectRowCell
label="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 reading
const dataGrid = screen.getByRole('grid', { name: 'Employee Grid' });
expect(dataGrid).toBeInTheDocument();
/** DataGridRow */
// Ensure each row exists
const [headerRow, hannahRow, isaiahRow] = within(dataGrid).getAllByRole('row');
expect(headerRow).toBeInTheDocument();
expect(hannahRow).toBeInTheDocument();
expect(isaiahRow).toBeInTheDocument();
/** DataGridHeaderCell */
// Ensure each header cell exists
const [
selectRowHeaderCell,
fullNameHeaderCell,
hoursHeaderCell
] = within(headerRow).getAllByRole('columnheader');
expect(selectRowHeaderCell).toBeInTheDocument();
expect(fullNameHeaderCell).toBeInTheDocument();
expect(hoursHeaderCell).toBeInTheDocument();
// Assert on sort click
const hoursHeaderCellSortButton = getByRole(hoursHeaderCell, 'button');
// Click the sort button
await userEvent.click(hoursHeaderCellSortButton);
// Assert sort expectations
/** DataGridSelectRowDataCell */
// Assert on row selection
const hannahSelectRowCellCheckbox = within(hannaRow).getByRole('checkbox', {
name: 'Select row for Hannah Ardent',
})
// Click the row selection checkbox
await userEvent.click(hannahSelectRowCellCheckbox);
// Assert row selection expectations
expect(hannahSelectRowCellCheckbox).toBeChecked();
});