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

@kedman1234/react-light-table

v2.1.0

Published

A lightweight, accessible, sortable, searchable, and selectable React table component for any dataset

Readme

@kedman1234/react-light-table

npm version License: MIT TypeScript

A lightweight, accessible, sortable, searchable, and selectable React table component for any dataset. Built with TypeScript, zero dependencies beyond React.

Features

  • 🔍 Real-time search — filter rows instantly across all visible columns
  • ↕️ Column sorting — click to sort ascending/descending on any column
  • ✅ Row selection — individual and select-all with callback support
  • 📄 Pagination — built-in page navigation with customizable page size
  • 👁️ Column visibility — toggle columns on/off via dropdown menu
  • 📌 Column pinning — pin any column to the left or right edge via sticky positioning
  • âš¡ Row virtualization — render thousands of rows efficiently (only visible rows in DOM)
  • 🎛️ Controlled props — take full ownership of search, sort, page, and selection state
  • 🎨 Custom cell render — return any JSX per cell with the render prop
  • 🎨 Theming — full CSS custom properties for easy customization
  • ♿ Accessible — ARIA roles, labels, and keyboard navigation
  • 📦 Lightweight — small bundle, zero runtime dependencies
  • 🔒 TypeScript — full type safety with generics support
  • 🔄 Dual data source — works with local arrays or remote URLs

Installation

npm install @kedman1234/react-light-table

Note: react (≥18.0.0) and react-dom (≥18.0.0) are required as peer dependencies.

Quick Start

With Local Data

import { Table } from "@kedman1234/react-light-table";
import "@kedman1234/react-light-table/dist/table.css";

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

const columns = [
  { key: "name", path: "name", label: "Name", sortable: true },
  { key: "email", path: "email", label: "Email", sortable: true },
  { key: "age", path: "age", label: "Age", sortable: true },
];

const data: User[] = [
  { id: 1, name: "Alice", email: "[email protected]", age: 30 },
  { id: 2, name: "Bob", email: "[email protected]", age: 25 },
];

function App() {
  return (
    <Table<User>
      columns={columns}
      data={data}
      rowKey="id"
      isSearchable
      isSelectable
      pageSize={10}
      bordered
      striped
    />
  );
}

With Remote URL

<Table<User>
  columns={columns}
  url="https://jsonplaceholder.typicode.com/users"
  rowKey="id"
  isSearchable
  emptyMessage="No users found"
  errorMessage="Failed to load users"
/>

API Reference

<Table> Props

| Prop | Type | Default | Description | |---|---|---|---| | columns | ColumnDef<T>[] | required | Column definitions array | | data | T[] | — | Local data array (takes priority over url) | | url | string | — | Remote data URL (triggers fetch) | | rowKey | keyof T & string | 'id' | Field to use as unique row identifier | | className | string | '' | Additional CSS class(es) for the table | | isSearchable | boolean | false | Show search input | | isSelectable | boolean | false | Show selection checkboxes | | searchableFields | string[] | all visible columns | Which column paths to search | | pageSize | number | — | Rows per page (omit to show all) | | loading | boolean | false | External loading state control | | emptyMessage | string \| ReactNode | 'No data available' | Empty state message | | errorMessage | string \| ReactNode | 'Failed to load data' | Error state message | | stickyHeader | boolean | false | Sticky table header | | striped | boolean | false | Striped row styling | | bordered | boolean | false | Bordered table styling | | virtualized | boolean | false | Enable row virtualization (only visible rows rendered) | | expandable | { render: (row) => ReactNode; expandedByDefault?: boolean } | — | Render expandable sub-rows spanning the full table width | | exportCsv | boolean | false | Show an Export CSV button for the current filtered+sorted dataset | | onSelectionChange | (rows: T[]) => void | — | Callback when selection changes | | onSort | (column, direction) => void | — | Callback when sort changes | | onPageChange | (page: number) => void | — | Callback when page changes |

Controlled props — when provided, the component defers state management to the parent:

| Prop | Type | Paired callback | Description | |---|---|---|---| | searchValue | string | onSearchChange | Controlled search text | | onSearchChange | (text: string) => void | — | Called on every search input change | | sortState | SortState | onSortChange | Controlled sort state | | onSortChange | (state: SortState) => void | — | Called when user clicks a sort button | | page | number | onPageChange | Controlled current page (1-based) | | selectedRows | T[] | onSelectionChange | Controlled selected rows |

Column Definition (ColumnDef<T>)

| Property | Type | Required | Description | |---|---|---|---| | key | string | ✅ | Unique column identifier | | path | keyof T & string | ✅ | Data object property to display | | label | string | ✅ | Column header text | | sortable | boolean | ❌ | Enable sorting for this column | | isVisible | boolean | ❌ | Initial visibility (default: true) | | className | string | ❌ | CSS class for column cells | | pin | 'left' \| 'right' | ❌ | Pin column to the left or right edge | | formatter | (value, row) => ReactNode | ❌ | Simple cell transform (string/number output) | | render | (value, row) => ReactNode | ❌ | Full JSX cell renderer — takes precedence over formatter |

SortState type

interface SortState {
  key: string;
  direction: 'asc' | 'desc' | 'none';
}

Examples

Custom Cell Renderer (render prop)

Use render when you need full JSX control over a cell — badges, links, buttons, icons:

const columns = [
  {
    key: "status",
    path: "status",
    label: "Status",
    render: (value, row) => (
      <span style={{ color: value === "active" ? "green" : "red", fontWeight: 600 }}>
        {String(value).toUpperCase()}
      </span>
    ),
  },
];

Use formatter for simple string transforms (value → string):

{
  key: "salary",
  path: "salary",
  label: "Salary",
  formatter: (value) => `$${Number(value).toLocaleString()}`,
}

Column Pinning

Pin columns to the left or right so they remain visible during horizontal scroll:

const columns = [
  { key: "id", path: "id", label: "ID", pin: "left" },
  { key: "name", path: "name", label: "Name", pin: "left" },
  // ... scrollable middle columns ...
  { key: "status", path: "status", label: "Status", pin: "right" },
];

<Table
  columns={columns}
  data={data}
  rowKey="id"
  bordered
/>

Use the CSS custom properties to adjust pinned-column appearance:

:root {
  --rlt-pin-cell-bg: #fff;         /* pinned body cell background */
  --rlt-pin-header-bg: #f8f9fa;    /* pinned header cell background */
  --rlt-pin-shadow-left: 2px 0 6px rgba(0, 0, 0, 0.15);
  --rlt-pin-shadow-right: -2px 0 6px rgba(0, 0, 0, 0.15);
}

Row Virtualization

Render large datasets efficiently — only visible rows (+ 10 buffer rows) are in the DOM:

<Table
  columns={columns}
  data={largeDataset}   // e.g. 10 000 rows
  rowKey="id"
  virtualized
  stickyHeader
/>

Control the visible container height via CSS:

:root {
  --rlt-virtual-height: 600px;   /* default: 400px */
}

Controlled Props

Take full ownership of search, sort, page, and selection — useful for URL-synced tables or server-side data:

import { useState } from "react";
import { Table } from "@kedman1234/react-light-table";
import type { SortState } from "@kedman1234/react-light-table";

function MyTable() {
  const [search, setSearch] = useState("");
  const [sort, setSort] = useState<SortState>({ key: "", direction: "none" });
  const [page, setPage] = useState(1);
  const [selected, setSelected] = useState<User[]>([]);

  return (
    <Table<User>
      columns={columns}
      data={data}
      rowKey="id"
      isSelectable
      pageSize={20}
      searchValue={search}
      onSearchChange={setSearch}
      sortState={sort}
      onSortChange={setSort}
      page={page}
      onPageChange={setPage}
      selectedRows={selected}
      onSelectionChange={setSelected}
    />
  );
}

Each controlled dimension is independent — you can control just searchValue and leave the rest uncontrolled.

Row Expansion

<Table<User>
  columns={columns}
  data={data}
  rowKey="id"
  expandable={{
    render: (row) => <div>{row.email}</div>,
    expandedByDefault: false,
  }}
/>

CSV Export

<Table<User>
  columns={columns}
  data={data}
  isSearchable
  exportCsv
/>

The CSV export uses the current filtered and sorted dataset, not the raw input array. Cell values with leading =, +, -, or @ are sanitized before download to reduce spreadsheet formula-injection risk.

Column Resizing And Keyboard Navigation

Column resize handles are available on every visible column header. Focus a grid cell or header and use arrow keys to move between neighboring cells. The column visibility menu traps Tab focus while open and closes with Escape.

Selection Callback

<Table
  columns={columns}
  data={data}
  isSelectable
  onSelectionChange={(selected) => {
    console.log("Selected rows:", selected);
  }}
/>

Pagination

<Table
  columns={columns}
  data={data}
  pageSize={25}
  onPageChange={(page) => {
    console.log("Current page:", page);
  }}
/>

Theming with CSS Variables

Customize the entire look using CSS custom properties:

:root {
  --rlt-font-family: "Inter", sans-serif;
  --rlt-header-bg: #1a1a2e;
  --rlt-header-color: #eee;
  --rlt-row-hover-bg: #f0f4ff;
  --rlt-row-selected-bg: #d4edff;
  --rlt-border-color: #e0e0e0;
  --rlt-sort-icon-color: #999;
  --rlt-sort-active-color: #1a73e8;
  --rlt-search-border-color: #ccc;
  --rlt-search-focus-border: #1a73e8;
  --rlt-pagination-active-bg: #1a73e8;
  --rlt-pagination-active-color: #fff;
  --rlt-cell-padding: 10px 14px;
}

Available CSS Variables

| Variable | Default | Description | |---|---|---| | --rlt-font-family | inherit | Font family | | --rlt-font-size | 14px | Base font size | | --rlt-header-bg | #f8f9fa | Header background | | --rlt-header-color | #333 | Header text color | | --rlt-header-font-weight | 600 | Header font weight | | --rlt-row-hover-bg | #f5f5f5 | Row hover background | | --rlt-row-selected-bg | #e3f2fd | Selected row background | | --rlt-row-striped-bg | #fafafa | Striped row background | | --rlt-border-color | #dee2e6 | Border color | | --rlt-sort-icon-color | #666 | Sort icon color | | --rlt-sort-active-color | #333 | Active sort icon color | | --rlt-search-border-color | #ccc | Search input border | | --rlt-search-focus-border | #4a90d9 | Search input focus border | | --rlt-cell-padding | 12px 15px | Cell padding | | --rlt-pagination-active-bg | #4a90d9 | Active page button bg | | --rlt-pagination-active-color | #fff | Active page button color | | --rlt-virtual-height | 400px | Virtualized scroll container height | | --rlt-pin-cell-bg | inherit | Pinned body cell background | | --rlt-pin-header-bg | #f8f9fa | Pinned header cell background | | --rlt-pin-shadow-left | 2px 0 5px rgba(0,0,0,0.12) | Pinned left column shadow | | --rlt-pin-shadow-right | -2px 0 5px rgba(0,0,0,0.12) | Pinned right column shadow |

Exported Hooks

The library also exports the internal hooks for advanced use cases:

  • useSort<T>(data, onSort?, sortState?, onSortChange?) — Sort state management
  • useSearch<T>(data, searchableFields?, searchValue?, onSearchChange?) — Search/filter logic
  • useSelection<T>(data, rowKey, onSelectionChange?, selectedRows?) — Selection management
  • usePagination<T>(data, pageSize?, onPageChange?, page?) — Pagination logic

Each hook supports the same controlled/uncontrolled pattern as the <Table> component.

Browser Support

  • Chrome (last 2 versions)
  • Firefox (last 2 versions)
  • Safari (last 2 versions)
  • Edge (last 2 versions)

Contributing

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/my-feature
  3. Install dependencies: npm install
  4. Run the demo: npm run dev
  5. Run tests: npm test
  6. Commit your changes: git commit -m 'Add my feature'
  7. Push to the branch: git push origin feature/my-feature
  8. Open a Pull Request

License

MIT © Kedar Vijay Kulkarni