Skip to content
Domphy

Integrations

Domphy works with most JavaScript/TypeScript libraries because it keeps a strict boundary:

  • UI is just declarative element objects rendered by ElementNode.
  • Reactivity is subscription-based (listener) and can be connected to any external store/event source.
  • State architecture is not owned by Domphy.

See related references:

Why It Works With Most JS/TS Libraries

Domphy does not force a global app model, router model, or data cache model.
If a library can notify changes, it can drive Domphy UI.

// External store -> Domphy listener
store.subscribe(() => listener())

// External stream -> Domphy listener
stream.subscribe(() => listener())

This keeps integration simple: use your preferred state architecture, and only bridge updates at the view edge.

Boundary Rule: Data and UI Must Stay Separate

Domphy does not encourage "plugin islands" for framework-level behavior, especially plugins that combine data/state orchestration with UI behavior.

Why:

  • It increases abstraction layers with little practical value.
  • It blurs the boundary between data flow and presentation.
  • It makes long-term maintenance harder, especially across teams.
  1. Keep data/state in the external library (query client, router, store, stream, etc.).
  2. Convert only the minimum needed signals into Domphy-reactive reads at render points.
  3. Keep patch/component code focused on presentation and interaction.

Domphy package, or vanilla?

Domphy ships first-party packages for capabilities that benefit from tight Domphy-reactivity adapters. Everything else you use vanilla, directly — Domphy's lifecycle hooks (_onMount/_onRemove) integrate imperative DOM libraries cleanly, with no virtual DOM fighting them.

NeedUse
async data / tables / routing / virtualization / forms@domphy/query · @domphy/table · @domphy/router · @domphy/virtual · @domphy/form
i18n@domphy/i18n — reactive i18next wrapper with typed keys
drag & drop@domphy/dnd
animationthe motion() patch (@domphy/ui)
charts@domphy/chart — line, bar, pie, scatter, radar, heatmap, candlestick, boxplot, gauge, treemap, funnel, sankey, graph; or vanilla Chart.js / ECharts / D3 via lifecycle hooks
rich textvanilla TipTap / ProseMirror / Lexical (framework-agnostic cores)
carouselvanilla embla-carousel (its core is framework-agnostic)
datesvanilla dayjs / date-fns / flatpickr
schema validationvanilla zod (works with @domphy/form via Standard Schema) — recipe
maps / 3Dvanilla leaflet/maplibre for maps; @domphy/three for 3D — declarative three.js scene graph (1-1 @react-three/fiber core port)
iconsany SVG string + the icon() patch (e.g. lucide icons)

If a library has a framework-agnostic core (most do — the "React" version is usually a thin wrapper), use that core. No wrapper needed.

DOM library pattern

The canonical way to mount any imperative DOM library:

{
  div: null, // the library's mount target
  _onMount: (node) => {
    const instance = new SomeLib(node.domElement, options)
    node.setMetadata("lib", instance)
  },
  _onRemove: (node) => {
    (node.getMetadata("lib") as SomeLib | undefined)?.destroy()
  },
}

When the library mutates the DOM itself (e.g. a drag-sort plugin reorders nodes), sync Domphy's logical tree without re-touching the DOM using node.children.move(from, to, /* updateDom */ false). React can't do this — its virtual DOM must own the tree; Domphy keeps tree and DOM in sync independently.

Examples