# Data Table

Data Tables are used to organize and display tabular data to users while
providing a multitude of features that allows the user to interact with the data
been displayed on the table.

The standard DataTable component is a fast, consistent way to display structured
data with built-in layout, styling, and accessibility. It supports sorting,
pagination, and filtering either on the client or server.

For more advanced use cases, you can now use atomic DataTable components (e.g.,
`Row`, `Cell`, `HeaderCell`) in combination with other Atlantis components to
build fully custom table layouts. This enables greater flexibility in the types
of content you can render in rows and cells like icons, inline labels, action
buttons, or multi-line content.

If you're using a table library (such as
[TanStack Table](https://tanstack.com/table/latest)), you can combine its logic
with our atomic components to fully control the behavior and layout. This
enables sorting, filtering, pagination, and row or bulk actions. The atomic
components provide layout and styling only — they do not prescribe any data
logic or behavior.

## Design & usage guidelines

The DataTable component is a great solution if you are looking to display data
in a tabular way while giving your user the ability to sort, paginate or filter
that data, this can be implemented on the server side or on the client side
depending on your needs.

A best practice in DataTable presentation is to ensure that all columns with
numerical data (and their headers) round to the same decimal point, and are
right-aligned. This makes it much easier for the reader to quickly parse large
distinctions in dollar amounts, inventory counts, and other key business data.

**Note**: The atomic DataTable components are the path forward and should be
considered prior to using the [DataList](../DataList/DataList.md) and
[Table](../Table/Table.md) components.

## Responsiveness

The standard DataTable component has the option to handle responsive design,
these options will allow us to fix the header or the first left column depending
on your needs, this will allow the user a more efficient way to visualize the
data.

When using atomic components, you can build your own responsive behavior. This
allows for greater flexibility, especially when working with table libraries
that control column visibility or mobile-friendly views. Atomic layouts let you
fully customize how data collapses or stacks at different breakpoints.


## Configuration

The DataTable component provides a quick and consistent solution for common
table needs, with built-in support for features like sorting and pagination.

For more advanced or customized experiences, such as dynamic filtering, bulk
selection, or fully tailored interactions, use the atomic DataTable components.
These building blocks give you full control over layout and behavior, while
preserving the visual consistency of the Atlantis design system. They pair well
with libraries like [TanStack Table](https://tanstack.com/table) to handle
complex data management.

To see examples of how the atomic DataTable components can be configured, see
[this section of storybook](/storybook/web/?path=/story/components-lists-and-tables-datatable-composable--basic).
These are just starting points, the components are fully composable and can
support a wide range of use cases.

### Basic Structure

The DataTable uses a compound component pattern with these atomic components:

```tsx
<DataTable.Container>
  <DataTable.Actions>{/* Filter controls, search, etc. */}</DataTable.Actions>

  <DataTable.Table>
    <DataTable.Header>
      <DataTable.HeaderCell>Name</DataTable.HeaderCell>
      <DataTable.HeaderCell>Email</DataTable.HeaderCell>
      <DataTable.HeaderCell>Role</DataTable.HeaderCell>
    </DataTable.Header>

    <DataTable.Body>
      <DataTable.Row>
        <DataTable.Cell>John Doe</DataTable.Cell>
        <DataTable.Cell>john@example.com</DataTable.Cell>
        <DataTable.Cell>Admin</DataTable.Cell>
      </DataTable.Row>
    </DataTable.Body>

    <DataTable.Footer>
      <DataTable.Row>
        <DataTable.Cell colSpan={2}>
          <Typography fontWeight="bold">Total</Typography>
        </DataTable.Cell>
        <DataTable.Cell>3 users</DataTable.Cell>
      </DataTable.Row>
    </DataTable.Footer>
  </DataTable.Table>

  <DataTable.Pagination>
    <DataTable.PaginationButton direction="previous" />
    <DataTable.PaginationButton direction="next" />
  </DataTable.Pagination>
</DataTable.Container>
```

## Component Customization

Atomic DataTable components are flexible and composable. Each component accepts
standard HTML attributes, including className, style, onClick, onMouseEnter, and
more.

### Column Widths

`DataTable.Table` supports an optional `layout` prop:

* **`auto`** (default): Columns size to their content. Use when you prefer
  content-driven sizing over set proportions.
* **`fixed`**: Column widths can be set explicitly on `DataTable.HeaderCell`.
  Columns without explicit widths share the remaining space equally.

Set widths on `DataTable.HeaderCell` via the `style` prop when using
`layout="fixed"`:

```tsx
<DataTable.Table layout="fixed">
  <DataTable.Header>
    <DataTable.HeaderCell style={{ width: "10%" }}>Status</DataTable.HeaderCell>
    <DataTable.HeaderCell style={{ width: "25%" }}>Name</DataTable.HeaderCell>
    {/* Takes remaining space */}
    <DataTable.HeaderCell>Description</DataTable.HeaderCell>
    <DataTable.HeaderCell style={{ width: "15%" }}>Amount</DataTable.HeaderCell>
  </DataTable.Header>
</DataTable.Table>
```

When using TanStack Table, you can also leverage its column sizing API and apply
widths dynamically:

```tsx
// In column definitions
const columns = [
  { accessorKey: "status", size: 10 }, // Treated as percentage
  { accessorKey: "name", size: 25 },
  { accessorKey: "description" }, // No size = remaining space
  { accessorKey: "amount", size: 15 },
];

// In header rendering
<DataTable.HeaderCell
  style={{
    width: header.column.columnDef.size && `${header.column.columnDef.size}%`,
  }}
>
  {flexRender(header.column.columnDef.header, header.getContext())}
</DataTable.HeaderCell>;
```

Rows and cells support fully custom content, including other components,
enabling rich, interactive table layouts. See an example
[here](/storybook/web/?path=/story/components-lists-and-tables-datatable-composable--advanced-filtering).

```tsx
<DataTable.Row>
  <DataTable.Cell>
    <div
      style={{
        display: "flex",
        alignItems: "center",
        gap: "var(--space-small)",
      }}
    >
      <Icon name="invoice" />
      <div>
        <Typography fontWeight="bold">Invoice #123</Typography>
        <Text variation="subdued">For Services rendered</Text>
      </div>
    </div>
  </DataTable.Cell>
  <DataTable.Cell>
    <StatusLabel status="warning" label="Late" />
  </DataTable.Cell>
  <DataTable.Cell>
    <div style={{ textAlign: "right" }}>$2400.00</div>
  </DataTable.Cell>
  <DataTable.RowActions>
    <Button icon="edit" label="Edit" />
    <Button icon="delete" label="Delete" />
  </DataTable.RowActions>
</DataTable.Row>
```

### Row Actions

Use `DataTable.RowActions` to render
[per-row controls](/storybook/web/?path=/story/components-lists-and-tables-datatable-composable--row-actions)
like buttons or menus. The content is fully custom; wire up events and state in
your app.

```tsx
<DataTable.Row>
  <DataTable.Cell>Invoice #123</DataTable.Cell>
  <DataTable.Cell>$2400.00</DataTable.Cell>
  <DataTable.RowActions>
    <Button icon="edit" label="Edit" onClick={() => onEdit(row)} />
    <Button icon="delete" label="Delete" onClick={() => onDelete(row)} />
  </DataTable.RowActions>
  {/* Other cells as needed */}
  <DataTable.Cell>...</DataTable.Cell>
  <DataTable.Cell>...</DataTable.Cell>
</DataTable.Row>
```

### Bulk Actions

[Bulk actions](/storybook/web/?path=/story/components-lists-and-tables-datatable-composable--bulk-selection)
are composed by adding a selection column and showing actions in the the
`DataTable.Actions` area when one or more rows are selected. State and behavior
are controlled by your app or a table library.

Selection column with checkboxes:

```tsx
<DataTable.Header>
  <DataTable.HeaderCell>
    <Checkbox
      ariaLabel="Select all"
      checked={allSelected}
      indeterminate={someSelected && !allSelected}
      onChange={toggleAll}
    />
  </DataTable.HeaderCell>
  <DataTable.HeaderCell>Name</DataTable.HeaderCell>
  <DataTable.HeaderCell>Email</DataTable.HeaderCell>
</DataTable.Header>

<DataTable.Body>
  {rows.map(row => (
    <DataTable.Row key={row.id}>
      <DataTable.Cell>
        <Checkbox
          ariaLabel={`Select ${row.name}`}
          checked={selectedRowIds.has(row.id)}
          onChange={() => toggleRow(row.id)}
        />
      </DataTable.Cell>
      <DataTable.Cell>{row.name}</DataTable.Cell>
      <DataTable.Cell>{row.email}</DataTable.Cell>
      <DataTable.RowActions>
        <Button label="Edit" onClick={() => onEdit(row)} />
        <Button label="Delete" onClick={() => onDelete(row)} />
      </DataTable.RowActions>
    </DataTable.Row>
  ))}
</DataTable.Body>
```

Show a bulk action bar when there are selected rows:

```tsx
{
  selectedCount > 0 && (
    <DataTable.Actions>
      <InlineLabel label={`${selectedCount} selected`} />
      <Button label="Archive" onClick={archiveSelected} />
      <Button label="Delete" onClick={deleteSelected} />
    </DataTable.Actions>
  );
}
```

### Sortable Headers

For
[sortable columns](/storybook/web/?path=/story/components-lists-and-tables-datatable-composable--sortable),
use DataTable.SortableHeader with the required props:

```tsx
<DataTable.Header>
  <DataTable.SortableHeader
    direction={
      currentSort === "desc"
        ? SortDirection.descending
        : currentSort === "asc"
        ? SortDirection.ascending
        : SortDirection.equilibrium
    }
    onSort={() => handleColumnSort("name")}
  >
    Name
  </DataTable.SortableHeader>
  <DataTable.HeaderCell>Email</DataTable.HeaderCell>
</DataTable.Header>
```

The `direction` prop accepts:

* `SortDirection.ascending` - ascending sort
* `SortDirection.descending` - descending sort
* `SortDirection.equilibrium` - no sort applied

When both `direction` and `onSort` are provided, the header becomes interactive
with a sort icon. When either is missing, it renders as a regular header cell.

Use the `align` prop to align the header content and sort icon. This is useful
for numerical columns, where both the values and the header should be
end-aligned:

```tsx
<DataTable.SortableHeader
  align="end"
  direction={currentSort}
  onSort={() => handleColumnSort("totalPrice")}
>
  Total price
</DataTable.SortableHeader>
```

The `direction` prop represents the current sort state of the column. Your
`onSort` callback should handle the state transitions (ascending → descending →
equilibrium) based on your application's logic.

### Footer

The `DataTable.Footer` component is used for column-aligned content like totals
or summaries. It must be placed inside `DataTable.Table`, **after**
`DataTable.Body`. The footer is a sibling to the body, not a child of it.

The footer uses `DataTable.Row` and `DataTable.Cell` components to align with
your table columns. Use `colSpan` on cells to span multiple columns.

You can include multiple rows in a footer, which is useful for showing subtotals
and grand totals:

```tsx
<DataTable.Table>
  <DataTable.Header>
    {/* Header cells */}
  </DataTable.Header>

  <DataTable.Body>
    {/* Table rows */}
  </DataTable.Body>

  <DataTable.Footer>
    {/* Subtotal row */}
    <DataTable.Row>
      <DataTable.Cell colSpan={4}>
        <Text>Subtotal</Text>
      </DataTable.Cell>
      <DataTable.Cell>
        <Text align="end">$2,250.00</Text>
      </DataTable.Cell>
    </DataTable.Row>
    {/* Grand total row */}
    <DataTable.Row>
      <DataTable.Cell colSpan={4}>
        <Typography fontWeight="bold">Total</Typography>
      </DataTable.Cell>
      <DataTable.Cell>
        <Typography fontWeight="bold" align="end">$3,750.00</Typography>
      </DataTable.Cell>
    </DataTable.Row>
  </DataTable.Footer>
</DataTable.Table>

<DataTable.Pagination>
  {/* Pagination goes outside the table */}
</DataTable.Pagination>
```

**Note**: Pagination should be placed outside the table, within the container.
See below.

### Pagination

For
[pagination functionality](/storybook/web/?path=/story/components-lists-and-tables-datatable-composable--pagination),
use `DataTable.Pagination` and `DataTable.PaginationButton`. Pagination should
be placed outside the table, within the container:

```tsx
<DataTable.Container>
  <DataTable.Table>{/* Header and Body */}</DataTable.Table>

  <DataTable.Pagination>
    <DataTable.PaginationButton
      direction="previous"
      disabled={!canGoPrevious}
      onClick={handlePreviousPage}
      ariaLabel={direction =>
        direction === "next" ? "Next page" : "Previous page"
      }
    />
    <DataTable.PaginationButton
      direction="next"
      disabled={!canGoNext}
      onClick={handleNextPage}
      ariaLabel={direction =>
        direction === "next" ? "Next page" : "Previous page"
      }
    />
  </DataTable.Pagination>
</DataTable.Container>
```

**Important**: Pagination does not go inside the footer. The footer is for
column-aligned content, like totals. Pagination goes outside the table, but
within the container, as it is the navigation controls for the table data:

```tsx
<DataTable.Container>
  <DataTable.Table>
    {/* Header and Body */}
    <DataTable.Footer>
      <DataTable.Row>
        <DataTable.Cell colSpan={4}>
          <Typography fontWeight="bold">Total</Typography>
        </DataTable.Cell>
        <DataTable.Cell>$3,750.00</DataTable.Cell>
      </DataTable.Row>
    </DataTable.Footer>
  </DataTable.Table>

  {/* Pagination is outside the table, not inside the footer */}
  <DataTable.Pagination>{/* Pagination buttons */}</DataTable.Pagination>
</DataTable.Container>
```

**Note**: The `ariaLabel` prop is required and should be a function that takes a
direction parameter and returns translated strings for accessibility. Consider
using your application's translation system:

```tsx
ariaLabel={(direction) =>
  direction === "next" ? t("Next page") : t("Previous page")
}
```

### Setting Up Your Own Provider

Atomic DataTable components are fully functional on their own, no provider is
required. You can render rows, cells, headers, and actions manually, using
static data or your own logic.

However, if you're integrating with a table library like TanStack Table, you may
choose to create a context-based provider to pass the table instance down to the
child components. This allows you to centralize state management, such as
sorting, pagination, and row models, while keeping your table layout clean and
declarative.

This pattern is entirely optional, but it can help streamline more complex or
dynamic table setups.

If you're using TanStack Table, here is a quick example of a starter provider
you can use to share the table instance with your components:

```tsx
import { createContext, useContext } from "react";
import type { Table } from "@tanstack/react-table";

const DataTableContext = createContext<Table<any> | null>(null);

const useDataTable = () => {
  const table = useContext(DataTableContext);
  return table as Table<any>;
};

const DataTableProvider = ({
  children,
  table,
}: {
  readonly children: React.ReactNode;
  readonly table: Table<any>;
}) => {
  return (
    <DataTableContext.Provider value={table}>
      {children}
    </DataTableContext.Provider>
  );
};
```

To use the provider, first create a TanStack table instance and then wrap your
components:

```tsx
import { useReactTable, getCoreRowModel } from "@tanstack/react-table";

const table = useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
});

<DataTableProvider table={table}>
  <YourTableAtoms />
</DataTableProvider>;
```


## Props

### Web

#### DataTable

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `columns` | `ColumnDef<T>[]` | Yes | — | Should follow the @tanstack /react-table [ColumnDef](https://tanstack.com/table/v8/docs/guide/column-defs). [createCo... |
| `data` | `T[]` | Yes | — | The actual data that will be used for the table. Typescript should infer T from typeof data. |
| `emptyState` | `ReactNode | ReactNode[]` | No | — | The elements to display when the data table is empty |
| `height` | `number` | No | — | This will force the table to have the specified hight |
| `loading` | `boolean` | No | `false` | When true, shows the loading state of the DataTable |
| `onRowClick` | `(row: Row<T>) => void` | No | — | Enables row click action. The provided callback will be executed when the row is clicked. |
| `pagination` | `PaginationType` | No | — | Enables pagination, mostly follows: https://tanstack.com/table/v8/docs/api/features/pagination |
| `pinFirstColumn` | `boolean` | No | — | Pins the firstColumn when scrolling horizontally |
| `sorting` | `SortingType` | No | — | Enables sorting, mostly follows: https://tanstack.com/table/v8/docs/api/features/sorting#table-options |
| `stickyHeader` | `boolean` | No | — | When set to true makes the header sticky while scrolling vertically |

#### DataTable.PaginationButton

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ariaLabel` | `(direction: "next" | "previous") => string` | Yes | — | Function that returns the aria-label for the button. Required for accessibility. Should return translated strings bas... |
| `direction` | `"next" | "previous"` | Yes | — | The direction of the pagination button |
| `disabled` | `boolean` | No | `false` | Whether the pagination button is disabled |
| `onClick` | `() => void` | No | — | Callback function when the pagination button is clicked |

#### DataTable.RowActions

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |

#### DataTable.SortableHeader

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `align` | `"center" | "end" | "start"` | No | `"start"` | The horizontal alignment of the header content and sort icon. |
| `children` | `ReactNode` | No | — | The header content to display (text, icons, etc.) |
| `direction` | `SortDirection` | No | — | The current sort direction for this column. When undefined, the header renders as non-interactive. |
| `onSort` | `() => void` | No | — | Callback function triggered when the sortable header is clicked. When undefined, the header renders as non-interactive. |

#### DataTable.Table

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `layout` | `"auto" | "fixed"` | No | — | Controls the table layout. - `auto` (default): Columns size to content. - `fixed`: Column widths can be set explicitl... |
