VitalGrid
Guides

VitalGrid Concepts Guide

This guide explains the core ideas behind VitalGrid in simple terms.

This guide explains the core ideas behind VitalGrid in simple terms.

It is for people who want to understand how VitalGrid is put together so they can use it correctly and extend it safely.

If you just want to see code, start with:

  • Getting Started
  • Then come back here when you want to know why things are structured this way

What VitalGrid provides

VitalGrid is a ready-to-use table system that:

  • Uses TanStack React Table under the hood
  • Supports large datasets with virtualization
  • Supports drag and drop for rows and columns
  • Supports dynamic columns driven by your backend
  • Works with any backend through a clean data source interface
  • Gives you composable React components to build your own table UI

To make this work without getting messy, we follow a few core concepts:

  1. Factory
  2. Hook
  3. Provider
  4. Compound components
  5. Data source (VitalGridDataSource)
  6. Columns (VitalGridColumn)
  7. Fields (cell types)
  8. Feature flags and capabilities

The rest of this guide walks through each of these.


1. Factory: createVitalGrid

The factory is your starting point.

You call it once, outside of React components:

  • Configure global features (sorting, resizing, virtualization, etc.)
  • Register field types (text, number, status, etc.)

Example (conceptual):

const VitalGrid = createVitalGrid({
  features: {
    sorting: true,
    filtering: true,
    columnResizing: true,
    virtualization: true,
    views: true,
  },
  fields: {
    text: createTextField(),
    number: createNumberField(),
    // your custom fields...
  },
});

The factory returns:

  • useVitalGrid – the main hook for runtime configuration
  • Provider – context provider for your grid
  • A set of UI components (Root, Toolbar, Header, Body, Columns, etc.)
  • Helpers like createColumnDef (depending on your entrypoint)

Key points:

  • The factory is pure configuration.
  • No data fetching here.
  • No React hooks here.
  • You can create more than one factory if you like (for different apps or setups).

2. Hook: VitalGrid.useVitalGrid

Inside your React component, you call the factory's hook.

Here you pass runtime values:

  • Your data source (backend adapter)
  • Your rows and columns (based on the data source)
  • Any per-instance options

Conceptually:

function MyGrid() {
  const dataSource = useMyDataSource();

  const { grid, VitalGridProvider } = VitalGrid.useVitalGrid({
    dataSource,
    data: dataSource.rows.current,
    columns: dataSource.columns.current.map((column) =>
      VitalGrid.createColumnDef(column)
    ),
  });

  return (
    <VitalGridProvider
      grid={grid}
      workspaceId="my-workspace"
      height={600}
    >
      {/* UI components go here */}
    </VitalGridProvider>
  );
}

What useVitalGrid does for you:

  • Wires up TanStack React Table
  • Applies feature flags
  • Respects what the data source says it supports
  • Exposes a grid object that contains:
    • table (the TanStack table)
    • features (final enabled features)
    • fields (field registry)
    • localPreferenceManager (for widths etc.)

Key points:

  • This is where you pass dataSource.
  • You do not pass dataSource to the factory.
  • This split keeps code clean and testable.

3. Provider: VitalGridProvider

VitalGridProvider wraps your grid UI.

It:

  • Stores the table object in context
  • Shares features, workspace/view IDs, height, scroll container, etc.
  • Powers all the compound components

Conceptual layout:

<VitalGridProvider
  grid={grid}
  workspaceId="my-workspace"
  viewId="main-view"
  height={600}
>
  <VitalGrid.Root height={600}>
    <VitalGrid.Toolbar showViewControls={grid.features.views} />
    <VitalGrid.Header />
    <VitalGrid.Body />
  </VitalGrid.Root>
</VitalGridProvider>

Key points:

  • You almost always:
    • Call useVitalGrid
    • Wrap with VitalGridProvider
    • Render compound components inside

4. Compound components

VitalGrid exposes a set of React components under the factory namespace.

Common ones:

  • Root
  • Toolbar and Toolbar.Slot
  • Columns, Columns.Left, Columns.Right
  • Header
  • Body
  • BodyCell

These are:

  • Thin wrappers around the table and context
  • Made to be easy to compose
  • A way to express "what my grid looks like" in JSX

Example:

<VitalGrid.Root height={600}>
  <VitalGrid.Toolbar showViewControls={grid.features.views}>
    <VitalGrid.Toolbar.Slot name="left">
      <Button onClick={onRefresh}>Refresh</Button>
    </VitalGrid.Toolbar.Slot>
    <VitalGrid.Toolbar.Slot name="right">
      <Button onClick={onExport}>Export</Button>
    </VitalGrid.Toolbar.Slot>
  </VitalGrid.Toolbar>

  <VitalGrid.Columns>
    <VitalGrid.Columns.Left>
      <VitalGrid.Columns.Select />
      <VitalGrid.Columns.Drag />
    </VitalGrid.Columns.Left>
    <VitalGrid.Columns.Right>
      <VitalGrid.Columns.Add />
    </VitalGrid.Columns.Right>
  </VitalGrid.Columns>

  <VitalGrid.Header />
  <VitalGrid.Body />
</VitalGrid.Root>

Key points:

  • They do not know about Convex or any backend.
  • They talk to the table and context only.
  • You pick and arrange them to fit your UI.

5. Data source: VitalGridDataSource

This is the contract between VitalGrid and your backend.

A data source:

  • Wraps your real API (Convex, REST, GraphQL, localStorage, etc.)

  • Exposes a simple shape:

    • rows:
      • current: T[]
      • create, update, delete
    • columns:
      • current: VitalGridColumn[]
      • create, update, delete
    • views (optional):
      • save, load, all
    • capabilities:
      • supportedFeatures (what this backend can do)
    • getRowId (optional):
      • How to read the row's ID (e.g. _id, id)

VitalGrid includes built-in data sources:

  • Local storage (useLocalStorageDataSource)
  • Dexie/IndexedDB (useDexieDataSource)

Note: The docs-site includes a Convex demonstration example, but Convex is not part of the core library. You can implement your own data source adapter for any backend by implementing the VitalGridDataSource interface.

Key points:

  • All backend-specific logic lives here.
  • useVitalGrid and components treat the data source as a black box.
  • To support a new backend, implement this interface and plug it in.

6. Columns: VitalGridColumn

VitalGridColumn is the standard column shape the grid understands.

It is used by:

  • VitalGridDataSource.columns.current
  • createColumnDef
  • Header, body, and system column logic

Typical fields:

  • id: string
    • Unique ID
    • Also used as accessor key
  • header: string
    • Label shown in header
  • type: string
    • Field type key (links to a field definition)
  • settings?: object
    • Extra config passed to the field component
  • Optional:
    • width, order, pinned, hidden, etc.

Key points:

  • All grid internals use VitalGridColumn.
  • Backend-specific column types are converted to VitalGridColumn in the data source layer.
  • Do not leak backend-specific shapes into your UI code.

7. Fields: cell types and value mapping

Fields define how a column behaves at the cell level.

A field:

  • Knows how to render a value
  • Knows how to edit a value
  • Can map between stored values and UI values with simple helpers

Important idea:

  • toDisplayValue:
    • Turns the stored cell value into something the component uses.
  • toPersistenceValue:
    • Turns user input back into the stored value.

Examples:

  • Text field:
    • Stored: string
    • Display: string
  • Date field:
    • Stored: ISO string
    • Display: Date object or formatted string in the UI
  • Status field:
    • Stored: status key
    • Display: colored badge

Key points:

  • Field logic is UI-level.
  • Do not put backend-specific logic in your fields.
  • Backend mapping belongs in the data source.

8. Feature flags and capabilities

VitalGrid has features like:

  • Sorting
  • Filtering
  • Column resizing
  • Column/row reordering (drag and drop)
  • Virtualization
  • Views
  • Selection
  • Add-column controls

These are controlled in two steps:

  1. Factory config:
    • You choose what you want:
const VitalGrid = createVitalGrid({
  features: {
    sorting: true,
    filtering: true,
    columnResizing: true,
    columnReordering: true,
    rowReordering: true,
    virtualization: true,
    views: true,
    selection: true,
    addColumn: true,
  },
});
  1. Data source capabilities:
    • Each data source declares what it supports.
    • VitalGrid combines:
      • factory features
      • data source capabilities
    • Result is grid.features:
      • Only features that are enabled and supported.

Key points:

  • You do not need to manually hide features the backend cannot handle.
  • Use grid.features in your UI (for example, to show view controls only when views is true).

Putting it together

When you build with VitalGrid, think in this order:

  1. Choose or implement a VitalGridDataSource for your backend.
  2. Create a factory with createVitalGrid:
    • Set features.
    • Register fields.
  3. In your component:
    • Call VitalGrid.useVitalGrid({ dataSource, data, columns }).
    • Wrap with VitalGridProvider.
    • Use the compound components (Root, Toolbar, Header, Body, Columns, etc.).
  4. Let:
    • The data source handle backend details.
    • VitalGridColumn describe columns.
    • Fields handle cell behavior.
    • Features and capabilities drive what is turned on.

If you follow these concepts, you get:

  • A table that is:
    • Virtualized
    • Drag and drop ready
    • Column-dynamic
    • Backend-agnostic
  • With a clear place to put every piece of logic.