table

Table

Data table with columns and rendering.

Table Examples

Copy-ready examples with live previews.

Basic

Define columns and rows. Use `renderCell` for formatting.

Live preview

Default (neutral)
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151,420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919

Basic

import Table from "@/packages/ui/src/_components/table/Table";
import type { ITableColumn } from "@/packages/ui/src/_components/table/Table.type";


const rows: UserRow[] = [
  { id: "u_1", name: "Luis", email: "luis@cssnt.dev", role: "Admin", status: "Active", createdAt: "2025-12-15", requests: 1420 },
  { id: "u_2", name: "Andrea", email: "andrea@cssnt.dev", role: "Editor", status: "Paused", createdAt: "2025-11-02", requests: 210 },
];

const columns = [
  { key: "name", header: "Name" },
  { key: "email", header: "Email", mono: true },
  { key: "role", header: "Role" },
  { key: "status", header: "Status" },
  { key: "createdAt", header: "Created", mono: true },
  { key: "requests", header: "Requests", align: "right", mono: true, width: 120 },
];

export default function Example() {
  return (
    <Table<UserRow>
      caption="Team"
      columns={columns}
      rows={rows}
      rowKey={(r) => r.id}
      renderCell={(row, col) => (col.key === "requests" ? row.requests.toLocaleString() : row[col.key])}
    />
  );
}

Striped + dense + column dividers

Quick styling flags for data-heavy views.

Live preview

Striped + dense + colDividers
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919

Dense table

import Table from "@/packages/ui/src/_components/table/Table";

export default function Example({ columns, rows }: any) {
  return (
    <Table
      caption="Striped + dense + colDividers"
      variant="primary"
      density="dense"
      striped
      colDividers
      columns={columns}
      rows={rows}
    />
  );
}

Sticky header

Wrap the table in a scroll container and enable `stickyHeader`.

Live preview

Sticky header (scroll)
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919

Sticky header

import Table from "@/packages/ui/src/_components/table/Table";

export default function Example({ columns, rows }: any) {
  return (
    <div style={{ maxHeight: 220, overflow: "auto" }}>
      <Table
        caption="Sticky header (scroll)"
        stickyHeader
        variant="secondary"
        columns={columns}
        rows={rows}
        rowKey={(r: any, i: number) => r.id + "-" + i}
      />
    </div>
  );
}

Tone / inverse

Use `variant` + `tone` for contrast rules in docs and dark surfaces.

Live preview

Inverse
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919
Pure white
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919
Pure black (inverse)
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919

Tones

import Table from "@/_components/table/Table";

export default function Example({ columns, rows }: any) {
  return (
    <>
      <Table caption="Inverse" variant="neutral-inverse" columns={columns} rows={rows} />
      <Table caption="Pure white" tone="pure-white" columns={columns} rows={rows} />
      <Table caption="Pure black" tone="pure-black" variant="neutral-inverse" columns={columns} rows={rows} />
    </>
  );
}

Empty state

When `rows` is empty, the table renders a single row with `emptyText`.

Live preview

Empty table
NameEmailRoleStatusCreatedRequests
No users yet

Empty state

import Table from "@/packages/ui/src/_components/table/Table";

export default function Example({ columns }: any) {
  return <Table caption="Empty table" columns={columns} rows={[]} emptyText="No users yet" />;
}

Row selection (controlled)

Use `isRowSelected` to apply the selected row class, and handle selection in your UI.

Live preview

Selectable rows (click)
NameEmailRoleStatusCreatedRequests
luis@cssnt.devAdminActive2025-12-151,420
andrea@cssnt.devEditorPaused2025-11-02210
carlos@cssnt.devViewerBlocked2025-10-1919

Selectable rows

"use client";

import { useState } from "react";
import Table from "@/packages/ui/src/_components/table/Table";

export default function Example({ columns, rows }: any) {
  const [selectedId, setSelectedId] = useState("u_2");

  return (
    <Table
      caption="Selectable rows (click)"
      variant="info"
      columns={columns}
      rows={rows}
      rowKey={(r: any) => r.id}
      isRowSelected={(r: any) => r.id === selectedId}
      renderCell={(row: any, col: any) => {
        if (col.key === "name") {
          return (
            <button type="button" style={{ all: "unset", cursor: "pointer" }} onClick={() => setSelectedId(row.id)}>
              {row.name}
            </button>
          );
        }
        return row[col.key];
      }}
    />
  );
}

Actions column

Add an actions column and render buttons or menus per row.

Live preview

Actions column
NameEmailRoleStatusCreatedRequestsActions
Luis Chavezluis@cssnt.devAdminActive2025-12-151,420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919

Actions

import Table from "@/packages/ui/src/_components/table/Table";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example({ columns, rows }: any) {
  return (
    <Table
      caption="Actions column"
      variant="success"
      columns={[...columns, { key: "id", header: "Actions", align: "right", width: 220 }]}
      rows={rows}
      rowKey={(r: any) => r.id}
      renderCell={(row: any, col: any) => {
        if (col.key !== "id") return row[col.key];

        return (
          <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
            <Button variant="transparent" variantRadio="sm" onClick={() => console.log("view", row.id)}>
              View
            </Button>
            <Button variant="primary" variantRadio="sm" onClick={() => console.log("edit", row.id)}>
              Edit
            </Button>
          </div>
        );
      }}
    />
  );
}

Custom cell rendering

Format values and render UI per cell.

Live preview

Custom cell rendering
NameEmailRoleStatusCreatedRequests
Luis Chavezluis@cssnt.devAdminActive2025-12-151,420
Andrea Mandrea@cssnt.devEditorPaused2025-11-02210
Carlos Rcarlos@cssnt.devViewerBlocked2025-10-1919

Custom cells

import Table from "@/packages/ui/src/_components/table/Table";

export default function Example({ columns, rows }: any) {
  return (
    <Table
      caption="Custom cell rendering"
      variant="tertiary"
      columns={columns}
      rows={rows}
      renderCell={(row: any, col: any) => {
        if (col.key === "email") return <a href={`mailto:${row.email}`}>{row.email}</a>;
        if (col.key === "requests") return Number(row.requests).toLocaleString();
        return row[col.key];
      }}
    />
  );
}

On this page

Table Props

Table — Props
PropTypeRequiredDefaultDescription
columnsArray<ITableColumn<T>>YesColumn definitions (header, key, alignment, etc.).
rowsArray<T>YesTable data (one object per row).
captionReact.ReactNodeNoOptional `<caption>` rendered inside the `<table>`.
variant"neutral" | "neutral-inverse" | "primary" | "secondary" | "tertiary" | "success" | "warning" | "danger" | "info"No"neutral"Semantic variant (mapped to modifier classes).
tone"default" | "pure-white" | "pure-black"No"default"Background/contrast tone modifier.
density"default" | "dense"No"default"Spacing density modifier.
stripedbooleanNofalseAdds striped row styling.
colDividersbooleanNofalseAdds vertical dividers between columns.
stickyHeaderbooleanNofalseMakes the header sticky inside the scroll container.
rowKey(row: T, index: number) => string | numberNorow.id ?? indexComputes the React `key` for each row.
isRowSelected(row: T, index: number) => booleanNo() => falseMarks a row as selected (adds `is-selected` class).
renderCell(row: T, col: ITableColumn<T>, rowIndex: number) => React.ReactNodeNorow[col.key]Custom cell renderer.
emptyTextReact.ReactNodeNo"No data"Content shown when `rows.length === 0`.
classNamestringNoExtra class name for the root container.
customStylesReact.CSSPropertiesNoInline styles for the root container.
ITableColumn.keykeyof T & stringYesProperty key used to read values from the row object.
ITableColumn.headerReact.ReactNodeYesHeader content for the column.
ITableColumn.align"left" | "center" | "right"No"left"Horizontal alignment for header + cells.
ITableColumn.monobooleanNofalseApplies a monospace style class to header + cells for that column.
ITableColumn.widthnumber | stringNoInline width for header/cells (e.g. 160, "12rem", "20%").