It always starts the same way. You send a form's fields from the server so the team can change them without a
deploy. Reasonable. Then one field has to show only when another field has a certain value. Then a field depends on
two others. Then validation has to cross-check them. Then some fields are hidden for some roles.
Six months later that payload is full of conditions, dependencies, and branching. It has no types. It has no tests.
Nobody can debug it. On a checkout or KYC screen an auditor opens it and cannot tell what it does. You never sat
down to design a language. You grew one by accident, and it is running in production right now.
Mike Hadlow gave this a name: the Configuration Complexity Clock. Every server-driven UI project sits somewhere on
that clock, and it only ticks one way.
Blueprint stops it. It is an open-source UI-contract system with a self-hosted visual Builder, and it went public
today. Here is how it works, and where it draws the line on purpose.
The one rule: describe the screen, not the behavior
Blueprint is a UI contract. The contract carries structure, meaning which components go where, with what data
bindings, visibility, and access rules. It never carries behavior. Logic stays in typed, testable code. That single
boundary is the entire design.
A contract is plain JSON:
{
"version": "1.0",
"lib": "tw",
"root": {
"type": "form",
"id": "kyc",
"children": [
{ "type": "field", "id": "email" },
{
"type": "field",
"id": "taxId",
"visibleWhen": { "field": "country", "equals": "IT" },
"access": { "resource": "customer.taxId", "action": "read" },
"validation": { "required": true, "pattern": "^IT\\d{11}$" }
}
]
}
}
Three things matter here.
-
visibleWhenis an object, not a function. It is serializable, so it is inspectable, diffable, and a visual tool can read and write it. You give up some expressiveness for that. On regulated screens, that is exactly the trade you want. -
accesslives inside the field contract. It is not a<CanRead>wrapper bolted on at the call site. -
There is no code anywhere in the payload. No expression strings. No
eval. It renders under a strict CSP.
The render pipeline (no eval, ever)
The runtime walks the tree and maps each node's type to a real React component from a closed catalog:
import { DashBlueprint } from "@dashforge/blueprint";
<DashBlueprint
version="1.0"
lib="tw" // or "mui"
root={contract.root} // the JSON above
data={envelope.data} // values, from your backend
rules={policy} // RBAC policy + named rules
validationMode="strict"
/>;
Nothing gets compiled from a string. A node whose type is not in the catalog throws a hard, visible error. It is
not a silent injection point. That is what makes it safe to render server-supplied UI on a payment page.
The escape hatch: custom nodes
A closed catalog would be a cage without a door. The door is custom nodes. You register real, typed React components
and reference them by type:
<DashBlueprint
root={contract.root}
customNodes={{
// your component, your code, your tests
"signature-pad": SignaturePad,
"id-scanner": IdScanner,
}}
/>;
The catalog covers the 90%. Custom nodes cover the rest, as code, where logic belongs. The payload stays data. This
is the line that stops config from ever turning into a language.
RBAC and visibility as data (shared front and back)
access and visibleWhen are part of the contract, and one policy governs both the API route and the field it protects. On the client this is not decoration: a provider loads that same policy from the server together with the current user's access and wires it into every component, so the rule the server enforces on the route is the rule the UI applies at render. You declare access, you do not hand-write guards. The rest is in the repo.
Where Blueprint sits (and where it does not)
Builder ──authors──▶ contract (JSON)
│
Your backend ──fills with data──▶ envelope (contract + data + state)
│
Your frontend (Blueprint runtime) ──renders──▶ real UI
Server-driven UI, and not one line of an interpreter reaches the client.
Now the honest part. Blueprint is not a compliance product. It does not run your business logic. It does not
inventory your third-party scripts. It renders structure and data. It shrinks the surface and governs it. It does
not erase the work.
Builder: author the contract visually
Hand-writing JSON does not scale past a few screens. Builder is a self-hosted visual editor. You drag components,
wire visibleWhen and access, validate live, and export the contract. The runtime is open-source. The Builder is
self-hosted. You clone the repo and run it. Your contracts never leave your infrastructure.
Architecture: https://dashforge-ui.com/blueprint/libraries
Try it
It is self-hosted. No SaaS. No hosted demo. You clone it and run it locally, which is exactly the point:
git clone https://github.com/kensaadi/blueprint
# runtime + Builder both live in this repo. run the Builder locally, render contracts with the runtime
You can also scaffold a fresh React 19 and TypeScript app on Dashforge:
npx dashforge-cli my-app --lib tw
- Repo (runtime + Builder): https://github.com/kensaadi/blueprint
- Docs: https://dashforge-ui.com/blueprint
One trade-off worth arguing about: serializable conditions instead of functions. Less expressive, but inspectable,
diffable, and auditable. On regulated screens that is the whole point. If you have shipped SDUI in production and
hit a wall this does not cover, open an issue on the repo. Edge cases are how the catalog earns its keep.
Top comments (0)