The Table component is used to show information from a data set.
View source codetype DataRow = {
id: number;
accountPayable: string;
supplierName: string;
amount: string;
};
const data: DataRow[] = [
{
id: 0,
accountPayable: "401AIRBNB",
supplierName: "Airbnb",
amount: "2€",
},
{
id: 1,
accountPayable: "401DELOITTE",
supplierName: "Deloitte",
amount: "324$",
},
{
id: 2,
accountPayable: "401MAILCHIMP",
supplierName: "Mailchimp",
amount: "13.29€",
},
{
id: 3,
accountPayable: "401APPLE",
supplierName: "Apple",
amount: "0€",
},
];
const columns: TableColumn<DataRow>[] = [
{
id: "accountPayable",
header: "Account payable",
renderCell: ({ accountPayable }) => accountPayable,
},
{
id: "supplierName",
header: "Supplier name",
renderCell: ({ supplierName }) => supplierName,
},
{
id: "amount",
header: "Amount",
align: "right",
width: "20%",
renderCell(row) {
return <span className="font-medium">{row.amount}</span>;
},
},
];
function Demo() {
const [activeRow, setActiveRow] = useState<number>();
return (
<Table
data={data}
columns={columns}
getRowId={(row: DataRow) => String(row.id)}
onRowClick={(row) => setActiveRow(row.id)}
getIsRowActive={(row) => row.id === activeRow}
/>
);
}
If no data has been provided, the table's empty state will appear. The title is mandatory, and the subtitle is optional.
<Table
data={[]}
emptyState={{
title: "There are no payables in this example",
subtitle: "Try looking in another example",
}}
{...otherProps}
/>
The Table component has two variants: normal
and compact
. The compact mode makes the rows smaller.
<Table rowHeight="compact" {...otherProps} />
The footer
prop allows you to add a footer to the Table, to load more data for example.
<Table
footer={<Button variant="secondaryNeutral" text="Load more" />}
{...otherProps}
/>
The getIsRowDisabled
prop allows you to disable rows in the table. These options will be neither clickable nor selectable.
<Table
getIsRowDisabled={(row: DataRow) => row.id === 0 || row.id === 2}
{...otherProps}
/>
To make a table sortable, you will need to add the getSortValue
function to the corresponding columns. You can make only specific columns sortable.
<Table
columns={[
{
id: "accountPayable",
header: "Account payable",
renderCell: ({ accountPayable }) => accountPayable,
getSortValue: (item) => item.accountPayable,
},
{
id: "supplierName",
header: "Supplier name",
renderCell: ({ supplierName }) => supplierName,
},
{
id: "amount",
header: "Amount",
align: "right",
width: "20%",
renderCell(row) {
return <span className="font-medium">{row.amount}</span>;
},
getSortValue: (item) => parseInt(item.amount),
},
]}
{...otherProps}
/>
If you want to make a Table component selectable, you will need to specify the following props: onAllRowsSelectionChange
, onRowSelectionChange
, and selectedRowIds
. Rows that have been disabled with getIsRowDisabled
are not selectable. You can also specify directly if a row should be selectable with getIsRowCheckable, without having to disable the row.
const [selectedRowIds, setSelectedRowsIds] = useState<string[]>([]);
return (
<Table
selectedRowIds={selectedRowIds}
onRowSelectionChange={(_, id, checked) => {
setSelectedRowsIds((options) => {
if (checked) {
return options.concat(id);
}
return options.filter((optionId) => optionId !== id);
});
}}
onAllRowsSelectionChange={(_, ids, checked) => {
setSelectedRowsIds(checked ? ids : []);
}}
{...otherProps}
/>
);
The maxHeight
prop allows you to specify a maximum height for the table. You will then be able to scroll through the elements.
<Table maxHeight={160} {...otherProps} />
The getRowVariant
prop allows you to specify a variant (info
or warning
) for the different rows.
<Table
getRowVariant={({ supplierName }) => {
if (supplierName === "Airbnb") return "warning";
if (supplierName === "Mailchimp") return "info";
return undefined;
}}
{...otherProps}
/>
The getCellVariant
prop allows you to specify a variant for each cell when necessary. This function should be used in the columns
prop.
<Table
columns={[
{
id: "accountPayable",
header: "Account payable",
renderCell: ({ accountPayable }) => accountPayable,
getCellVariant: ({ accountPayable }) =>
accountPayable === "401AIRBNB" ? "success" : undefined,
},
{
id: "supplierName",
header: "Supplier name",
renderCell: (row) => {
const { supplierName } = row;
return (
<>
<span className="inline-block mr-8">
{getCellVariant(row) === "alert" ? "❌" : "👌"}
</span>
{supplierName}
</>
);
},
getCellVariant,
},
{
id: "amount",
header: "Amount",
align: "right",
width: "20%",
renderCell({ amount }) {
return <span className="font-medium">{amount}</span>;
},
},
]}
{...otherProps}
/>
The groupBy
prop allows you to group rows together. This function should return a string that will act as a key to group the rows together.
You can customize the header of each group by using the renderGroupedRowHeader
prop.
const Demo = () => {
const nf = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "EUR",
});
const dateFormatter = useDateFormatter();
return (
<Table
columns={[
{
id: "date",
renderCell: (cell) =>
dateFormatter(new Date(cell.date), DATE_FORMAT.MEDIUM),
header: "Date",
},
{
id: "description",
renderCell: (cell) => cell.description,
header: "Description",
},
{
id: "amount",
renderCell: (cell) => nf.format(cell.amount),
header: "Amount",
},
]}
data={[
{
id: 0,
avatar: "/jean.webp",
description: "Supplies for workshop",
amount: 50.5,
employeeName: "Lewis Barker",
employeeId: 1,
date: "2025-01-15",
},
{
id: 1,
avatar: "/jean.webp",
description: "Team lunch for 11 persons",
amount: 150.51,
employeeName: "Lewis Barker",
date: "2024-12-15",
employeeId: 1,
},
{
id: 2,
avatar: "/laurent.webp",
description: "Laptop charger",
amount: 90.5,
employeeName: "Nicolas Harvey",
employeeId: 2,
date: "2024-01-03",
},
]}
getRowId={(row) => String(row.id)}
groupBy={(row) => `${row.employeeId}`}
renderGroupedRowHeader={(_, aggregatedRows) => {
const { avatar, employeeName } = aggregatedRows[0];
const sum = aggregatedRows.reduce((acc, row) => acc + row.amount, 0);
return (
<div className="flex items-center gap-8 py-8">
<Avatar src={avatar} text={employeeName} />
<p className="grow body-m text-content-primary">{employeeName}</p>
<p>{`${aggregatedRows.length} requests`}</p>
<Button variant="secondaryNeutral" text={`Approve ${nf.format(sum)}`} />
</div>
);
}}
/>
); }
The renderCell
function should return a ReactNode
, which allows you to build any desired design. Here is an example that includes a dropdown, tags, and an Avatar component:
<Table
columns={[
{
id: "rule",
header: "Rule Name",
renderCell: ({ ruleIcon, ruleName }) => (
<div className="flex gap-8 items-center">
<HighlightIcon name={ruleIcon} size={32} variant="peach" />
<p>{ruleName}</p>
</div>
),
},
{
id: "conditions",
header: "Conditions",
renderCell: ({ conditions }) =>
conditions.split(",").map((condition) => {
return (
<Tag key={condition} variant="neutral">
{condition}
</Tag>
);
}),
},
{
id: "by",
width: "72px",
header: "By",
renderCell: ({ fromName, fromAvatar }) => {
return <Avatar text={fromName} src={fromAvatar} />;
},
},
{
id: "action",
width: "72px",
header: "Action",
renderCell: () => {
return (
<DropdownMenu
placement="bottom-end"
options={[
{ key: "edit", label: "Edit", iconName: "pen" as IconName },
{
key: "delete",
label: "Delete",
iconName: "trash" as IconName,
},
]}
renderButton={(getToggleButtonProps) => {
const { isDropdown, ...rest } = getToggleButtonProps();
return (
<IconButton
{...rest}
aria-label="action"
variant="tertiaryNeutral"
iconName="ellipsis-horizontal"
/>
);
}}
renderOption={(option) => (
<DropdownItem
label={option.label}
isSelected={false}
prefix={<Icon name={option.iconName} />}
/>
)}
onSelect={() => {}}
/>
);
},
},
]}
data={[
{
id: 4,
ruleIcon: "building-storefront" as IconName,
ruleName: "Software Suppliers",
conditions: "Invoice, Acme",
fromName: "laurent",
fromAvatar: "/laurent.webp",
},
{
id: 5,
ruleIcon: "car" as IconName,
ruleName: "Taxi & Car sharing",
conditions: "Bolt, Uber, FreeNow",
fromName: "chloe",
fromAvatar: "/chloe.webp",
},
{
id: 6,
ruleIcon: "buildings-office" as IconName,
ruleName: "Office expenses",
conditions: "Rental costs, Office",
fromName: "bertrand",
fromAvatar: "/bertrand.webp",
},
]}
getRowId={(row) => String(row.id)}
/>
Table.Mini is a variant of the Table with a compact view and less features. It should only be used to display row data.
Table.Mini doesn't support:
Address | 51 rue de Londres |
Postal code | 75008 |
City | Paris |
<Table.Mini
columns={[
{
id: "label",
renderCell: (cell) => cell.label,
},
{
id: "value",
renderCell: (cell) => cell.value,
},
]}
data={[
{
id: 0,
label: "Address",
value: "51 rue de Londres",
},
{
id: 1,
label: "Postal code",
value: "75008",
},
{
id: 2,
label: "City",
value: "Paris",
},
]}
getRowId={(row) => String(row.id)}
/>
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
There are no payables in this example Try looking in another example |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount | |
---|---|---|---|
401AIRBNB | Airbnb | 2€ | |
401DELOITTE | Deloitte | 324$ | |
401MAILCHIMP | Mailchimp | 13.29€ | |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | Airbnb | 2€ |
401DELOITTE | Deloitte | 324$ |
401MAILCHIMP | Mailchimp | 13.29€ |
401APPLE | Apple | 0€ |
Account payable | Supplier name | Amount |
---|---|---|
401AIRBNB | 👌Airbnb | 2€ |
401DELOITTE | 👌Deloitte | 324$ |
401MAILCHIMP | ❌Mailchimp | 13.29€ |
401APPLE | 👌Apple | 0€ |
Date | Description | Amount |
---|---|---|
L Lewis Barker 2 requests | ||
Jan 15, 2025 | Supplies for workshop | €50.50 |
Dec 15, 2024 | Team lunch for 11 persons | €150.51 |
N Nicolas Harvey 1 requests | ||
Jan 3, 2024 | Laptop charger | €90.50 |
Rule Name | Conditions | By | Action |
---|---|---|---|
Software Suppliers | Invoice Acme | l | |
Taxi & Car sharing | Bolt Uber FreeNow | c | |
Office expenses | Rental costs Office | b |