Gesso
Data

Data grid

The database layer, Notion-style — a schema-driven, editable grid over a typed collection.

The data grid is the first piece of Ramson's database layer: the part of Notion that is just data. A collection is a schema (an ordered list of typed properties) plus rows (cell values keyed by property id), and the grid renders and edits that shape directly. Any internal tool that has records behind an API can hand them to this grid and get a Notion-style view with no bespoke table code.

This is front end only. There is no Postgres and no server yet. The collection below is mock data held in React state, and every edit rewrites that state, which is the same read/write shape a real backend would serve later.

ProjectStatusPriorityTagsOwnerDueEffortOn trackBrief
1Ramson UI kit — data layerIn progressHighDesignJSJoeSep 5, 20268ramson.tech/kit
2Washi database GUINot startedHighDesignOpsJSJoeSep 20, 202613Empty
3Winter Shore-Up 2026In progressMediumHomeBQBasquiatOct 15, 20265ramson.tech/winter
4iCreditScore redemptionBlockedHighFinanceOpsDKDickinsonSep 1, 20265Empty
5Outdoor Revival 2026DoneLowHomeBQBasquiatAug 10, 20263Empty
6Subscription auditIn progressMediumFinanceWEWASH-EAug 30, 20262ramson.tech/subs
6 rows

Choice cells (select, multiSelect) render PropertyTag, not role-colored Chip. The hue lives on the option, mixed in cat-colors.ts.

Click a cell to edit it. Pick a status or priority from its menu, toggle On track, edit the tags, and then use the + in the header to add a property or New at the bottom to add a row. Everything updates the one collection object.

The field registry — how it extends

The grid knows nothing about any specific property type. It looks each one up in a field registry, and every type owns three things there: how it draws in a cell, how it is edited, and its icon. Adding a whole new kind of property to the system (the CMS move) is adding one case to PropertyType and one entry to the registry, and nothing else changes.

// registry/data/lib/field-types.tsx
import type { Icon } from '@phosphor-icons/react';

export interface FieldType {
  type: PropertyType;
  label: string;
  icon: Icon;
  emptyValue: CellValue;
  Display: React.FC<FieldDisplayProps>;   // read mode
  Editor?: React.FC<FieldEditorProps>;    // edit mode
}

export const FIELD_TYPES: Record<PropertyType, FieldType> = {
  text: { /* ... */ },
  number: { /* ... */ },
  // add a new type here and the grid can render and edit it
};

The nine types shipped so far: text, number, select, multi-select, status, date, checkbox, url, and person. A person cell picks from a directory the host app supplies, which is the seam a relation or a linked collection grows into.

The data shape

interface PropertyDef {
  id: string;
  name: string;
  type: PropertyType;
  options?: SelectOption[];   // select / multiSelect / status
  numberFormat?: 'plain' | 'integer' | 'currency' | 'percent';
}

interface Row {
  id: string;
  cells: Record<string, CellValue>;   // propertyId -> value
}

interface Collection {
  name: string;
  properties: PropertyDef[];
  rows: Row[];
}

CellValue is a loose union (string | number | boolean | string[] | Person | null). Each field type reads only the shape it owns, and null is the universal empty.

Usage

The grid is controlled. It never mutates in place; it produces the next collection and calls onChange, so the host owns the state (and, later, the persistence).

import { DataGrid } from '@/components/data/data-grid';
import type { Collection } from '@/components/data/lib/types';

function ProjectsView() {
  const [collection, setCollection] = useState<Collection>(initial);
  return (
    <DataGrid
      collection={collection}
      onChange={setCollection}
      people={team}
    />
  );
}

Accessibility and the honest edges

  • The grid uses role="grid" with row and cell roles, cells are focusable, and Enter opens the editor.
  • Focus-visible rings, forced-colors-safe outlines, and a real role="checkbox" on the boolean cell.
  • Choice editors use a small dependency-free popover that closes on outside click or Escape. When the kit gains a Radix Popover primitive these move onto it and gain full focus management. Columns do not resize or stick yet, and property config (rename, retype, recolor options) is not built.
  • Tag color is the one categorical palette the data layer owns, mixed off an oklch anchor against the live surfaces so it stays legible in light and dark with no .dark variant.

Roadmap

  1. Views on the same collection: board (group by a select), gallery, list.
  2. Property config: rename, change type, add and recolor options, reorder columns.
  3. Filter, sort, and search over the collection.
  4. Relations and rollups (the person directory generalizes into linked collections).
  5. The Postgres schema and the API that serializes to and from Collection.

Install

shadcn add @gesso/data-grid

On this page