Grapes homepage1.57.0
  • Guide
  • Tokens
  • Components
    Grapes on GithubGrapes on Figma
    Interaction
    • Button
    • IconButton
    • FloatingActionBar
    • Link
    Icons
    • Icon
    • HighlightIcon
    Form
    • AmountInput
    • Autocomplete
    • AutocompleteMultiple
    • AutocompletePlace
    • CheckboxBox
    • CheckboxField
    • DatePicker
    • FormField
    • Input
    • OptionGroup
    • PasswordInput
    • PhoneInput
    • RadioBox
    • RadioField
    • RadioGroup
    • Select
    • SwitchField
    • TextArea
    • TextInput
    • Upload
    • UploadButton
    Feedback
    • Badge
    • Banner
    • Callout
    • EmptyState
    • Modal
    • ModalSlideshow
    • DeprecatedModalSlideshow
    • PageModal
    • Skeleton
    • Tag
    • Toast
    • Tooltip
    Data display
    • Accordion
    • Avatar
    • Box
    • Calendar
    • CalendarRange
    • CollapsibleList
    • FileCard
    • InfoTip
    • ListBox
    • ListView
    • Panel
    • SidePanel
    • DeprecatedPreview
    • Table
    • Timeline
    • useDateFormatter
    Navigation
    • DropdownItem
    • DropdownMenu
    • Navigation
    • NavigationItem
    • Popover
    • Tabs

    Table

    The Table component is used to show information from a data set.

    View source code
    • Usage
    • Props

    Basic usage

    type 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}
        />
      );
    }
    

    Empty state

    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}
    />
    

    Row height

    The Table component has two variants: normal and compact. The compact mode makes the rows smaller.

    <Table rowHeight="compact" {...otherProps} />
    

    Footer

    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}
    />
    

    Disabled rows

    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}
    />
    

    Sort

    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}
    />
    

    Selectable

    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}
      />
    );
    

    Max height

    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} />
    

    Row variant

    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}
    />
    

    Cell variant

    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}
    />
    

    Group rows

    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>
        );
      }}
    />
    ); }
    
    

    Control in Cell

    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

    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:

    • Cell and Row variant
    • Sort
    • Empty state
    • Selectable
    Address51 rue de Londres
    Postal code75008
    CityParis
    <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 payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    There are no payables in this example
    Try looking in another example
    Account payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payable
    Supplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    401AIRBNBAirbnb2€
    401DELOITTEDeloitte324$
    401MAILCHIMPMailchimp13.29€
    401APPLEApple0€
    Account payableSupplier nameAmount
    401AIRBNB👌Airbnb2€
    401DELOITTE👌Deloitte324$
    401MAILCHIMP❌Mailchimp13.29€
    401APPLE👌Apple0€
    DateDescriptionAmount
    L

    Lewis Barker

    2 requests

    Jan 15, 2025Supplies for workshop€50.50
    Dec 15, 2024Team lunch for 11 persons€150.51
    N

    Nicolas Harvey

    1 requests

    Jan 3, 2024Laptop charger€90.50
    Rule NameConditionsByAction

    Software Suppliers

    Invoice Acmel

      Taxi & Car sharing

      Bolt Uber FreeNowc

        Office expenses

        Rental costs Officeb