Claude Code for TanStack Virtual: Headless Virtualization — Claude Skills 360 Blog
Blog / Frontend / Claude Code for TanStack Virtual: Headless Virtualization
Frontend

Claude Code for TanStack Virtual: Headless Virtualization

Published: June 20, 2027
Read time: 6 min read
By: Claude Skills 360

TanStack Virtual provides headless row virtualization — useVirtualizer({ count, getScrollElement, estimateSize }) creates a virtualizer instance. virtualizer.getVirtualItems() returns the visible range with key, index, start, and size per item. <div style={{ height: virtualizer.getTotalSize() }}> sets the scroll container height. <div style={{ transform: \translateY(${item.start}px)` }}>positions each row.measureElementcallback enables dynamic height measurement viaref. overscan={5}pre-renders buffer rows.useWindowVirtualizervirtualizes against the window scroll. Bidirectional scroll:scrollToIndex(0, { align: “start” })and dynamic prepend withadjustedOffset. Sticky indices: getVirtualIndexes({ stickyIndices: [0] }). useVirtualizeralso handles horizontal virtualization viahorizontal: true. Column-level virtualization: two nested useVirtualizer` instances for table rows and columns. Claude Code generates TanStack Virtual lists, infinite feeds, and virtualized tables.

CLAUDE.md for TanStack Virtual

## TanStack Virtual Stack
- Version: @tanstack/react-virtual >= 3.10
- Setup: const parentRef = useRef<HTMLDivElement>(null); const virt = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 60, overscan: 8 })
- Render: <div ref={parentRef} className="overflow-auto h-[500px]"><div style={{ height: virt.getTotalSize() }} className="relative">{virt.getVirtualItems().map(item => <div key={item.key} style={{ position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${item.start}px)` }}>)}</div></div>
- Dynamic: <div key={item.key} ref={virt.measureElement} data-index={item.index} style={{ ... }}>
- Scroll to: virt.scrollToIndex(targetIndex, { align: "center", behavior: "smooth" })

Virtualized List Component

// components/virtual/VirtualList.tsx — TanStack Virtual with dynamic measurement
"use client"
import { useRef, useState, useCallback, useMemo } from "react"
import { useVirtualizer } from "@tanstack/react-virtual"

type ListItem = {
  id: string
  title: string
  body: string
  meta?: string
  badge?: string
  badgeColor?: string
}

interface VirtualListProps {
  items: ListItem[]
  onItemClick?: (item: ListItem) => void
  height?: number
  estimatedItemHeight?: number
}

export function VirtualList({
  items,
  onItemClick,
  height = 600,
  estimatedItemHeight = 88,
}: VirtualListProps) {
  const parentRef = useRef<HTMLDivElement>(null)
  const [selectedId, setSelectedId] = useState<string | null>(null)
  const [query, setQuery] = useState("")

  const filtered = useMemo(
    () => query
      ? items.filter((i) => i.title.toLowerCase().includes(query.toLowerCase()) || i.body.toLowerCase().includes(query.toLowerCase()))
      : items,
    [items, query],
  )

  const virtualizer = useVirtualizer({
    count: filtered.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => estimatedItemHeight,
    overscan: 8,
  })

  const scrollToTop = useCallback(() => {
    virtualizer.scrollToIndex(0, { align: "start", behavior: "smooth" })
  }, [virtualizer])

  const handleSelect = useCallback((item: ListItem) => {
    setSelectedId(item.id)
    onItemClick?.(item)
  }, [onItemClick])

  return (
    <div className="flex flex-col border rounded-xl overflow-hidden bg-card" style={{ height }}>
      {/* Search */}
      <div className="flex items-center gap-3 p-3 border-b">
        <input
          type="search"
          value={query}
          onChange={(e) => { setQuery(e.target.value); virtualizer.scrollToOffset(0) }}
          placeholder={`Search ${items.length.toLocaleString()} items…`}
          className="flex-1 px-3 py-2 text-sm rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-ring"
        />
        <button onClick={scrollToTop} className="text-xs text-muted-foreground hover:text-foreground px-2">↑ Top</button>
      </div>

      {/* Count */}
      <div className="px-4 py-1.5 border-b bg-muted/30">
        <p className="text-xs text-muted-foreground">
          {filtered.length.toLocaleString()} {query ? "results" : "items"}
        </p>
      </div>

      {/* Scroll container */}
      <div ref={parentRef} className="flex-1 overflow-auto">
        <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
          {virtualizer.getVirtualItems().map((vItem) => {
            const item = filtered[vItem.index]
            if (!item) return null
            const isSelected = item.id === selectedId

            return (
              <div
                key={vItem.key}
                data-index={vItem.index}
                ref={virtualizer.measureElement}
                style={{
                  position: "absolute",
                  top: 0,
                  left: 0,
                  width: "100%",
                  transform: `translateY(${vItem.start}px)`,
                }}
              >
                <button
                  onClick={() => handleSelect(item)}
                  className={`w-full text-left px-4 py-3 border-b transition-colors ${
                    isSelected ? "bg-primary/10 border-l-2 border-l-primary" : "hover:bg-muted/50"
                  }`}
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="min-w-0 flex-1">
                      <p className="text-sm font-medium truncate">{item.title}</p>
                      <p className="text-xs text-muted-foreground mt-0.5 line-clamp-2 leading-relaxed">
                        {item.body}
                      </p>
                      {item.meta && (
                        <p className="text-xs text-muted-foreground/70 mt-1">{item.meta}</p>
                      )}
                    </div>
                    {item.badge && (
                      <span
                        className="flex-shrink-0 text-xs px-2 py-0.5 rounded-full font-medium mt-0.5"
                        style={{
                          backgroundColor: `${item.badgeColor ?? "#6366f1"}20`,
                          color: item.badgeColor ?? "#6366f1",
                        }}
                      >
                        {item.badge}
                      </span>
                    )}
                  </div>
                </button>
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

Virtualized Data Table

// components/virtual/VirtualTable.tsx — TanStack Virtual + Table with column virtualization
"use client"
import { useRef, useState } from "react"
import { useVirtualizer } from "@tanstack/react-virtual"

type Column<T> = {
  key: keyof T
  header: string
  width: number
  render?: (value: T[keyof T], row: T) => React.ReactNode
}

interface VirtualTableProps<T extends { id: string }> {
  rows: T[]
  columns: Column<T>[]
  height?: number
  rowHeight?: number
}

export function VirtualTable<T extends { id: string }>({
  rows,
  columns,
  height = 500,
  rowHeight = 48,
}: VirtualTableProps<T>) {
  const scrollRef = useRef<HTMLDivElement>(null)
  const [sortKey, setSortKey] = useState<keyof T | null>(null)
  const [sortDir, setSortDir] = useState<"asc" | "desc">("asc")

  const sorted = sortKey
    ? [...rows].sort((a, b) => {
        const av = a[sortKey], bv = b[sortKey]
        const cmp = av < bv ? -1 : av > bv ? 1 : 0
        return sortDir === "asc" ? cmp : -cmp
      })
    : rows

  const rowVirtualizer = useVirtualizer({
    count: sorted.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: () => rowHeight,
    overscan: 10,
  })

  const totalWidth = columns.reduce((sum, c) => sum + c.width, 0)

  const toggleSort = (key: keyof T) => {
    if (sortKey === key) setSortDir((d) => d === "asc" ? "desc" : "asc")
    else { setSortKey(key); setSortDir("asc") }
  }

  return (
    <div className="border rounded-xl overflow-hidden bg-card">
      {/* Header */}
      <div className="overflow-x-auto border-b bg-muted/50" style={{ minWidth: totalWidth }}>
        <div className="flex" style={{ width: totalWidth }}>
          {columns.map((col) => (
            <button
              key={String(col.key)}
              onClick={() => toggleSort(col.key)}
              className="flex items-center gap-1 px-3 py-3 text-xs font-semibold text-muted-foreground uppercase tracking-wide text-left hover:text-foreground transition-colors flex-shrink-0"
              style={{ width: col.width }}
            >
              {col.header}
              {sortKey === col.key && (
                <span className="text-primary">{sortDir === "asc" ? "↑" : "↓"}</span>
              )}
            </button>
          ))}
        </div>
      </div>

      {/* Scroll body */}
      <div ref={scrollRef} className="overflow-auto" style={{ height }}>
        <div style={{ height: rowVirtualizer.getTotalSize(), width: totalWidth, position: "relative" }}>
          {rowVirtualizer.getVirtualItems().map((vRow) => {
            const row = sorted[vRow.index]
            if (!row) return null

            return (
              <div
                key={vRow.key}
                style={{
                  position: "absolute",
                  top: 0,
                  left: 0,
                  width: "100%",
                  height: rowHeight,
                  transform: `translateY(${vRow.start}px)`,
                }}
                className={`flex items-center border-b ${vRow.index % 2 === 0 ? "" : "bg-muted/20"}`}
              >
                {columns.map((col) => (
                  <div
                    key={String(col.key)}
                    className="px-3 text-sm truncate flex-shrink-0"
                    style={{ width: col.width }}
                  >
                    {col.render
                      ? col.render(row[col.key], row)
                      : String(row[col.key] ?? "")}
                  </div>
                ))}
              </div>
            )
          })}
        </div>
      </div>

      {/* Footer */}
      <div className="px-4 py-2 border-t text-xs text-muted-foreground bg-muted/20">
        {rows.length.toLocaleString()} rows · {columns.length} columns
      </div>
    </div>
  )
}

For the react-window alternative when a smaller package size, a simpler API with pre-built FixedSizeList/VariableSizeList/FixedSizeGrid components, and an established ecosystem with react-window-infinite-loader is preferred — react-window is more opinionated but easier to drop in while TanStack Virtual is headless with no default DOM structure, ideal for custom grid layouts, see the react-window guide. For TanStack Table when combining row virtualization with column features like sorting, filtering, column resizing, and row grouping — @tanstack/react-table with @tanstack/react-virtual is the canonical pairing for high-performance data grids, see the TanStack Table guide. The Claude Skills 360 bundle includes TanStack Virtual skill sets covering dynamic heights, window virtualization, and table grids. Start with the free tier to try headless virtualization generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free