Server room with a holographic data table and a cat on a server rack

Billions of Rows? No Problem. Meet HighTable

The Gray Cat
The Gray Cat

Most React table libraries work great until your dataset gets serious. A few hundred rows? Easy. A few thousand? Still manageable. But what happens when you need to display millions -- or even billions -- of rows? That is where hightable enters the picture. Built by the team at Hyperparam, HighTable is a virtualized table component for React that renders only the rows currently visible in the viewport, fetches data asynchronously as the user scrolls, and does it all with zero runtime dependencies.

Whether you are building a data explorer, a log viewer, or an analytics dashboard backed by a massive dataset, HighTable gives you a focused, lightweight solution that does one thing exceptionally well.

What Makes It Tick

HighTable packs several capabilities into its lean frame:

  • Virtualized scrolling that renders only visible rows, keeping memory usage constant regardless of dataset size
  • Asynchronous data loading through a DataFrame abstraction that fetches rows on demand with AbortSignal support
  • Column sorting with controlled and uncontrolled modes
  • Column resizing with auto-sizing and persistent widths via a cache key
  • Row selection including multi-select with shift+click
  • Column visibility controls for showing and hiding columns programmatically
  • Custom cell rendering through a renderCellContent prop
  • Keyboard navigation for page-based movement through data
  • Loading placeholders with animated indicators while cells resolve

Getting Started

Install with your preferred package manager:

npm install hightable
yarn add hightable

HighTable requires React 18.3.1 or newer, including React 19. It ships as an ESM module with full TypeScript types.

Your First Table in Sixty Seconds

Wrapping an Array

The fastest way to get a table on screen is with the arrayDataFrame helper, which converts a plain array of objects into the DataFrame format HighTable expects:

import HighTable from 'hightable'
import { arrayDataFrame } from 'hightable'
import 'hightable/src/HighTable.css'

const data = [
  { name: 'Alice', age: 32, city: 'Portland' },
  { name: 'Bob', age: 27, city: 'Austin' },
  { name: 'Charlie', age: 45, city: 'Denver' },
]

function App() {
  return <HighTable data={arrayDataFrame(data)} />
}

That is it. You get a styled, scrollable table with column headers derived from your object keys. The arrayDataFrame utility handles all the plumbing behind the scenes.

Adding Sorting

To make columns sortable, wrap the DataFrame with sortableDataFrame and wire up the ordering state:

import { useState } from 'react'
import HighTable from 'hightable'
import { arrayDataFrame, sortableDataFrame } from 'hightable'
import type { OrderBy } from 'hightable'
import 'hightable/src/HighTable.css'

const rawData = arrayDataFrame([
  { name: 'Alice', age: 32, score: 88 },
  { name: 'Bob', age: 27, score: 95 },
  { name: 'Charlie', age: 45, score: 72 },
])

function SortableTable() {
  const [orderBy, setOrderBy] = useState<OrderBy | undefined>()
  const data = sortableDataFrame(rawData)

  return (
    <HighTable
      data={data}
      orderBy={orderBy}
      onOrderByChange={setOrderBy}
    />
  )
}

Clicking a column header toggles sorting. The sortableDataFrame wrapper adds client-side sorting logic to any DataFrame without mutating the original.

Custom Cell Content

Need to render something fancier than plain text in a cell? The renderCellContent prop gives you full control:

import HighTable from 'hightable'
import { arrayDataFrame } from 'hightable'

const data = arrayDataFrame([
  { name: 'Alice', status: 'active', progress: 0.88 },
  { name: 'Bob', status: 'inactive', progress: 0.45 },
])

function FancyTable() {
  return (
    <HighTable
      data={data}
      renderCellContent={({ value, column }) => {
        if (column === 'progress' && typeof value === 'number') {
          const percent = Math.round(value * 100)
          return <div style={{ width: `${percent}%`, background: '#4caf50', height: 8 }} />
        }
        return undefined
      }}
    />
  )
}

Return undefined from the renderer to fall back to the default string display. This lets you selectively override only the columns that need special treatment.

Scaling to Millions with DataFrames

The DataFrame Contract

The real power of HighTable is its DataFrame interface. Instead of passing a flat array, you define an object that describes how to access your data lazily:

import type { DataFrame } from 'hightable'
import { createEventTarget } from 'hightable'

function createRemoteDataFrame(totalRows: number): DataFrame {
  const cache = new Map<number, Record<string, unknown>>()
  const eventTarget = createEventTarget()

  return {
    columnDescriptors: [
      { name: 'id', sortable: true },
      { name: 'timestamp', sortable: true },
      { name: 'message' },
    ],
    numRows: totalRows,
    getCell(rowIndex: number, columnName: string) {
      return cache.get(rowIndex)?.[columnName]
    },
    getRowNumber(rowIndex: number) {
      return rowIndex
    },
    async fetch(startRow: number, endRow: number, signal?: AbortSignal) {
      const response = await fetch(
        `/api/logs?start=${startRow}&end=${endRow}`,
        { signal }
      )
      const rows = await response.json()
      rows.forEach((row: Record<string, unknown>, i: number) => {
        cache.set(startRow + i, row)
      })
      eventTarget.dispatchEvent(new Event('resolve'))
    },
    eventTarget,
  }
}

When a cell is not yet loaded, getCell returns undefined and HighTable displays a loading placeholder. Once fetch completes and fires a resolve event, the table re-renders with the real data. The AbortSignal support means rapid scrolling cancels stale requests automatically.

Controlling Column Visibility

For wide datasets with many columns, you can let users toggle which columns are visible:

import { useState } from 'react'
import HighTable from 'hightable'

function FilterableColumns({ data }: { data: DataFrame }) {
  const [visibility, setVisibility] = useState<Record<string, { hidden: true }>>({
    internalId: { hidden: true },
    debugInfo: { hidden: true },
  })

  return (
    <HighTable
      data={data}
      columnsVisibility={visibility}
      onColumnsVisibilityChange={setVisibility}
    />
  )
}

Columns listed in the visibility map with hidden: true are excluded from rendering. The callback keeps your state in sync when the user interacts with column controls.

Handling Cell Events

HighTable exposes event handlers for interactive use cases like detail panels or context menus:

<HighTable
  data={data}
  onDoubleClickCell={({ rowIndex, column }) => {
    console.log(`Double clicked row ${rowIndex}, column ${column}`)
  }}
  onKeyDownCell={({ event, rowIndex, column }) => {
    if (event.key === 'Enter') {
      openDetailPanel(rowIndex)
    }
  }}
/>

These handlers receive the row index and column name along with the original DOM event, giving you everything needed to build rich interactions on top of the table.

Why Not TanStack Table or AG Grid?

If you have used React tables before, you have probably encountered TanStack Table or AG Grid. Both are excellent, but they solve different problems. TanStack Table is headless and highly flexible, but it does not include virtualization out of the box and expects you to provide all data upfront. AG Grid is a full-featured enterprise grid with built-in virtualization, filtering, and grouping, but it is significantly heavier and its advanced features require a commercial license.

HighTable carves out its own niche: a focused, zero-dependency virtualized table with an async-first data model. If your primary challenge is displaying a truly massive dataset that cannot fit in memory, the DataFrame abstraction is purpose-built for that scenario. You trade the breadth of AG Grid's feature set for a dramatically simpler API and a smaller footprint.

Wrapping Up

HighTable is a sharp tool for a specific job. It does not try to be everything to everyone. It does not have built-in filtering, pivoting, or cell editing. What it does have is a clean async DataFrame model that lets you render tables backed by datasets of any size, zero dependencies adding weight to your bundle, and a straightforward React API with controlled and uncontrolled patterns for sorting, selection, and visibility.

If you are building something that needs to display large tabular data with lazy loading, hightable is worth a serious look. It is actively maintained, TypeScript-first, and supports both React 18 and 19. At version 0.26 it is still pre-1.0, but with over 100 releases since mid-2024, the API is maturing fast.