Column Pinning

Column pinning keeps columns visible at the left or right edge of the table while scrolling horizontally. Enable "Split pinned columns into multiple tables" to render pinned columns as separate table elements, which is useful when you need independent scrolling regions.

Username
Role
Account Status
Last Visited At
Visit Count
{
"columnPinning": {}
}
import {useMemo, useState} from "react"

import {faker} from "@faker-js/faker"

import {
  type ColumnDef,
  type ColumnOrderState,
  getCoreRowModel,
  type VisibilityState,
} from "@qualcomm-ui/core/table"
import {Button} from "@qualcomm-ui/react/button"
import {Checkbox} from "@qualcomm-ui/react/checkbox"
import {ProgressRing} from "@qualcomm-ui/react/progress-ring"
import {flexRender, Table, useReactTable} from "@qualcomm-ui/react/table"
import {CodeHighlight} from "@qualcomm-ui/react-mdx/code-highlight"
import {clsx} from "@qualcomm-ui/utils/clsx"

import {PinnableHeader} from "./pinnable-header"
import {type User, useUserData} from "./use-data"

export function ColumnPinningDemo() {
  const {data = [], isFetching, refetch} = useUserData(15)

  const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
  const [columnOrder, setColumnOrder] = useState<ColumnOrderState>([])
  const [columnPinning, setColumnPinning] = useState({})

  const [isSplit, setIsSplit] = useState(false)
  const rerender = () => refetch()

  // always memoize your data and columns
  const userColumns: ColumnDef<User>[] = useMemo(
    () => [
      {
        accessorKey: "username",
        header: "Username",
        id: "username",
      },
      {
        accessorKey: "role",
        header: "Role",
        id: "role",
        size: 120,
      },
      {
        accessorKey: "accountStatus",
        header: "Account Status",
        id: "accountStatus",
      },
      {
        accessorKey: "lastVisitedAt",
        header: "Last Visited At",
        id: "lastVisitedAt",
        minSize: 205,
      },
      {
        accessorKey: "visitCount",
        header: "Visit Count",
        id: "visitCount",
      },
    ],
    [],
  )

  const table = useReactTable({
    columns: userColumns,
    data,
    getCoreRowModel: getCoreRowModel(),
    onColumnOrderChange: setColumnOrder,
    onColumnPinningChange: setColumnPinning,
    onColumnVisibilityChange: setColumnVisibility,
    state: {
      columnOrder,
      columnPinning,
      columnVisibility,
    },
  })

  const randomizeColumns = () => {
    table.setColumnOrder(
      faker.helpers.shuffle(table.getAllLeafColumns().map((d) => d.id)),
    )
  }

  return (
    <div className="flex w-full flex-col gap-4 p-2">
      <div className="mt-4 flex flex-wrap items-center gap-2">
        <Button onClick={() => void rerender()} variant="outline">
          Regenerate
        </Button>
        <Button onClick={randomizeColumns} variant="outline">
          Shuffle Columns
        </Button>
        {isFetching ? <ProgressRing size="xs" /> : null}
      </div>

      <div className="mt-4">
        <Checkbox
          checked={isSplit}
          label="Split pinned columns into multiple tables"
          onCheckedChange={setIsSplit}
          size="sm"
        />
      </div>

      <div className={clsx("flex", {"gap-4": isSplit})}>
        {isSplit && table.getIsSomeColumnsPinned("left") ? (
          <Table.Root showColumnDivider>
            <Table.Table>
              <Table.Header>
                {table.getLeftHeaderGroups().map((headerGroup) => (
                  <Table.Row key={headerGroup.id}>
                    {headerGroup.headers.map((header) => (
                      <PinnableHeader key={header.id} header={header} />
                    ))}
                  </Table.Row>
                ))}
              </Table.Header>
              <Table.Body>
                {table.getRowModel().rows.map((row) => {
                  return (
                    <Table.Row key={row.id}>
                      {row.getLeftVisibleCells().map((cell) => {
                        return (
                          <Table.Cell key={cell.id}>
                            {flexRender(
                              cell.column.columnDef.cell,
                              cell.getContext(),
                            )}
                          </Table.Cell>
                        )
                      })}
                    </Table.Row>
                  )
                })}
              </Table.Body>
            </Table.Table>
          </Table.Root>
        ) : null}

        {(isSplit && table.getCenterFlatHeaders().length) || !isSplit ? (
          <Table.Root showColumnDivider>
            <Table.Table>
              <Table.Header>
                {(isSplit
                  ? table.getCenterHeaderGroups()
                  : table.getHeaderGroups()
                ).map((headerGroup) => (
                  <Table.Row key={headerGroup.id}>
                    {headerGroup.headers.map((header) => (
                      <PinnableHeader key={header.id} header={header} />
                    ))}
                  </Table.Row>
                ))}
              </Table.Header>
              <Table.Body>
                {table.getRowModel().rows.map((row) => {
                  return (
                    <Table.Row key={row.id}>
                      {(isSplit
                        ? row.getCenterVisibleCells()
                        : row.getVisibleCells()
                      ).map((cell) => {
                        return (
                          <Table.Cell key={cell.id}>
                            {flexRender(
                              cell.column.columnDef.cell,
                              cell.getContext(),
                            )}
                          </Table.Cell>
                        )
                      })}
                    </Table.Row>
                  )
                })}
              </Table.Body>
            </Table.Table>
          </Table.Root>
        ) : null}

        {isSplit && table.getIsSomeColumnsPinned("right") ? (
          <Table.Root showColumnDivider>
            <Table.Table>
              <Table.Header>
                {table.getRightHeaderGroups().map((headerGroup) => (
                  <Table.Row key={headerGroup.id}>
                    {headerGroup.headers.map((header) => (
                      <PinnableHeader key={header.id} header={header} />
                    ))}
                  </Table.Row>
                ))}
              </Table.Header>
              <Table.Body>
                {table.getRowModel().rows.map((row) => {
                  return (
                    <Table.Row key={row.id}>
                      {row.getRightVisibleCells().map((cell) => {
                        return (
                          <Table.Cell key={cell.id}>
                            {flexRender(
                              cell.column.columnDef.cell,
                              cell.getContext(),
                            )}
                          </Table.Cell>
                        )
                      })}
                    </Table.Row>
                  )
                })}
              </Table.Body>
            </Table.Table>
          </Table.Root>
        ) : null}
      </div>

      <CodeHighlight
        className="w-fit"
        code={JSON.stringify(
          {columnPinning: table.getState().columnPinning},
          null,
          2,
        )}
        disableCopy
        language="json"
      />
    </div>
  )
}