Table
Tables display information in an easily scannable format, helping users identify patterns and insights.This component utilizes the TanStack Table API for its core functionalities.
Available from eds-core/1.5.0
Updated in eds-core/1.15.0
. See changelog for more details.
Quick Start
- Installation
npm install @adaptavant/eds-core
- Import
import { Table } from '@adaptavant/eds-core';
Basic usage
To ensure that the columns and data are not recreated on every render, you should memoize them using React.useMemo
. This improves performance by preventing unnecessary re-renders.
When dealing with dynamic data that can change over time, useState
should be used to manage the data state. This allows the table to update correctly when the data changes.
1 | Torie | Rustman |
2 | Kordula | Gecks |
3 | Vikki | Simoens |
4 | Burnaby | Cowern |
5 | Teddie | Traice |
const basicColumnDef = [
{
header: "Id",
accessorKey: "id",
},
{
header: "First Name",
accessorKey: "first_name",
},
{
header: "Last Name",
accessorKey: "last_name",
},
];
const userList = [
{
id: 1,
first_name: "Torie",
last_name: "Rustman",
},
{
id: 2,
first_name: "Kordula",
last_name: "Gecks",
},
{
id: 3,
first_name: "Vikki",
last_name: "Simoens",
},
{
id: 4,
first_name: "Burnaby",
last_name: "Cowern",
},
{
id: 5,
first_name: "Teddie",
last_name: "Traice",
},
];
const columns = React.useMemo(() => basicColumnDef, []);
const data = React.useMemo(() => userList, []);
return <Table columns={columns} data={data} />;
Data examples
Supports a variety of data types, including arrays of any type and objects. Column definition accessorKey differs for each type Refer below example,
1) Array of Objects
When your data is an array of objects, you can use accessorKey
to specify the key for each column.
John | Doe | 28 |
Jane | Smith | 34 |
const userList = [
{ firstName: "John", lastName: "Doe", age: 28 },
{ firstName: "Jane", lastName: "Smith", age: 34 },
];
const columnsDef = [
{ header: "First Name", accessorKey: "firstName" },
{ header: "Last Name", accessorKey: "lastName" },
{ header: "Age", accessorKey: "age" },
];
const columns = React.useMemo(() => columnsDef, []);
const data = React.useMemo(() => userList, []);
return <Table columns={columns} data={data} />;
2) Array of Arrays
When your data is an array of arrays, each inner array typically represents a row in the table. You have two approaches to define columns when dealing with an array of arrays:
Using accessorFn
to Access Index
Specify each column's header and use accessorFn
to access the corresponding index in each row.
const data = [
["John", "Doe", 28],
["Jane", "Smith", 34],
];
const columns = [
{ header: "First Name", accessorFn: (row) => row[0] },
{ header: "Last Name", accessorFn: (row) => row[1] },
{ header: "Age", accessorFn: (row) => row[2] },
];
Using accessorKey
with Numeric Strings
Alternatively, you can use accessorKey
directly with numeric strings to specify the index.
const data = [
['John', 'Doe', 28],
['Jane', 'Smith', 34],
];
const columns = [
{
accessorKey: '0',
header: 'First Name',
},
{
accessorKey: '1',
header: 'Last Name',
},
{
accessorKey: '2',
header: 'Age',
},
];
Note: When using accessorKey
with numeric strings, ensure each string corresponds to the index of the data array correctly.
3) Nested Objects
When your data includes nested objects, there are two ways to access nested keys.
Using dot notation
Use accessorKey
with dot notation to directly access nested properties within your data objects. This method is simple and concise, allowing you to specify the exact path to the nested property as a string.
// Using dot notations
const data = [
{ name: { first: 'John', last: 'Doe' }, age: 28 },
{ name: { first: 'Jane', last: 'Smith' }, age: 34 },
];
const columns = [
{ header: 'First Name', accessorKey: 'name.first' },
{ header: 'Last Name', accessorKey: 'name.last' },
{ header: 'Age', accessorKey: 'age' },
];
Using accessorFn
Function
Use accessorFn
to define a function that accesses the nested properties within your data objects. This method offers more flexibility, allowing for complex data transformations or calculations if needed.
// Alternatively use accessorFn to access the row data
const data = [
{ name: { first: "John", last: "Doe" }, age: 28 },
{ name: { first: "Jane", last: "Smith" }, age: 34 },
];
const columns = [
{ header: "First Name", accessorKey: "name.first" }, // Accesses the 'first' property within the 'name' object
{ header: "Last Name", accessorKey: "name.last" }, // Accesses the 'last' property within the 'name' object
{ header: "Age", accessorKey: "age" }, // Accesses the 'age' property directly
];
Custom cell content
To display custom content in the table cells can be achieved by defining a custom function and using it within the cell definition. Refer to the TanStack Cell API for more information on cellContext
in the callback. Below example uses getValue() from the context object.
John | Doe | 123-456-7890 |
Jane | Smith | 987-654-3210 |
Sam | Brown | 456-789-1230 |
Alice | Johnson | 321-654-9870 |
Bob | Davis | 654-321-9876 |
// returns custom component
const cellWithToolTip = ({ getValue }) => {
return (
<Tooltip content={getValue()} placement="bottom-start">
{({ triggerProps }) => <span {...triggerProps}>{getValue()}</span>}
</Tooltip>
);
};
const userList = [
{
id: 1,
first_name: "John",
last_name: "Doe",
date_of_birth: "1990-01-01",
country: "USA",
phone: "123-456-7890",
},
{
id: 2,
first_name: "Jane",
last_name: "Smith",
date_of_birth: "1985-05-15",
country: "Canada",
phone: "987-654-3210",
},
{
id: 3,
first_name: "Sam",
last_name: "Brown",
date_of_birth: "1992-07-22",
country: "UK",
phone: "456-789-1230",
},
{
id: 4,
first_name: "Alice",
last_name: "Johnson",
date_of_birth: "1988-03-12",
country: "Australia",
phone: "321-654-9870",
},
{
id: 5,
first_name: "Bob",
last_name: "Davis",
date_of_birth: "1995-11-30",
country: "New Zealand",
phone: "654-321-9876",
},
];
const columnsDef = [
{ header: "First Name", accessorKey: "first_name" },
{ header: "Last Name", accessorKey: "last_name" },
{ header: "Phone", accessorKey: "phone", cell: cellWithToolTip },
];
const columns = React.useMemo(() => columnsDef, []);
const data = React.useMemo(() => userList, []);
return <Table columns={columns} data={data} />;
Header groups
Header groups allow you to create nested column headers in your table component, which helps organize and display related columns under a common header. To achieve header groups, define your columns with a nested structure where each group has its own header
and a columns
array for its child columns.
Name | Info | ||||
---|---|---|---|---|---|
1 | John | Doe | 1990-01-01 | USA | 123-456-7890 |
2 | Jane | Smith | 1985-05-15 | Canada | 987-654-3210 |
3 | Sam | Brown | 1992-07-22 | UK | 456-789-1230 |
4 | Alice | Johnson | 1988-03-12 | Australia | 321-654-9870 |
5 | Bob | Davis | 1995-11-30 | New Zealand | 654-321-9876 |
const userList = [
{
id: 1,
first_name: "John",
last_name: "Doe",
date_of_birth: "1990-01-01",
country: "USA",
phone: "123-456-7890",
},
{
id: 2,
first_name: "Jane",
last_name: "Smith",
date_of_birth: "1985-05-15",
country: "Canada",
phone: "987-654-3210",
},
{
id: 3,
first_name: "Sam",
last_name: "Brown",
date_of_birth: "1992-07-22",
country: "UK",
phone: "456-789-1230",
},
{
id: 4,
first_name: "Alice",
last_name: "Johnson",
date_of_birth: "1988-03-12",
country: "Australia",
phone: "321-654-9870",
},
{
id: 5,
first_name: "Bob",
last_name: "Davis",
date_of_birth: "1995-11-30",
country: "New Zealand",
phone: "654-321-9876",
},
];
// Column definitions with header groups
const columnsDef = [
{
header: "Id",
accessorKey: "id",
},
{
header: "Name",
columns: [
{
header: "First Name",
accessorKey: "first_name",
},
{
header: "Last Name",
accessorKey: "last_name",
},
],
},
{
header: "Info",
columns: [
{
header: "Date of Birth",
accessorKey: "date_of_birth",
},
{
header: "Country",
accessorKey: "country",
},
{
header: "Phone",
accessorKey: "phone",
},
],
},
];
const columns = React.useMemo(() => columnsDef, []);
const data = React.useMemo(() => userList, []);
return <Table columns={columns} data={data} />;
Pagination
To enable and configure pagination, utilize the pagination
prop when integrating the table component into your application. This prop allows you to specify pagination setting.
Notes: Currently, we support only client-side pagination.
const pagination = { pageSize: 10 };
const [data, setData] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const basicColumnDef = [
{
header: "FirstName",
accessorKey: "name.first",
},
{
header: "LastName",
accessorKey: "name.last",
},
{
header: "Age",
accessorKey: "dob.age",
},
{
header: "Phone",
accessorKey: "phone",
},
{
header: "Country",
accessorKey: "location.country",
},
];
React.useEffect(() => {
fetch("https://randomuser.me/api/?results=50")
.then((response) => response.json())
.then((data) => {
setData(data.results);
setLoading(false);
})
.catch((error) => {
console.log(error);
setLoading(false);
});
}, []);
const columns = React.useMemo(() => basicColumnDef, []);
if (loading) {
return (
<Box className="flex justify-center w-full p-4 h-[200px]">
<Loading size="48" />
</Box>
);
}
return <Table columns={columns} data={data} pagination={pagination} />;
Empty template
Use emptyTemplate
prop In cases where your table encounters empty datasets. The empty template functionality enhances user experience by displaying custom content or messages. This ensures users receive informative feedback instead of encountering a blank or confusing interface.
No data to display |
// Define custom fallBackTemplate
const FallBackTemplate = () => {
return (
<Box className="flex justify-center text-center p-4 h-[200px]">
<Box className="flex flex-col items-center justify-center">
<InformationIcon size="16" />
<p className="text-primary">No data to display</p>
</Box>
</Box>
);
};
//Empty data
const userList = [];
const columnsDef = [
{ header: "First Name", accessorKey: "firstName" },
{ header: "Last Name", accessorKey: "lastName" },
{ header: "Age", accessorKey: "age" },
];
const columns = React.useMemo(() => columnsDef, []);
const data = React.useMemo(() => userList, []);
return <Table columns={columns} data={data} emptyTemplate={<FallBackTemplate/>} />;
Column definition
Use columns
prop to specify how each column should behave, including how data should be accessed, displayed, and optionally, how it should be formatted or customized. By defining columns explicitly, you gain fine-grained control over the presentation and functionality of your tables.
Below is a detailed breakdown of the fields used in a column definition.
Field | Type | Definition | Example |
---|---|---|---|
|
| The display name of the column. This can be a string or a React node for custom rendering. |
|
|
| The key used to access the value from the row data. This should match the key in the data object. |
|
|
| A function to access the value from the row data. This can be used when the data requires transformation or computation. |
|
|
| A custom cell formatter. This can be a React component or a string for basic rendering. The function callback has a context object , which provides more access to the table instance. |
|
|
| An array of column definitions. This is used for nested columns to create column groups. |
|
Sorting
By default, sorting is enabled on the table. You can set the enableSorting
prop to false to disable sorting, and the sortDescFirst
prop to define the initial sorting order (ascending or descending).
Note: Refer Tanstack table sorting API guide for more context on table and column props.
First Name | |||
---|---|---|---|
1 | Torie | Rustman | male |
2 | Kordula | Gecks | female |
3 | Vikki | female | |
4 | Burnaby | Cowern | male |
5 | Teddie | Traice | female |
6 | Ben | William | male |
const basicColumnDef = [
{
header: "Id",
accessorKey: "id",
},
{
header: "First Name",
accessorKey: "first_name",
// disable sorting for firstName column
enableSorting: false,
},
{
header: "Last Name",
accessorKey: "last_name",
sortingFn: "basic", // Built-in sorting
sortUndefined: "last", // sorts undefined columns to the last
},
{
header: "Gender",
accessorKey: "gender",
// custom sorting function for gender
sortingFn: (rowA, rowB) => {
const genderOrder = ["male", "female"];
return (
genderOrder.indexOf(rowA.getValue("gender")) -
genderOrder.indexOf(rowB.getValue("gender"))
);
},
},
];
const userList = [
{
id: 1,
first_name: "Torie",
last_name: "Rustman",
gender: "male",
},
{
id: 2,
first_name: "Kordula",
last_name: "Gecks",
gender: "female",
},
{
id: 3,
first_name: "Vikki",
last_name: undefined,
gender: "female",
},
{
id: 4,
first_name: "Burnaby",
last_name: "Cowern",
gender: "male",
},
{
id: 5,
first_name: "Teddie",
last_name: "Traice",
gender: "female",
},
{
id: 6,
first_name: "Ben",
last_name: "William",
gender: "male",
},
];
const columns = React.useMemo(() => basicColumnDef, []);
const data = React.useMemo(() => userList, []);
// By default first sortingDirection for string datatype is ascending , set sortDescFirst to true to change it to DESC for the entire table.
return <Table columns={columns} data={data} sortDescFirst={true} />;
Style API
Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.
Please refer to the Style API documentation for more insights.
Table parts
const pagination = { pageSize: 10 };
const [data, setData] = React.useState([]);
const basicColumnDef = [
{
header: 'FirstName',
accessorKey: 'name.first',
},
{
header: 'LastName',
accessorKey: 'name.last',
},
{
header: 'Age',
accessorKey: 'dob.age',
},
{
header: 'Phone',
accessorKey: 'phone',
},
{
header: 'Country',
accessorKey: 'location.country',
},
];
React.useEffect(() => {
fetch('https://randomuser.me/api/?results=50')
.then((response) => response.json())
.then((data) => {
setData(data.results);
})
.catch((error) => console.log(error));
}, []);
const columns = React.useMemo(() => basicColumnDef, []);
return (
<Table
classNames={{
baseContainer: 'border-4 border-input-critical-pressed',
tableContainer: 'max-h-96 overflow-auto',
table: 'bg-caution-secondary',
// Styles the table header
headerRow: 'sticky top-0 bg-gray-400 ',
headerCell: 'bg-palette-violet-background',
headerContentWrapper: 'p-1 pl-2 bg-palette-blue-background-active',
headerContent: 'font-stronger text-constant-white',
sortIconsWrapper: 'bg-accent-secondary border-2',
sortIcon: 'fill-positive',
// Styles the table body row
row: 'border-input-active',
cell: 'font-stronger text-caution-secondary',
// Styles the footer pagination wrapper
footerContainer: 'bg-positive-secondary',
}}
columns={columns}
data={data}
pagination={pagination}
/>
);
Stylable Parts | Description |
---|---|
baseContainer | Wrapper for the table component which holds table and pagination. |
tableContainer | Wrapper that holds the entire table, ensuring proper layout, scrolling, and responsiveness |
table | Component for displaying data in rows and columns with a consistent design and interactions |
header | Section that contains the header row with all header cells, defining column titles and interactions. |
headerRow | Contains all header cells, defining column titles, sorting, actions etc., |
headerCell | Container that holds header content and aligns with the table’s structure. |
headerContentWrapper | Container that holds the column header content, ensuring proper alignment, spacing, and interaction |
headerContent | Main content inside a column header, typically including text, icons, or actions |
sortIconsWrapper | Container that holds sorting icons. |
sortIcon | Visually indicate and enable sorting of column data. |
body | The main section that holds the data rows. It follows structured spacing, typography. |
row | Row represents a single record or data entry, structured across column. It typically includes text, icons etc., |
cell | Cell is a single unit of content within a row and column. |
footerContainer | An optional row at the bottom that provides summary information, pagination controls, actions etc., |
Usage guidelines
Do
- Presenting tabular information: Present tabular information with a clear, repeating structure.
- Scanning structured data: Help the user scan structured data to make informed decisions.
- Paginating resources: If dealing with larger datasets, consider implementing pagination to display all of the user’s resources effectively.
Don’t
- Simple tables: Do not use table to present simple information that isn't naturally tabular.
- Emphasizing Fewer Sections: Do not use table when you have only a few sections and you want to emphasize each section more.
Best practices
Do
Introduce a simple hierarchy that enables users to find specific data faster.
Don’t
Avoid introducing a complex, difficult-to-scan hierarchy.
Do
Keep table headings short and precise.
Don’t
Avoid long table headings that include unnecessary details.
Do
Ensure there’s a clear link between the column heading and associated data.
Don’t
Avoid displaying data that’s unrelated to the column heading.
Do
Where there are gaps in data, display ‘N/A’ (Not applicable) as the value.
Don’t
Avoid using an en dash (–) or blank cell to represent gaps in data.
Do
The height of components inside cells must match the slot height of 24 px.
Don’t
Avoid using component variants that exceed the slot height of 24 px. These variants increase individual row height in tables.
Do
Display an associated CTA above the upper-right corner of the table. Use the less prominent button variant (secondary).
Don’t
Avoid using the prominent button variant (primary) and displaying a CTA in other areas of the screen.
Do
An empty state message should include a concise description of why data isn’t available. Provide a solution if possible.
Don’t
Avoid less descriptive messages that don’t offer the user unique information or a solution.
Do
Base column width on the average length of associated data.
Don’t
Avoid setting a column width that creates inconsistent spacing between data in one column and the next.
Do
If the data in a cell exceeds the column width, truncate the data and use a tooltip to show it in full.
Don’t
Avoid wrapping data to multiple lines and increasing individual row height.