Libraries

Blueprint, in your codebase.

Two libraries: the runtime that renders a contract into real UI on the client, and the SDK that fills a contract with your data on the server — shipping per language, Node, Go and Java. No eval, no lock-in; you host both.

@dashforge/blueprint · runtime@dashforge/blueprint-nodeblueprint-goblueprint-java
Overview

The library

@dashforge/blueprint is an open UI contract plus a thin runtime that renders it. JSON goes in, a real screen comes out. The runtime walks each node and maps it to one of your own components — there is no interpreter, no template language, and no eval: the contract describes the screen, the behavior stays in your typed React. That's what keeps a config-driven UI auditable and CSP-clean — the runtime runs no code from the JSON, and one malformed node degrades to an inline fallback instead of taking down the whole tree.

Install

Add it to your app

One package — it bundles both the Tailwind and MUI flavor bindings. Peers: React 19+ and zod 4.

bash
npm i @dashforge/blueprint

Using the MUI flavor? add its runtime too: @mui/material, @emotion/react and @emotion/styled.

Usage

Render a contract

Spread a contract, choose a flavor, and hand the runtime whatever the screen needs. Everything is a prop — the JSON stays pure data.

tsx
import { DashBlueprint } from '@dashforge/blueprint'

export function Screen({ contract, user }) {
  return (
    <DashBlueprint
      {...contract}
      lib="tw"
      forms={{ signin: { onSubmit: (values) => auth(values) } }}
      resolveData={(source) => catalog[source]}
    />
  )
}

What you pass:

  • {...contract} — the { version, root } object you loaded (from a CDN, your backend, or a file). root is the node tree.
  • lib — "tw" or "mui", the flavor its atoms render through. Both ship in the package.
  • forms / slots / customNodes / icons / intl — the escape hatches: where your real, typed React plugs in (submit handlers, custom components, icons, i18n).
  • resolveData — the client-side counterpart of the server SDK: it fills any $data.* list the contract left dynamic. Resolve it on the server with the SDK, or here on the client — your call.

Server or client. <DashBlueprint> is SSR-safe — it touches no browser-only APIs at render, so you can server-render it (Next.js, Remix, renderToString) for the first paint, then hydrate. It's interactive, so in the App Router it lives inside a client boundary ('use client'). One caveat: supply the same intl.t on server and client, or the two renders diverge and hydration mismatches.

The contract

What the JSON holds

A contract is a declarative description of the screen — and nothing else: a version and a root node tree. Each node carries its type (an atom), its props, children, and optional visibility, access and i18n. It never carries logic: no onClick, no eval, no code-from-strings.

json
{
  "version": "1.0",
  "root": {
    "type": "form",
    "nodeId": "signin",
    "children": [
      { "type": "field", "props": { "name": "email", "label": "Email", "type": "email" } },
      { "type": "select", "props": { "name": "role", "options": { "source": "$data.roles" } },
        "visibility": { "field": "$form.mode", "eq": "team" } },
      { "type": "submit", "props": { "label": "Sign in" } }
    ]
  }
}
Escape hatches

Drop in real React

Anything the closed atom catalog doesn't cover, you inject as real, typed React — the wall always has a door. Three hatches, all props on DashBlueprint:

tsx
<DashBlueprint
  {...contract}
  lib="tw"
  customNodes={{ CurrencyField }}
  currency={<CurrencyField name="currency" />}
  slots={{ 'legal-copy': <LegalCopy /> }}
  forms={{ signin: { onSubmit } }}
/>
  • customNodes — for a component the catalog doesn't have. The contract carries a { type: "custom", props: { component: "CurrencyField", … } } node, and your component is mounted with those props. Use this when you control the contract, or the Builder emits a custom node.
  • slots — override an existing node by its nodeId; your element renders in its place, as-is. So if the Builder authored an input whose nodeId is currency, mounting your own field is currency={<CurrencyField name="currency" />} — the camelCase shorthand for slots={{ currency: … }} (use the slots object form for kebab-case ids like legal-copy).
  • forms — bind a form node's onSubmit (also onValuesChange, defaultValues) by its nodeId.

The key is the node's nodeId — its addressing key, not the form name, though they often match. And a slot override renders your element verbatim: for a form field it does not inherit the field's binding, so wire your component to the form yourself — or author the field as a custom node, which receives the node's props.

Design systems

Tailwind or MUI

One contract, either design system. Set lib="tw" and every atom renders through @dashforge/tw; set lib="mui" and the same contract renders through @mui/material. Both flavor packs ship inside @dashforge/blueprint — no separate install, no registration step. There is no Blueprint theme baked into the output: atoms adopt your tokens and components, so a Blueprint screen looks like the rest of your app, not like Blueprint. Write the contract once; switch the flavor with one prop.

Reference

The full surface

Every prop on <DashBlueprint>. Only version and root are required — everything else is opt-in.

PropWhat it does
versionThe contract version — "1.0". Required.
rootThe root node of the contract tree. Required.
lib"tw" or "mui" — the flavor its atoms render through.
customNodesMap a custom node's component id to your React component (it receives the node's props).
formsPer-nodeId form config: onSubmit, plus defaultValues, schema/resolver, mode, onError.
slotsOverride a node by its nodeId with your element (wins over the atom binding).
<camelCaseId>Top-level shorthand for slots — any camelCase prop maps to that nodeId.
iconsIcon registry — maps the icon ids the contract references to render functions (lucide, MUI, custom SVG).
intli18n config (t, locale?, registry?) — resolves $t refs. Absent → keys render literally.
resolveDataResolve a $data.* list binding on the client — the counterpart of the server SDK.
rulesNamed visibility predicates a contract can reference by name.
validationMode"strict" / "lenient". Env-aware default: strict in prod, lenient in dev.
onValidationDiagnosticsFires after every compile with { errors, warnings } — ship it to your logger.
metadataArbitrary passthrough carried alongside the contract.
Build on it

JSON in. Real UI out. Both halves are yours to host.