VirtualGrid

The VirtualGrid component efficiently renders large 2D grids by only rendering visible cells. It supports variable row/column sizes, cell spanning (colspan/rowspan), and sticky headers/columns.

When to Use

  • VirtualList: For 1D vertical scrolling lists (file trees, chat messages, log viewers)
  • VirtualGrid: For 2D grids with both horizontal and vertical scrolling (data tables, spreadsheets, image galleries, dashboards)

Props

PropTypeDefaultDescription
dataT[][]-2D array of cell data
rowCountnumber-Number of rows in the grid
columnCountnumber-Number of columns in the grid
rowHeightnumber | ((rowIndex: number) => number)-Height for each row
columnWidthnumber | ((colIndex: number) => number)-Width for each column
heightnumber | string-Container height
widthnumber | string'100%'Container width
overscanRowsnumber2Extra rows to render outside visible area
overscanColumnsnumber2Extra columns to render outside visible area
getCellKey(row: number, col: number) => string-Function to get a unique key for each cell
getColSpan(row: number, col: number) => number-Function to get column span for a cell
getRowSpan(row: number, col: number) => number-Function to get row span for a cell
children(data: T, row: number, col: number, style: CSSProperties) => ReactNode-Render function for each cell
stickyHeaderbooleanfalseKeep first row visible when scrolling
stickyColumnbooleanfalseKeep first column visible when scrolling
classNamestring-Additional className for the container

Basic Usage

A simple grid with fixed cell sizes.

R0C0
R0C1
R0C2
R1C0
R1C1
R1C2
R2C0
R2C1
R2C2
const rowCount = 100;
const columnCount = 20;
const data = Array.from({ length: rowCount }, (_, row) =>
  Array.from({ length: columnCount }, (_, col) => ({
    row,
    col,
    value: `R${row}C${col}`
  }))
);

<VirtualGrid
  data={data}
  rowCount={rowCount}
  columnCount={columnCount}
  rowHeight={36}
  columnWidth={100}
  height={300}
>
  {(cell, row, col, style) => (
    <div
      style={style}
      className="flex items-center justify-center border-r border-b"
    >
      {cell?.value}
    </div>
  )}
</VirtualGrid>

Keep the first row visible while scrolling vertically.

Name
Email
Role
User 1
user1@example.com
Admin
User 2
user2@example.com
User
const columns = ['Name', 'Email', 'Role', 'Status', ...];

<VirtualGrid
  data={data}
  rowCount={100}
  columnCount={columns.length}
  rowHeight={36}
  columnWidth={120}
  height={300}
  stickyHeader
>
  {(cell, row, col, style) => (
    <div
      style={style}
      className={row === 0 ? 'bg-bg-tertiary font-medium' : 'bg-bg-primary'}
    >
      {cell}
    </div>
  )}
</VirtualGrid>

Sticky Column

Keep the first column visible while scrolling horizontally.

Row 1
Row 2
Row 3
792
827
493
928
301
501
<VirtualGrid
  data={data}
  rowCount={50}
  columnCount={15}
  rowHeight={36}
  columnWidth={(col) => col === 0 ? 80 : 100}
  height={300}
  stickyColumn
>
  {(cell, row, col, style) => (
    <div
      style={style}
      className={col === 0 ? 'bg-bg-tertiary font-medium' : 'bg-bg-primary'}
    >
      {cell}
    </div>
  )}
</VirtualGrid>

Sticky Header and Column

Combine sticky header and column for spreadsheet-like behavior.

A
B
1
2
99
42
62
36
const getColumnLetter = (index) => String.fromCharCode(65 + index);

<VirtualGrid
  data={data}
  rowCount={100}
  columnCount={26}
  rowHeight={32}
  columnWidth={(col) => col === 0 ? 50 : 80}
  height={350}
  stickyHeader
  stickyColumn
>
  {(cell, row, col, style) => {
    const isHeader = row === 0 || col === 0;
    return (
      <div
        style={style}
        className={isHeader ? 'bg-bg-secondary font-medium' : 'bg-bg-primary'}
      >
        {cell}
      </div>
    );
  }}
</VirtualGrid>

Variable Row Heights

Use a function for rowHeight to specify different heights per row.

R0C0
Expanded row
R0C1
Expanded row
R0C2
Expanded row
R1C0
R1C1
R1C2
R2C0
R2C1
R2C2
<VirtualGrid
  data={data}
  rowCount={50}
  columnCount={5}
  rowHeight={(row) => row % 4 === 0 ? 80 : 40}
  columnWidth={120}
  height={300}
>
  {(cell, row, col, style) => (
    <div
      style={style}
      className={cell?.expanded ? 'bg-brand-tertiary' : 'bg-bg-primary'}
    >
      R{row}C{col}
    </div>
  )}
</VirtualGrid>

Cell Spanning (Colspan)

Use getColSpan to create cells that span multiple columns.

Date
Event
SF
Event for row 1
LA
Event for row 2
const getColSpan = (row, col) => {
  if (col === 1) return 2; // Event column spans 2 cells
  return 1;
};

<VirtualGrid
  data={data}
  rowCount={20}
  columnCount={6}
  rowHeight={40}
  columnWidth={100}
  height={300}
  getColSpan={getColSpan}
  stickyHeader
>
  {(cell, row, col, style) => (
    <div style={style}>{cell}</div>
  )}
</VirtualGrid>

A practical example showing an image gallery grid.

1
2
3
5
6
7
9
10
11
<VirtualGrid
  data={data}
  rowCount={20}
  columnCount={4}
  rowHeight={120}
  columnWidth={120}
  height={350}
>
  {(cell, row, col, style) => (
    <div style={style} className="p-1">
      <div
        className="w-full h-full rounded-uk-md"
        style={{ backgroundColor: cell?.color }}
      >
        {cell?.id + 1}
      </div>
    </div>
  )}
</VirtualGrid>

Types

interface VirtualGridProps<T> {
  data: T[][];
  rowCount: number;
  columnCount: number;
  rowHeight: number | ((rowIndex: number) => number);
  columnWidth: number | ((colIndex: number) => number);
  height: number | string;
  width?: number | string;
  overscanRows?: number;
  overscanColumns?: number;
  getCellKey?: (row: number, col: number) => string;
  getColSpan?: (row: number, col: number) => number;
  getRowSpan?: (row: number, col: number) => number;
  children: (data: T, row: number, col: number, style: CSSProperties) => ReactNode;
  stickyHeader?: boolean;
  stickyColumn?: boolean;
  className?: string;
}

Hook: useVirtualGrid

The useVirtualGrid hook can be used to build custom virtualized grid implementations.

import { useVirtualGrid } from '@vuer-ai/vuer-uikit';

const {
  visibleRange,
  totalWidth,
  totalHeight,
  getRowOffset,
  getColumnOffset,
  getRowHeight,
  getColumnWidth,
  getCellStyle,
} = useVirtualGrid({
  rowCount: 100,
  columnCount: 20,
  rowHeight: 40,
  columnWidth: 100,
  containerWidth: 800,
  containerHeight: 400,
  scrollTop: 0,
  scrollLeft: 0,
  overscanRows: 2,
  overscanColumns: 2,
});

Usage

import { VirtualGrid } from '@vuer-ai/vuer-uikit';

function MyComponent() {
  const rowCount = 1000;
  const columnCount = 50;

  const data = Array.from({ length: rowCount }, (_, row) =>
    Array.from({ length: columnCount }, (_, col) => `${row}-${col}`)
  );

  return (
    <VirtualGrid
      data={data}
      rowCount={rowCount}
      columnCount={columnCount}
      rowHeight={40}
      columnWidth={100}
      height={400}
      stickyHeader
    >
      {(cell, row, col, style) => (
        <div style={style} className="p-2 border">
          {cell}
        </div>
      )}
    </VirtualGrid>
  );
}