ephemera

Table

Ledger paper with a red margin and double-ruled totals.

October stationery accounts
DateDescriptionAmount
Oct 02Stamps, first class12.40
Oct 05Envelopes (100)8.95
Oct 09Sealing wax, red6.50
Oct 11Fountain pen ink14.00
Total41.85

Install

$ npx shadcn@latest add https://ephemera.kerrshift.com/r/table.json

Or copy the source below into components/ui/table.tsx. Core components need the Paper Theme and Radix where noted.

Usage

example.tsx
import { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption } from "@/components/ui/table";

<Table>
  <TableCaption>October accounts</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Date</TableHead>
      <TableHead>Description</TableHead>
      <TableHead numeric>Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Oct 02</TableCell>
      <TableCell>Stamps</TableCell>
      <TableCell numeric>12.40</TableCell>
    </TableRow>
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell colSpan={2}>Total</TableCell>
      <TableCell numeric>12.40</TableCell>
    </TableRow>
  </TableFooter>
</Table>

Props

PropTypeDefaultDescription
TableHead / TableCell numericboolean—Right-aligned, typewritten tabular figures.
TableRow data-state"selected"—Highlight a row.
partsTable, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption—Same structure as shadcn/ui Table, so it works with TanStack Table.

Any other props are passed to the root element.

Fonts

Components read optional CSS variables and fall back to system fonts: --font-display, --font-receipt, --font-type, --font-hand and --font-stamp. This site uses Fraunces, IBM Plex Mono, Courier Prime, Caveat and Anton.

Source

components/ui/table.tsx
import * as React from "react";

const type = { fontFamily: "var(--font-type, 'Courier New', ui-monospace, monospace)" };

/** Ledger paper: ruled rows, a red margin after the first column, double rules for totals. */
export function Table({ className = "", ...props }: React.TableHTMLAttributes<HTMLTableElement>) {
  return (
    <div className="paper relative w-full overflow-x-auto rounded-[3px] border border-(--edge) bg-(--paper-raised) shadow-[0_8px_18px_-10px_rgb(0_0_0/.3)]">
      <table className={`w-full border-collapse text-[14px] text-(--ink) ${className}`} {...props} />
    </div>
  );
}

export function TableHeader({ className = "", ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
  return <thead className={`border-b-[3px] border-double border-(--margin) ${className}`} {...props} />;
}

export function TableBody({ className = "", ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
  return <tbody className={`[&>tr:last-child]:border-b-0 ${className}`} {...props} />;
}

export function TableFooter({ className = "", ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
  return (
    <tfoot
      className={`border-t-[3px] border-double border-(--ink) font-bold [&>tr]:border-b-0 ${className}`}
      {...props}
    />
  );
}

export function TableRow({ className = "", ...props }: React.HTMLAttributes<HTMLTableRowElement>) {
  return (
    <tr
      className={`border-b border-(--rule) transition-colors hover:bg-[color-mix(in_srgb,var(--pen)_5%,transparent)] data-[state=selected]:bg-[color-mix(in_srgb,var(--pen)_9%,transparent)] [&>*:first-child]:border-r [&>*:first-child]:border-(--margin) ${className}`}
      {...props}
    />
  );
}

export function TableHead({
  className = "",
  style,
  numeric,
  ...props
}: React.ThHTMLAttributes<HTMLTableCellElement> & { numeric?: boolean }) {
  return (
    <th
      className={`h-10 whitespace-nowrap px-4 align-middle text-[10.5px] font-bold uppercase tracking-[0.18em] text-(--ink-muted) ${numeric ? "text-right" : "text-left"} ${className}`}
      style={{ ...type, ...style }}
      {...props}
    />
  );
}

export function TableCell({
  className = "",
  style,
  numeric,
  ...props
}: React.TdHTMLAttributes<HTMLTableCellElement> & { numeric?: boolean }) {
  return (
    <td
      className={`h-11 px-4 align-middle ${numeric ? "whitespace-nowrap text-right tabular-nums" : ""} ${className}`}
      style={numeric ? { ...type, ...style } : style}
      {...props}
    />
  );
}

export function TableCaption({ className = "", style, ...props }: React.HTMLAttributes<HTMLTableCaptionElement>) {
  return (
    <caption
      className={`caption-bottom px-4 py-3 text-left text-[18px] text-(--ink-muted) ${className}`}
      style={{ fontFamily: "var(--font-hand, 'Bradley Hand', cursive)", ...style }}
      {...props}
    />
  );
}