Shim

Table

Display tabular data in a grid with selection and sorting.

Documentation
React Aria
Pattern
W3C ARIA
Source
GitHub
Issues
Report
Composes
Checkbox
Install
pnpm dlx @kkga/shim add table
GamesFile folder
Program FilesFile folder
bootmgrSystem file
DocumentsFile folder
DownloadsFile folder
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/shim-ui/table";

export default () => (
  <Table aria-label="Files" className="table-fixed">
    <TableHeader>
      <Column isRowHeader>Name</Column>
      <Column>Type</Column>
    </TableHeader>
    <TableBody>
      <Row>
        <Cell>Games</Cell>
        <Cell>File folder</Cell>
      </Row>
      <Row>
        <Cell>Program Files</Cell>
        <Cell>File folder</Cell>
      </Row>
      <Row>
        <Cell>bootmgr</Cell>
        <Cell>System file</Cell>
      </Row>
      <Row>
        <Cell>Documents</Cell>
        <Cell>File folder</Cell>
      </Row>
      <Row>
        <Cell>Downloads</Cell>
        <Cell>File folder</Cell>
      </Row>
    </TableBody>
  </Table>
);

Install

Use the CLI or copy the source code manually.

pnpm dlx @kkga/shim add table

Content

Provide columns via the columns prop on <TableHeader> and data with items on <TableBody> to render dynamic tables.

Unicorn
Dragon
Mermaid
"use client";
import { CheckIcon, XIcon } from "@phosphor-icons/react";
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/shim-ui/table";

let columns = [
  { id: "creature", name: "Creature", isRowHeader: true },
  { id: "canFly", name: "Can fly" },
  { id: "hasHorn", name: "Has horn" },
  { id: "scary", name: "Scary" },
];

let rows = [
  {
    id: 1,
    creature: "Unicorn",
    canFly: false,
    hasHorn: true,
    scary: false,
  },
  {
    id: 2,
    creature: "Dragon",
    canFly: true,
    hasHorn: false,
    scary: true,
  },
  {
    id: 3,
    creature: "Mermaid",
    canFly: false,
    hasHorn: false,
    scary: false,
  },
];

const Check = () => (
  <CheckIcon className="text-success-text" size={16} weight="bold" />
);
const X = () => <XIcon className="text-neutral-text-subtle" size={16} />;

export default () => (
  <Table aria-label="Mythical Creatures" className="table-fixed">
    <TableHeader columns={columns}>
      {(column) => (
        <Column isRowHeader={column.isRowHeader}>{column.name}</Column>
      )}
    </TableHeader>
    <TableBody items={rows}>
      {({ creature, canFly, hasHorn, scary }) => (
        <Row>
          <Cell>{creature}</Cell>
          <Cell className="align-middle">{canFly ? <Check /> : <X />}</Cell>
          <Cell className="align-middle">{hasHorn ? <Check /> : <X />}</Cell>
          <Cell className="align-middle">{scary ? <Check /> : <X />}</Cell>
        </Row>
      )}
    </TableBody>
  </Table>
);

Sorting

Enable column sorting by providing a sortDescriptor with a column key and direction value. Direction accepts "ascending" or "descending" and the column matches the column key.

Alice JohnsonMathematics3.9
Bob BrownPhysics3.7
Jane SmithMechanical Engineering3.8
John DoeComputer Science3.5
"use client";
import { useMemo, useState } from "react";
import type { SortDescriptor } from "react-aria-components";
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/shim-ui/table";

interface RowData {
  id: number;
  name: string;
  major: string;
  gpa: number;
}

let rows: RowData[] = [
  { id: 1, name: "John Doe", major: "Computer Science", gpa: 3.5 },
  { id: 2, name: "Jane Smith", major: "Mechanical Engineering", gpa: 3.8 },
  { id: 3, name: "Alice Johnson", major: "Mathematics", gpa: 3.9 },
  { id: 4, name: "Bob Brown", major: "Physics", gpa: 3.7 },
];

export default () => {
  let [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
    column: "name",
    direction: "ascending",
  });

  let items = useMemo(() => {
    let sorted = [...rows].sort((a, b) => {
      let column = sortDescriptor.column as keyof RowData;
      if (typeof a[column] === "number" && typeof b[column] === "number") {
        return a[column] - b[column];
      }
      if (typeof a[column] === "string" && typeof b[column] === "string") {
        return a[column].localeCompare(b[column]);
      }
      return 0;
    });
    if (sortDescriptor.direction === "descending") {
      sorted.reverse();
    }
    return sorted;
  }, [sortDescriptor]);

  return (
    <Table
      aria-label="Students"
      onSortChange={setSortDescriptor}
      sortDescriptor={sortDescriptor}
    >
      <TableHeader>
        <Column allowsSorting id="name" isRowHeader>
          Name
        </Column>
        <Column allowsSorting id="major">
          Major
        </Column>
        <Column allowsSorting id="gpa">
          GPA
        </Column>
      </TableHeader>
      <TableBody items={items}>
        {({ name, major, gpa }) => (
          <Row>
            <Cell>{name}</Cell>
            <Cell>{major}</Cell>
            <Cell className="tabular-nums">{gpa}</Cell>
          </Row>
        )}
      </TableBody>
    </Table>
  );
};

Selection

Tables support single and multiple row selection via the selectionMode prop. Set it to "single" or "multiple" to enable selection. Use selectedKeys and onSelectionChange to control the selected rows.

GamesFile folder6/7/2020
Program FilesFile folder4/7/2021
bootmgrSystem file11/20/2010
"use client";
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/shim-ui/table";

export default () => (
  <Table aria-label="Files" selectionMode="multiple">
    <TableHeader>
      <Column isRowHeader>Name</Column>
      <Column>Type</Column>
      <Column>Date Modified</Column>
    </TableHeader>
    <TableBody>
      <Row>
        <Cell>Games</Cell>
        <Cell>File folder</Cell>
        <Cell>6/7/2020</Cell>
      </Row>
      <Row>
        <Cell>Program Files</Cell>
        <Cell>File folder</Cell>
        <Cell>4/7/2021</Cell>
      </Row>
      <Row>
        <Cell>bootmgr</Cell>
        <Cell>System file</Cell>
        <Cell>11/20/2010</Cell>
      </Row>
    </TableBody>
  </Table>
);

Variant

Tables support three visual variants: "surface", "ghost", and "zebra". The "surface" variant has a solid background and borders, while the "ghost" variant has a transparent background and no borders. The "zebra" variant alternates row background colors for better readability.

John DoeAdministrator
Jane SmithEditor
Sam JohnsonSubscriber
John DoeAdministrator
Jane SmithEditor
Sam JohnsonSubscriber
John DoeAdministrator
Jane SmithEditor
Sam JohnsonSubscriber
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/shim-ui/table";

export default () =>
  (["zebra", "ghost", "surface"] as const).map((variant) => (
    <Table
      aria-label="People"
      className="table-fixed"
      key={variant}
      variant={variant}
    >
      <TableHeader>
        <Column className="w-1/3" isRowHeader>
          Name
        </Column>
        <Column>Role</Column>
      </TableHeader>
      <TableBody>
        <Row>
          <Cell>John Doe</Cell>
          <Cell>Administrator</Cell>
        </Row>
        <Row>
          <Cell>Jane Smith</Cell>
          <Cell>Editor</Cell>
        </Row>
        <Row>
          <Cell>Sam Johnson</Cell>
          <Cell>Subscriber</Cell>
        </Row>
      </TableBody>
    </Table>
  ));

Size

Tables support four sizes that adjust the padding and typography of the table elements. The sizes are numbered from 1 to 4, with 1 being the smallest and 4 the largest.

John DoeAdministrator
Jane SmithEditor
John DoeAdministrator
Jane SmithEditor
John DoeAdministrator
Jane SmithEditor
John DoeAdministrator
Jane SmithEditor
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/shim-ui/table";

export default () =>
  ([1, 2, 3, 4] as const).map((size) => (
    <Table
      aria-label="People"
      className="table-fixed"
      key={size}
      selectionMode="multiple"
      size={size}
    >
      <TableHeader>
        <Column className="w-1/3" isRowHeader>
          Name
        </Column>
        <Column>Role</Column>
      </TableHeader>
      <TableBody>
        <Row>
          <Cell>John Doe</Cell>
          <Cell>Administrator</Cell>
        </Row>
        <Row>
          <Cell>Jane Smith</Cell>
          <Cell>Editor</Cell>
        </Row>
      </TableBody>
    </Table>
  ));