Domphy Adapter

The bridge pattern — a tableVersion counter bumped from onStateChange — is the same wiring in every table. @domphy/table/domphy packages it once: createDomphyTable owns the controlled-state loop and exposes a reactive version you read to re-render.

npm install @domphy/table @domphy/core

@domphy/core is a peer dependency of the adapter, so the main @domphy/table entry stays a dependency-free, byte-identical port. Import from the /domphy subpath:

import { createDomphyTable } from "@domphy/table/domphy"

createDomphyTable

createDomphyTable(options) takes the same options as createTable, but fills in state, onStateChange, and renderFallbackValue for you and wires them to Domphy reactivity. It returns the full table instance plus a reactive version.

import { createColumnHelper, getCoreRowModel, getSortedRowModel, getPaginationRowModel } from "@domphy/table"
import { createDomphyTable } from "@domphy/table/domphy"
import { table as tableUI } from "@domphy/ui"

const { table, version } = createDomphyTable<Person>({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    initialState: { pagination: { pageIndex: 0, pageSize: 5 } },
})

const App: DomphyElement<"table"> = {
    table: (l) => {
        version(l) // re-render on any sort / filter / pagination / selection change
        return [
            {
                thead: table.getHeaderGroups().map((group) => ({
                    tr: group.headers.map((header) => ({
                        th: String(header.column.columnDef.header),
                        onClick: header.column.getToggleSortingHandler(),
                        _key: header.id,
                    })),
                    _key: group.id,
                })),
            },
            {
                tbody: table.getRowModel().rows.map((row) => ({
                    tr: row.getVisibleCells().map((cell) => ({
                        td: String(cell.getValue() ?? ""),
                        _key: cell.id,
                    })),
                    _key: row.id,
                })),
            },
        ]
    },
    $: [tableUI()],
}

Return value

MemberDescription
tableThe full table-core Table instance — every header group, row model, and feature method.
version(l)Reactive change counter. Read it (with the listener) anywhere you render from the instance; it bumps on every state change.
state(l)The current TableState, reactive when a listener is passed.
setState(updater)Forwards to table.setState.
destroy()Releases the version state's listeners.

Why a version counter

A table's rendered output — getRowModel().rows, header sort markers, the page indicator — depends on essentially all of the table state. One coarse version signal that bumps on any change is the right granularity: read version(l) once at the top of the region that renders from the instance, and the whole region reconciles. Domphy patches the DOM in place, so re-deriving rows is cheap and keyed rows keep their nodes.

For controls that depend on a single slice (e.g. a page-size selector), you can read state(l).pagination instead to avoid re-rendering on unrelated changes.

Cleanup

version is a Domphy state; release it when the table's subtree unmounts:

{
    table: (l) => { version(l); return [...] },
    $: [tableUI()],
    _onRemove: () => destroy(),
}

When to use the bridge directly

Use the raw bridge pattern when you need fully-controlled state living in your own model, or per-slice signals instead of one counter. The adapter is built on that exact pattern.