Skip to content

Column Resizing Example

A resize handle on every column plus an external button that sets the product column to a fixed width. Both publish to the same resizeColumn$ stream; columnWidths$ exposes the realized layout for the readout.

  • HeaderEdge — slot position for the handle
  • ResizeHandle — shadcn drag affordance
  • resizeColumn$ — width override stream
  • columnWidths$ — realized layout state for display
import { Button } from '@/components/ui/button'
import { DataTable, DataTableCell, DataTableColumn, DataTableColumnHeader, HeaderEdge } from '@/components/ui/data-table'
import { ResizeHandle } from '@/components/ui/data-table/column-resize'
import { localModel, columnWidths$, columns$, useEngineRef, useRemoteCellValue, useRemotePublisher } from '@virtuoso.dev/data-table'
import { resetColumnWidthOverrides$, resizeColumn$ } from '@virtuoso.dev/data-table/column-resize'

import { products } from './data'

import type { ColumnInfo } from '@virtuoso.dev/data-table'

function findColumnKey(columns: Map<string, ColumnInfo> | undefined, field: string) {
  return columns ? [...columns].find(([, info]) => info.field === field)?.[0] : undefined
}

const model = localModel({ data: products })

export default function App() {
  const engineRef = useEngineRef()
  const columns = useRemoteCellValue(columns$, engineRef)
  const widths = useRemoteCellValue(columnWidths$, engineRef)
  const resizeColumn = useRemotePublisher(resizeColumn$, engineRef)
  const resetColumnWidthOverrides = useRemotePublisher(resetColumnWidthOverrides$, engineRef)

  const nameKey = findColumnKey(columns, 'name')
  const nameWidth = nameKey && widths ? Math.round(widths.get(nameKey) ?? 0) : 0

  return (
    <div className="space-y-4">
      <div className="flex flex-wrap items-center gap-2 text-sm">
        <Button
          disabled={!nameKey}
          onClick={() => {
            if (nameKey) {
              resizeColumn({ key: nameKey, width: 280 })
            }
          }}
          variant="outline"
        >
          Set product to 280px
        </Button>
        <Button onClick={() => resetColumnWidthOverrides()} variant="outline">
          Reset widths
        </Button>
        <span className="text-muted-foreground">Product width: {nameWidth || 'measuring'}px</span>
      </div>

      <DataTable className="rounded-xl" model={model} engineRef={engineRef} style={{ height: 400 }}>
        <DataTableColumn field="name">
          <DataTableColumnHeader>
            <HeaderEdge component={ResizeHandle} />
            {() => 'Product'}
          </DataTableColumnHeader>
          <DataTableCell className="font-medium">{({ row }) => row.data.name}</DataTableCell>
        </DataTableColumn>
        <DataTableColumn field="category">
          <DataTableColumnHeader>
            <HeaderEdge component={ResizeHandle} />
            {() => 'Category'}
          </DataTableColumnHeader>
          <DataTableCell>{({ cellValue }) => String(cellValue)}</DataTableCell>
        </DataTableColumn>
        <DataTableColumn field="stock">
          <DataTableColumnHeader className="justify-end">
            <HeaderEdge component={ResizeHandle} />
            {() => 'Stock'}
          </DataTableColumnHeader>
          <DataTableCell className="text-right tabular-nums">{({ cellValue }) => String(cellValue)}</DataTableCell>
        </DataTableColumn>
      </DataTable>
    </div>
  )
}

Drag handles cover spreadsheet-style interaction. External controls cover presets, reset buttons, and commands that shouldn’t depend on a pointer — both publish to the same stream, so they stay consistent.