npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

all-purpose-table

v1.0.11

Published

A production-grade, plug-and-play React Table component with TypeScript support

Readme

All Purpose Table

A production-grade, plug-and-play React table component with TypeScript support and zero dependencies.

Demos can be found at: https://apt-demos.vercel.app/

✨ Features

  • 🎯 Plug and Play - Install and use, no configuration needed
  • 📦 Zero Dependencies — No CSS frameworks or icon libraries required
  • 🟦 TypeScript Native — Full type safety and IntelliSense support
  • 🌑 Dark Modeprefers-color-scheme and manual class toggling (.dark, html.dark)
  • 📊 Feature Rich:
    • Sorting per column
    • Pagination with configurable rows per page
    • Column visibility toggle
    • Column resizing (drag) with optional localStorage persistence
    • Expandable rows
    • Custom cell renderers
    • Full custom row rendering
    • Row click handlers
    • Scrollable body with fixed header
    • Mobile responsive with optional auto-sizing on header click

📦 Installation

npm install all-purpose-table

⚡ Quick Start

import { Table } from "all-purpose-table";

function App() {
  const headers = [
    { accessor: "id",    label: "ID",    isSortable: true },
    { accessor: "name",  label: "Name",  isSortable: true },
    { accessor: "email", label: "Email" },
  ];

  const data = [
    { id: 1, name: "John Doe",   email: "[email protected]" },
    { id: 2, name: "Jane Smith", email: "[email protected]" },
  ];

  return <Table manualHeaders={headers} manualRowData={data} />;
}

📖 API Reference

Table Props

| Prop | Type | Default | Description | | ----------------------------- | -------------------- | --------------- | ------------------------------------------------------------------------------- | | manualHeaders | TableHeader[] | required | Array of column definitions | | manualRowData | object[] | required | Array of data objects | | height | string | "100%" | Table container height | | rowHeight | number | 40 | Height of each row in pixels | | shouldPaginate | boolean | true | Enable/disable pagination | | rowsPerPage | number | 60 | Number of rows per page | | initialSort | SortConfig | null | Initial sort configuration | | rowClassName | (row) => string | undefined | Custom row class names | | onRowClick | (row) => void | undefined | Row click handler | | minColWidth | number | 50 | Minimum column width in pixels | | columnWidthsStorageKey | string | undefined | localStorage key for persisting column widths | | rowsPerPageOptions | number[] | [20, 50, 100] | Options for rows per page selector | | onRowsPerPageChange | (value) => void | undefined | Callback when rows per page changes | | expandedRowId | string | null | ID of currently expanded row | | renderExpandedRow | (row) => ReactNode | undefined | Render function for expanded row content | | renderFullRow | (row) => ReactNode | undefined | Render function for custom full-width rows (can be used as header/dropdown row) | | mobileAutoSizeOnHeaderClick | boolean | false | Enable mobile auto-sizing on header click | | mobileBreakpoint | number | 768 | Mobile breakpoint in pixels |

TableHeader Interface

interface TableHeader {
  accessor: string; // Key to access data in row object
  label: string; // Display label for column
  isSortable?: boolean; // Enable sorting for this column
  width?: string | number; // Initial column width
  minWidth?: string | number; // Minimum column width
  cellRenderer?: (args: {
    // Custom cell renderer
    row: any;
    value: any;
  }) => React.ReactNode;
}

🔩 Advanced Usage

Custom Cell Renderers

const headers: TableHeader[] = [
  {
    accessor: "status",
    label: "Status",
    cellRenderer: ({ value }) => (
      <span className={`badge badge-${value.toLowerCase()}`}>{value}</span>
    ),
  },
  {
    accessor: "actions",
    label: "Actions",
    // Cells with accessor "actions" automatically stop row-click propagation
    cellRenderer: ({ row }) => (
      <button onClick={() => handleEdit(row.id)}>Edit</button>
    ),
  },
];

Expandable Rows

const [expandedRowId, setExpandedRowId] = useState<string | null>(null);

<Table
  manualHeaders={headers}
  manualRowData={data}
  expandedRowId={expandedRowId}
  onRowClick={(row) =>
    setExpandedRowId(expandedRowId === row.id ? null : row.id)
  }
  renderExpandedRow={(row) => (
    <div style={{ padding: 16 }}>
      <p>Details for {row.name}</p>
    </div>
  )}
/>

Full Custom Row

Set fullRow: true on any data object to replace that row entirely with renderFullRow:

const data = [
  { id: 1, name: "John" },
  { id: "divider", fullRow: true }, // uses renderFullRow
];

<Table
  manualHeaders={headers}
  manualRowData={data}
  renderFullRow={(row) => (
    <div style={{ padding: "8px 16px", fontWeight: "bold" }}>Section Header</div>
  )}
/>

Rows Per Page Selector

The footer (including the dropdown) only renders when onRowsPerPageChange is provided:

const [rowsPerPage, setRowsPerPage] = useState(20);

<Table
  manualHeaders={headers}
  manualRowData={data}
  rowsPerPage={rowsPerPage}
  rowsPerPageOptions={[10, 20, 50, 100]}
  onRowsPerPageChange={setRowsPerPage}
/>

Persistent Column Widths

<Table
  manualHeaders={headers}
  manualRowData={data}
  columnWidthsStorageKey="my-table-columns"
/>

Column Visibility Toggle

import { Table, ColumnVisibilityToggle } from "all-purpose-table";

function App() {
  const [visibleColumns, setVisibleColumns] = useState(["id", "name", "email"]);

  const availableColumns = [
    { key: "id", label: "ID" },
    { key: "name", label: "Name" },
    { key: "email", label: "Email" },
    { key: "phone", label: "Phone" },
  ];

  const filteredHeaders = headers.filter((h) =>
    visibleColumns.includes(h.accessor),
  );

  return (
    <div>
      <ColumnVisibilityToggle
        availableColumns={availableColumns}
        visibleColumns={visibleColumns}
        onColumnsChange={setVisibleColumns}
        storageKey="my-table-visible-columns"
      />
      <Table manualHeaders={filteredHeaders} manualRowData={data} />
    </div>
  );
}

🎨 Styling & Customization

The table comes with built-in styles. All CSS classes are prefixed with apt- to avoid conflicts.

Dark Mode

Dark mode is toggled by adding the dark class to any ancestor element (e.g. <html> or a wrapper <div>):

// Toggle dark mode
document.documentElement.classList.toggle("dark");

No extra configuration is needed — the component responds automatically.

CSS Variables

Override CSS variables to customise colours, spacing, and more:

:root {
  --apt-color-primary: #1f2937;
  --apt-color-border: #d1d5db;
  --apt-color-bg: white;
  --apt-color-bg-secondary: #f9fafb;
  --apt-color-accent: #9333ea;
  /* ... and more */
}

CSS Class Reference

| Class | Element | |---|---| | .apt-table-container | Outermost wrapper | | .apt-scroll-area | Scrollable region (contains both table parts) | | .apt-thead-wrapper | Fixed header wrapper | | .apt-tbody-wrapper | Scrollable body wrapper | | .apt-table | <table> element | | .apt-thead | <thead> | | .apt-tbody | <tbody> | | .apt-row | <tr> | | .apt-td | <td> / <th> | | .apt-footer | Pagination footer |

🔧 Framework Compatibility

  • ✅ Next.js (App Router & Pages Router)
  • ✅ Vite
  • ✅ Create React App
  • ✅ Remix
  • ✅ Any React 17+ project

Note: The component uses browser APIs (localStorage, ResizeObserver, window) — wrap it in a client-only boundary when using SSR frameworks like Next.js App Router.

🏭 Production Ready

  • Tree-shakeable — Only bundle what you use
  • TypeScript — Full type definitions included
  • Zero dependencies — No external libraries required
  • PerformantuseMemo and derived state throughout, no unnecessary re-renders

📄 License

MIT