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

@venkateshsirigineedi/bolt-table

v0.1.0

Published

Virtualized React table with column drag & drop, pinning, resizing, sorting, filtering, and pagination.

Downloads

59

Readme

@venkateshsirigineedi/bolt-table

A high-performance, fully-featured React table component built on TanStack Virtual and @dnd-kit. Only the rows visible in the viewport are ever in the DOM — making it fast for datasets of any size.

npm version license


Features

  • Row virtualization — only visible rows are rendered, powered by TanStack Virtual
  • Drag to reorder columns — grab any header and drag it to a new position
  • Column pinning — pin columns to the left or right edge via right-click
  • Column resizing — drag the right edge of any header to resize
  • Column hiding — hide/show columns via the right-click context menu
  • Sorting — client-side or server-side, with custom comparators per column
  • Filtering — client-side or server-side, with custom filter functions per column
  • Pagination — client-side slice or server-side with full control
  • Row selection — checkbox or radio, with select-all, indeterminate state, and disabled rows
  • Expandable rows — auto-measured content panels below each row, controlled or uncontrolled
  • Shimmer loading — animated skeleton rows on initial load and infinite scroll append
  • Infinite scrollonEndReached callback with configurable threshold
  • Empty state — custom renderer or default "No data" message
  • Auto height — table shrinks/grows to fit rows, capped at 10 rows by default
  • Right-click context menu — sort, filter, pin, hide, plus custom items
  • Dark mode — works out of the box with CSS variables

Installation

npm install @venkateshsirigineedi/bolt-table

Peer dependencies

These must be installed separately in your project:

npm install @tanstack/react-virtual @dnd-kit/core @dnd-kit/sortable lucide-react

Quick start

import { BoltTable, ColumnType } from '@venkateshsirigineedi/bolt-table';

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

const columns: ColumnType<User>[] = [
  { key: 'name',  dataIndex: 'name',  title: 'Name',  width: 200 },
  { key: 'email', dataIndex: 'email', title: 'Email', width: 280 },
  { key: 'age',   dataIndex: 'age',   title: 'Age',   width: 80  },
];

const data: User[] = [
  { id: '1', name: 'Alice',   email: '[email protected]',   age: 28 },
  { id: '2', name: 'Bob',     email: '[email protected]',     age: 34 },
  { id: '3', name: 'Charlie', email: '[email protected]', age: 22 },
];

export default function App() {
  return (
    <BoltTable<User>
      columns={columns}
      data={data}
      rowKey="id"
    />
  );
}

Next.js (App Router)

BoltTable uses browser APIs and must be wrapped in a client boundary. Remove the 'use client' directive from the component files and wrap usage instead:

'use client';
import { BoltTable } from '@venkateshsirigineedi/bolt-table';

Styling

BoltTable uses Tailwind CSS utility classes and Shadcn/ui CSS variables (--muted, --background, --border, etc.).

Make sure your project has Tailwind configured and the Shadcn CSS variables defined in your global stylesheet. If you use a different design system, you can override styles via the styles and classNames props.


Props

BoltTable

| Prop | Type | Default | Description | |------|------|---------|-------------| | columns | ColumnType<T>[] | — | Column definitions (required) | | data | T[] | — | Row data array (required) | | rowKey | string \| (record: T) => string | 'id' | Unique row identifier | | rowHeight | number | 40 | Height of each row in pixels | | expandedRowHeight | number | 200 | Estimated height for expanded rows | | maxExpandedRowHeight | number | — | Max height for expanded row panels (makes them scrollable) | | accentColor | string | '#1890ff' | Color used for sort icons, selected rows, resize line, etc. | | className | string | '' | Class name for the outer wrapper | | classNames | ClassNamesTypes | {} | Granular class overrides per table region | | styles | StylesTypes | {} | Inline style overrides per table region | | gripIcon | ReactNode | — | Custom drag grip icon (defaults to GripVertical) | | hideGripIcon | boolean | false | Hide the drag grip icon from all headers | | pagination | PaginationType \| false | — | Pagination config, or false to disable | | onPaginationChange | (page, pageSize) => void | — | Called when page or page size changes | | onColumnResize | (columnKey, newWidth) => void | — | Called when a column is resized | | onColumnOrderChange | (newOrder) => void | — | Called when columns are reordered | | onColumnPin | (columnKey, pinned) => void | — | Called when a column is pinned/unpinned | | onColumnHide | (columnKey, hidden) => void | — | Called when a column is hidden/shown | | rowSelection | RowSelectionConfig<T> | — | Row selection config | | expandable | ExpandableConfig<T> | — | Expandable row config | | onEndReached | () => void | — | Called when scrolled near the bottom (infinite scroll) | | onEndReachedThreshold | number | 5 | Rows from end to trigger onEndReached | | isLoading | boolean | false | Shows shimmer skeleton rows when true | | onSortChange | (columnKey, direction) => void | — | Server-side sort handler (disables local sort) | | onFilterChange | (filters) => void | — | Server-side filter handler (disables local filter) | | columnContextMenuItems | ColumnContextMenuItem[] | — | Custom items appended to the header context menu | | autoHeight | boolean | true | Auto-size table height to content (capped at 10 rows) | | layoutLoading | boolean | false | Show full skeleton layout (headers + rows) | | emptyRenderer | ReactNode | — | Custom empty state content |


ColumnType<T>

| Field | Type | Default | Description | |-------|------|---------|-------------| | key | string | — | Unique column identifier (required) | | dataIndex | string | — | Row object property to display (required) | | title | string \| ReactNode | — | Header label (required) | | width | number | 150 | Column width in pixels | | render | (value, record, index) => ReactNode | — | Custom cell renderer | | shimmerRender | () => ReactNode | — | Custom shimmer skeleton for this column | | sortable | boolean | true | Show sort controls for this column | | sorter | boolean \| (a: T, b: T) => number | — | Custom sort comparator for client-side sort | | filterable | boolean | true | Show filter option in context menu | | filterFn | (value, record, dataIndex) => boolean | — | Custom filter predicate for client-side filter | | hidden | boolean | false | Hide this column | | pinned | 'left' \| 'right' \| false | false | Pin this column to an edge | | className | string | — | Class applied to all cells in this column | | style | CSSProperties | — | Styles applied to all cells in this column |


Examples

Sorting

Client-side (no onSortChange — BoltTable sorts locally):

const columns: ColumnType<User>[] = [
  {
    key: 'name',
    dataIndex: 'name',
    title: 'Name',
    sortable: true,
    // Optional custom comparator:
    sorter: (a, b) => a.name.localeCompare(b.name),
  },
  {
    key: 'age',
    dataIndex: 'age',
    title: 'Age',
    sortable: true,
    // Default numeric comparator used when sorter is omitted
  },
];

<BoltTable columns={columns} data={data} />

Server-side (provide onSortChange — BoltTable delegates to you):

const [sortKey, setSortKey] = useState('');
const [sortDir, setSortDir] = useState<SortDirection>(null);

<BoltTable
  columns={columns}
  data={serverData}
  onSortChange={(key, dir) => {
    setSortKey(key);
    setSortDir(dir);
    refetch({ sortKey: key, sortDir: dir });
  }}
/>

Filtering

Client-side (no onFilterChange):

const columns: ColumnType<User>[] = [
  {
    key: 'status',
    dataIndex: 'status',
    title: 'Status',
    filterable: true,
    // Exact match instead of default substring:
    filterFn: (value, record) => record.status === value,
  },
];

Server-side:

<BoltTable
  columns={columns}
  data={serverData}
  onFilterChange={(filters) => {
    setActiveFilters(filters);
    refetch({ filters });
  }}
/>

Pagination

Client-side (pass all data, BoltTable slices it):

<BoltTable
  columns={columns}
  data={allUsers}          // all 500 rows
  pagination={{ pageSize: 20 }}
  onPaginationChange={(page, size) => {
    setPage(page);
  }}
/>

Server-side (pass only the current page):

<BoltTable
  columns={columns}
  data={currentPageData}   // only 20 rows
  pagination={{
    current: page,
    pageSize: 20,
    total: 500,
    showTotal: (total, [from, to]) => `${from}-${to} of ${total} users`,
  }}
  onPaginationChange={(page, size) => fetchPage(page, size)}
/>

Disable pagination:

<BoltTable columns={columns} data={data} pagination={false} />

Row selection

const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);

<BoltTable
  columns={columns}
  data={data}
  rowKey="id"
  rowSelection={{
    type: 'checkbox',           // or 'radio'
    selectedRowKeys,
    onChange: (keys, rows) => setSelectedRowKeys(keys),
    // Disable selection for specific rows:
    getCheckboxProps: (record) => ({
      disabled: record.status === 'locked',
    }),
  }}
/>

Expandable rows

<BoltTable
  columns={columns}
  data={data}
  rowKey="id"
  expandable={{
    rowExpandable: (record) => record.details !== null,
    expandedRowRender: (record) => (
      <div style={{ padding: 16 }}>
        <h4>{record.name} — Details</h4>
        <pre>{JSON.stringify(record.details, null, 2)}</pre>
      </div>
    ),
    // Optional: control expanded state yourself
    // expandedRowKeys={expandedKeys}
    // onExpandedRowsChange={(keys) => setExpandedKeys(keys)}
  }}
  expandedRowHeight={150}      // initial estimate
  maxExpandedRowHeight={400}   // makes panel scrollable if taller
/>

Infinite scroll

const [data, setData] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(false);

const loadMore = async () => {
  setIsLoading(true);
  const newRows = await fetchNextPage();
  setData(prev => [...prev, ...newRows]);
  setIsLoading(false);
};

<BoltTable
  columns={columns}
  data={data}
  isLoading={isLoading}
  onEndReached={loadMore}
  onEndReachedThreshold={8}
  pagination={false}
/>

Column pinning

Pinning via column definition:

const columns: ColumnType<User>[] = [
  { key: 'name',    dataIndex: 'name',    title: 'Name',    pinned: 'left',  width: 200 },
  { key: 'email',   dataIndex: 'email',   title: 'Email',   width: 250 },
  { key: 'actions', dataIndex: 'actions', title: 'Actions', pinned: 'right', width: 100 },
];

Users can also pin/unpin columns at runtime via the right-click context menu.


Custom cell rendering

const columns: ColumnType<User>[] = [
  {
    key: 'status',
    dataIndex: 'status',
    title: 'Status',
    width: 120,
    render: (value, record) => (
      <span
        style={{
          padding: '2px 8px',
          borderRadius: 4,
          fontSize: 12,
          backgroundColor: record.status === 'active' ? '#d1fae5' : '#fee2e2',
          color: record.status === 'active' ? '#065f46' : '#991b1b',
        }}
      >
        {String(value)}
      </span>
    ),
  },
];

Custom context menu items

<BoltTable
  columns={columns}
  data={data}
  columnContextMenuItems={[
    {
      key: 'copy',
      label: 'Copy column data',
      icon: <CopyIcon className="h-3 w-3" />,
      onClick: (columnKey) => copyColumnToClipboard(columnKey),
    },
    {
      key: 'reset-width',
      label: 'Reset width',
      onClick: (columnKey) => resetColumnWidth(columnKey),
    },
  ]}
/>

Styling overrides

<BoltTable
  columns={columns}
  data={data}
  accentColor="#6366f1"
  classNames={{
    header: 'text-xs uppercase tracking-wider text-gray-500',
    cell: 'text-sm',
    pinnedHeader: 'border-r border-indigo-200',
    pinnedCell: 'border-r border-indigo-100',
  }}
  styles={{
    header: { fontWeight: 600 },
    rowHover: { backgroundColor: '#f0f9ff' },
    rowSelected: { backgroundColor: '#e0e7ff' },
    pinnedBg: 'rgba(238, 242, 255, 0.95)',
  }}
/>

Loading skeleton

// Full skeleton on initial load (no data yet)
<BoltTable
  columns={columns}
  data={[]}
  isLoading={true}
  pagination={{ pageSize: 20 }}
/>

// Layout skeleton before column widths are known
<BoltTable
  columns={columns}
  data={[]}
  layoutLoading={true}
/>

Fixed height (fill parent)

By default, BoltTable auto-sizes to its content. To fill a fixed-height container instead:

<div style={{ height: 600 }}>
  <BoltTable
    columns={columns}
    data={data}
    autoHeight={false}
  />
</div>

Type exports

import type {
  ColumnType,
  ColumnContextMenuItem,
  RowSelectionConfig,
  ExpandableConfig,
  PaginationType,
  SortDirection,
  DataRecord,
} from '@venkateshsirigineedi/bolt-table';

License

MIT © Venkatesh Sirigineedi