@department-of-veterans-affairs/clinical-design-system-datagrid
v1.0.1
Published
A clinician-facing datagrid built for the Department of Veterans Affairs.
Maintainers
Keywords
Readme
Data Grid
A Data Grid is an interactive, structured table that allows users to efficiently view, edit, filter, sort, and manipulate large datasets. Unlike static tables, a Data Grid provides dynamic functionality such as search, pagination, column customization, and actions to support data analysis and decision-making.
Installation instructions
yarn add @department-of-veterans-affairs/clinical-design-system-datagrid
npm install @department-of-veterans-affairs/clinical-design-system-datagrid
Features
- Quickly analyze data with various configurable tools. A toolbar can be added that includes a search bar, toggles to show/hide columns, and the ability to include additional custom components, like filters. Search, filtering, sorting, visibility, visual order, and pinning can also be applied per column.
- Compliant with Web Content Accessibility Guidelines (WCAG) Level AA. See below for more details.
- Allow users to easily interact with and update data with cell editors. Types include checkboxes, text inputs, and text areas.
- Customizable table properties, like pagination controls and column order, to enhance the experience for your particular use case. Implementors can also define custom sizes for columns in order to best present the data. Users are able to resize column widths while using the app.
When to Use a Data Grid
Use a Data Grid when your users need to:
- Organize and display structured clinical data in a tabular format.
- Sort, filter, and compare multiple data points across data (e.g., patients or timeframes).
- View large datasets efficiently.
- Quickly edit data or make decisions about their task.
When Not to Use a Data Grid
- For highly interactive workflows that require complex decision-making beyond data review.
- When a visual representation (e.g., charts, dashboards) better supports analysis or trends.
- If a simpler list view is more intuitive for scanning or task completion.
Key Differences: Data Grid vs. Static Table
Static Table
Interactivity Displays data in a fixed layout
Pagination Shows all rows at once, leading to potential performance issues
Customization Fixed structure with no user adjustments
Actions No built-in actions
Data Grid
Interactivity Allows sorting, filtering, and inline actions
Pagination Supports large datasets with pagination
Customization Columns can be resized, reordered, or hidden
Actions Enables in-cell editing and search through the whole dataset or to a specific column.
Best Practices
- Prioritize readability – Use clear headers and meaningful column labels. Place the most relevant data in the first columns and keep the content focused by removing unnecessary details. Avoid overcrowding the table with too many data points, such as rarely used information.
- Use filtering and sorting – Allow clinicians to refine data views efficiently.
- Highlight critical information – Use color-coded tags, bold text, or icons to make key data easily scannable using the right format.
Accessibility
- Use VACDS components within DataGrid, rather than components from other libraries or design systems. This ensures that the most accessible and visually consistent experience is provided to users.
- Use readable column header names, as they are referenced on other elements within DataGrid. For example, the accessible name for the table header column menu buttons matches the table header column name.
- Use unique IDs and column names for each column.
- DataGrid has fully fledged keyboard functionality. The focus order of each element within the component matches logical reading order. You do not need to add a tabindex to the component.
- The DataGrid toolbar adapts responsively to different screen sizes. On smaller screens, the filter controls will be nested within an accordion component for optimized space and usability.
Keyboard navigation
- For the most part, you can use the Tab key to navigate to each interactive element.
- Menu
- When focus is set on the Menu trigger, you can press Enter to open or close it.
- When the Menu is open, you can use Tab to navigate to each Menu item.
- When Tab is pressed on the last Menu item, the Menu will close and focus will move to the next interactive element.
- When Shift + Tab is pressed on the Menu trigger, the Menu will close and focus will move to the previous interactive element.
- Table header resizer
- When focus is set on the resizer element, you can use the left and right arrow keys to change the size of the column.
- Table body
- You can enter and leave the table body with the Tab key. Upon entering the table, focus will be set on the first table body cell by default.
- When a table body cell has focus, you can use the arrow keys (left, right, up, and down) to navigate to other cells.
- When navigating away from the table using the Tab key, returning to the table will restore focus to the last active cell. This allows keyboard users to seamlessly resume their work from where they left off.
- Editable table cells
- If the cell is editable, you can press Enter to enter “Editing Mode”.
- When in “Editing Mode”, you can choose to make, save, or cancel changes:
- Pressing Enter or Tab will save changes.
- Pressing Escape will cancel changes.
- Any of the three actions will exit “Editing Mode” and set focus back to the associated cell.
Technical Documentation
Features:
- use a simple array of objects to populate the table with data
- predefine default column settings such as sorting order, visibility, horizontal order, and pagination settings
- use TanStack's algorithms or customize your own to filter column data
- use the default pagination behavior or manually control it
- enable editable mode for table cells for text inputs, resizable textareas, and checkboxes
- AA Web Accessibility Standards already built-in
- customize column features by enabling pinning, search, sorting, visibility, and visual order
- add callbacks to add more fine grained control over pagination and cell editing
- access or update the current grid state
Quickstart Guide: The DataGrid component has 3 required props: data, columns, and tableCaption. The data prop consists of an array of type Array, where nestedType is an interface that defines the shape of the data. This array contains fields that will be used with the columns property to populate the table. The columns prop is an array of type Array<ColumnDef>, where nestedType is the same interface that was used to define the data prop. See below for the interface used for the stories below. The tableCaption prop is used to enhance accessibility for screen readers and can optionally be hidden. More information on defining data types can be found in the TanStack docs. This data will be turned into the rows of your table and used to help define the structure and capabilities of the columns. See the DefaultDataGridWithRequiredProps story for a code sample. To further customize a table, many more props can be added as seen in the props descriptions below.
Data
Sample Data Interface:
data: {
id: number;
name: string
};
nested: {
id: number | null;
name: string | null;
};
}Props
data*
GridData<T> - A required array that will be turned into the rows and used for the column definitions of the DataGrid table. Typically, each item in the array is an object with key/values. More info can be found in TanStack's docs. If it is desired for the data grid to re-render when the data has been updated, like when an edit has been performed, this object should be wrapped inside of a state object. The state object's setter can then be called to trigger a re-render from a callback, such as onCellEditSave.
GridData<any>
- -
initialState
InitialState - An optional object that contains the initial state for the DataGrid table for things such as sorting, columnVisibility, columnPinning, columnOrder, and pagination. If the pagination's pageIndex and pageSize are not specified, then it will fallback to a pageIndex of 0 and a pageSize of 10.
InitialState
-
columns*
Array<ColumnDef<T>>A required array of objects that will define the structure of the table columns. Each column must contain a header property and an accessorFn, which returns the value to be displayed based on the info contained in the data property. Different TanStack options can be found here: core properties, column sizing, column visibility, filtering, and sorting . Users can also provide a boolean value to the editable property to enable editing mode for all the cells in a column. With editable mode activated, users can modify the content in the column's cell. The cellEditor property can be set to define what kind of editor will be shown. Current types include 'text', 'date' (future release), 'checkbox', and 'textarea'. If a type is not provided for an editable cell, then a Text Input component will be used. Users must also include the onCellEditSave callback prop in order to fully implement the editable cell feature
ColumnDef<any>[]
-
columnResizeMode
ColumnResizeMode - An optional argument which decides when the column size state is updated. onChange updates when a user is actively dragging the resize handle and onEnd does it when a users releases the resize handle.
"onChange""onEnd"
-
filterFns
Record<string, TS.FilterFn<unknown>> - Optionally adds a globally available TanStack filter function or customize your own that can be referenced in a column's filterFn option by their key. This will function is used when using the column search function.
Record<string, FilterFn<unknown>>
- -
manualPagination
boolean - Enabling this optional boolean argument allows manual pagination of the rows before passing them to the table. This is a helpful option for server-side pagination and aggregation. More details can be found here.
boolean
-
paginationTotalPages
number - If it is known, this optional argument can be used to set the total pageCount when manually controlling pagination. -1 can be used if the number is unknown, which will hide the paginated page numbers. If not manually managing pagination, then this will be handled by the table's internal state
number
-
paginationCurrentPage
number - The current pagination page number when manually controlling pagination. If this field is not specified, then the table's internal state will manage this.
number
-
paginationOnClickNext
() => void - This callback will be executed when manually managing pagination, otherwise it will be handled by the table.
(() => void)
- -
paginationOnClickPrevious
() => void - This callback will be executed when manually managing pagination, otherwise it will be handled by the table.
(() => void)
- -
toolbarSlots
React.ReactNode[] - Adds an optional array of provided components in the toolbar, such as tools to filter the table's data, such as a Select component. If no slots are defined, then the toolbar will not render. It is highly recommended to use existing CDS components to ensure accessibility compliance and a more consistent visual appearance.
ToolBarSlot[]
- -
activeFilters
React.RefObject<Record<string, string>> - An optional ref object that stores the current filter values for the DataGrid. It is required when using the chipBar prop to display filter chips below the toolbar. This prop does not control the actual filtering behavior of the table. It only provides the values needed to render chips. Each key in the object should represent a column ID (e.g., "patient".gender"), and each value should be the selected filter value for that column (e.g., "female"). If the value is an empty string (""), no chip will be displayed for that filter.
RefObject<Record<string, string>>
- -
chipBar
boolean - Enables the chipbar that renders below the toolbar.
boolean
- -
onCellEditSave
(newValue, cellId, rowId) => void - This optional callback will be executed when a user has focused outside of or has pressed 'enter' or 'tab' on an editable cell. Cells can be marked as 'editable' in the columns property by marking the editable property as true. Typically, this callback should be used to update the state of the data argument after it's been persisted in a backend system in order to refresh the table with the latest data. Please note, in order for this to work, the data object must be wrapped inside of a state object with a setter. In addition to setting this, the editable field in the columns prop must be set to true and the cellEditor must be specified if another type is desired besides the default text input.
OnCellEditSave
- -
onCustomRowModelChange
(rowID) => void - This optional callback will be executed when a user selects a row and returns the selected row ID.
((row: any) => void)
- -
onCellClick
(selectedCellRowID, selectCellColumnID, selectedCell) => void - This optional callback will be executed when a user clicks on a cell. It returns is the row ID that the cell is in, the column ID that the cell is in, and the cell object that was clicked.
((selectedCellRow: any, selectCellColumn: any, selectedCell: any) => void)
- -
tableCaption*
string - A required prop that describes the table's purpose, which is helpful for users who utilize screen readers. This has a minimum of one character with no max.
string
- -
hideTableCaption
boolean - An optional prop that will allow the tableCaption prop to hide or show.
boolean
-
columnMode
string - Can either be set to pinning (default) or sticky. If set to sticky, the first column will be sticky and the pin buttons within each column menu will not render.
"sticky""pinning"
"pinning"
-
getRowIdFn
(row: T) => string - An optional prop that will set a custom row id. This prop must be a pure function and return a unique string. If this is not included, then each row will use the default id, which is just the row's index.
((row: any) => string)
- -
autoResetPageIndex
boolean - An optional prop that controls whether the page index automatically resets to 0 when certain state changes occur (like sorting, filtering, etc.). When set to true, the page index will automatically reset. When set to false, the page index will remain unchanged. When undefined (default), TanStack Table uses its default behavior. Important: This behavior is automatically disabled when manualPagination is set to true, but can be overridden by explicitly setting this prop. More details can be found here.
boolean
-
onColumnVisibilityChange
(newState: ColumnVisibilityState, oldState?: ColumnVisibilityState) => void - This callback will be executed when the column visibility changes. It returns the new column visibility state and optionally the old state.
((newState: VisibilityState, oldState?: VisibilityState) => void)
- -
onColumnPinningChange
(newState: ColumnPinningState, oldState?: ColumnPinningState) => void - This callback will be executed when the column pinning changes. It returns the new column pinning state and optionally the old state.
((newState: ColumnPinningState, oldState?: ColumnPinningState) => void)
- -
onColumnFiltersChange
(newState: ColumnFiltersState, oldState?: ColumnFiltersState) => void - This callback will be executed when the column filters change. It returns the new column filters state and optionally the old state.
((newState: ColumnFiltersState, oldState?: ColumnFiltersState) => void)
- -
onSortingChange
(newState: SortingState, oldState?: SortingState) => void - This callback will be executed when the sorting changes. It returns the new sorting state and optionally the old state.
((newState: SortingState, oldState?: SortingState) => void)
- -
onDataLengthChange
(newDataLength: number) => void - ⚠️ IN PROGRESS: STOPGAP SOLUTION This callback is executed whenever the number of filtered rows changes. It provides the current count of visible rows after filtering, searching, or data updates. This feature is subject to change when the pagination and number-of-pages work is implemented in the future.
((newDataLength: number) => void)
- -
fullWidth
boolean - If set to true, the table will take up the full width of its container.
boolean
- -
recordCountLabel
An optional prop that customizes the label used in the record count text. If not provided, it defaults to "rows". For example, setting this to patients will change the text from "1-10 of 25 rows" to "1-10 of 25 patients". Note: If manualPagination is set to true, paginationTotalPages and paginationCurrentPage must be defined in order for the record count text to render.
string
- -
ref
React.MutableRefObject<any> - An optional ref that will allow access to the table's API. This currently includes three functions: getDataGridState(), updateDataGridState(), and setPage() (optional). getDataGridState() is used to return the current state of the grid, such as column order and pinned columns. updateDataGridState() allows you to update multiple state properties at once by passing an object with a DataGridState type (all properties are optional). setPage() allows you to programmatically navigate to a specific page by passing a zero-based page index. Note: The setPage method is optional for backwards compatibility - existing consumers do not need to update their ref types. Examples: yourRefVariableName.current.getDataGridState(), yourRefVariableName.current.updateDataGridState({sorting: [{ id: 'patient.fullName', desc: true }]}), yourRefVariableName.current.setPage?.(2) (navigates to page 3).