Best Boilerplates for Building No-Code/Low-Code Platforms 2026
·StarterPick Team
no-codelow-codedrag-and-dropreact-flowcraft-js2026
TL;DR
Building a no-code/low-code platform is a complex UI engineering challenge — the right choice depends on what type of builder you're making. For workflow/logic builders: React Flow is the standard (150K+ downloads/week, used by Retool, Linear). For drag-and-drop UI builders: Craft.js (page builders) or GrapesJS (website builders). For form builders: React JSON Schema Form or Formik-based custom builders. There's no all-in-one boilerplate — combine these primitives.
Key Takeaways
- React Flow: Node-based workflow/graph editors (like n8n, Retool logic flow)
- Craft.js: React drag-and-drop page builder (like landing page or email builders)
- GrapesJS: Full website/email builder with HTML output (non-React)
- JSONForms: Form builder from JSON Schema (good for admin panels)
- No single boilerplate: Pick the right primitive library for your type of builder
- 2026 trend: AI-powered no-code (natural language → workflow) emerging
Builder Types and Their Libraries
Type → Library → Use Case
─────────────────────────────────────────────────
Workflow/Logic → React Flow → n8n-like, pipeline builders
Page/UI builder → Craft.js → Landing page, email template
Website builder → GrapesJS → Full website editor
Form builder → JSON Forms → Dynamic form generation
Dashboard builder → GridStack.js → Widget/dashboard layout
Query builder → React QueryBuilder → Filter/search UI builders
React Flow: Workflow Builders
npm install @xyflow/react
// Minimal workflow builder:
'use client';
import { ReactFlow, Background, Controls, MiniMap,
addEdge, useNodesState, useEdgesState } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { useCallback } from 'react';
const initialNodes = [
{ id: '1', type: 'input', data: { label: 'Start' }, position: { x: 100, y: 100 } },
{ id: '2', data: { label: 'Process' }, position: { x: 300, y: 100 } },
{ id: '3', type: 'output', data: { label: 'End' }, position: { x: 500, y: 100 } },
];
const initialEdges = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
export function WorkflowBuilder() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[]
);
return (
<div style={{ width: '100%', height: '600px' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
</div>
);
}
// Custom node types — the key to a real builder:
import { Handle, Position, type NodeProps } from '@xyflow/react';
interface EmailNodeData {
label: string;
to: string;
subject: string;
onUpdate: (data: Partial<EmailNodeData>) => void;
}
export function EmailNode({ data, selected }: NodeProps<EmailNodeData>) {
return (
<div className={`bg-white border-2 rounded-lg p-4 min-w-[200px] ${
selected ? 'border-blue-500' : 'border-gray-200'
}`}>
<Handle type="target" position={Position.Left} />
<div className="flex items-center gap-2 mb-3">
<span>📧</span>
<span className="font-semibold">{data.label}</span>
</div>
<input
className="w-full border rounded px-2 py-1 text-sm mb-2"
placeholder="To: {{user.email}}"
value={data.to}
onChange={(e) => data.onUpdate({ to: e.target.value })}
/>
<input
className="w-full border rounded px-2 py-1 text-sm"
placeholder="Subject"
value={data.subject}
onChange={(e) => data.onUpdate({ subject: e.target.value })}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
// Node palette — drag from sidebar to canvas:
const nodeTypes = {
email: EmailNode,
sms: SmsNode,
delay: DelayNode,
condition: ConditionNode,
webhook: WebhookNode,
};
// Full workflow builder with palette:
export function WorkflowBuilderFull() {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onDrop = useCallback((event: DragEvent) => {
const type = event.dataTransfer.getData('application/reactflow');
const position = { x: event.clientX, y: event.clientY };
const newNode = { id: nanoid(), type, data: { label: type }, position };
setNodes((nds) => [...nds, newNode]);
}, []);
return (
<div className="flex h-screen">
{/* Sidebar palette */}
<NodePalette />
{/* Canvas */}
<div className="flex-1" onDrop={onDrop} onDragOver={(e) => e.preventDefault()}>
<ReactFlow nodes={nodes} edges={edges}
onNodesChange={onNodesChange} onEdgesChange={onEdgesChange}
nodeTypes={nodeTypes}>
<Background />
<Controls />
</ReactFlow>
</div>
</div>
);
}
Craft.js: Page Builder
npm install @craftjs/core
// Craft.js page builder — the core pattern:
import { Editor, Frame, Element, useNode, useEditor } from '@craftjs/core';
// Draggable, configurable text component:
const Text = ({ text, fontSize, color }: any) => {
const { connectors: { connect, drag }, selected } = useNode((state) => ({
selected: state.events.selected,
}));
return (
<p
ref={(ref) => connect(drag(ref!))}
style={{ fontSize, color, cursor: 'move', outline: selected ? '2px solid blue' : 'none' }}
>
{text}
</p>
);
};
// Define the settings panel for this component:
Text.craft = {
displayName: 'Text',
props: { text: 'Hello World', fontSize: 16, color: '#000000' },
related: {
toolbar: () => (
<div>
<label>Text</label>
<input type="text" /> {/* Connected to component props */}
<label>Font Size</label>
<input type="number" />
</div>
),
},
};
// The builder UI:
export function PageBuilder() {
return (
<Editor resolver={{ Text, Container, Image }}>
<div className="flex h-screen">
{/* Component palette */}
<Sidebar />
{/* Canvas */}
<div className="flex-1 bg-gray-100 p-8">
<Frame>
<Element is="div" canvas>
<Text text="Click to edit" fontSize={16} color="#333" />
</Element>
</Frame>
</div>
{/* Properties panel */}
<SettingsPanel />
</div>
</Editor>
);
}
JSON Schema Form Builder
npm install @rjsf/core @rjsf/utils @rjsf/validator-ajv8
// Dynamic form from JSON Schema:
import Form from '@rjsf/core';
import validator from '@rjsf/validator-ajv8';
const schema = {
title: 'Contact Form',
type: 'object',
required: ['firstName', 'email'],
properties: {
firstName: {
type: 'string',
title: 'First Name',
},
email: {
type: 'string',
title: 'Email',
format: 'email',
},
plan: {
type: 'string',
title: 'Plan',
enum: ['free', 'pro', 'enterprise'],
enumNames: ['Free', 'Pro', 'Enterprise'],
},
},
};
const uiSchema = {
plan: { 'ui:widget': 'radio' },
email: { 'ui:placeholder': 'your@email.com' },
};
export function DynamicForm({ schema, onSubmit }: any) {
return (
<Form
schema={schema}
uiSchema={uiSchema}
validator={validator}
onSubmit={({ formData }) => onSubmit(formData)}
/>
);
}
Architecture: Saving Builder State
// Storing no-code builder state in DB:
// All builders (React Flow, Craft.js, etc.) produce JSON state
// Store as JSONB in Postgres
// Prisma schema:
// model Workflow {
// id String @id @default(cuid())
// userId String
// name String
// nodes Json ← React Flow nodes array
// edges Json ← React Flow edges array
// createdAt DateTime @default(now())
// }
// Saving workflow:
async function saveWorkflow(userId: string, name: string, flowData: any) {
const { nodes, edges } = flowData;
await db.workflow.upsert({
where: { id: flowData.id },
create: { userId, name, nodes, edges },
update: { name, nodes, edges, updatedAt: new Date() },
});
}
// Loading workflow:
async function loadWorkflow(workflowId: string) {
const workflow = await db.workflow.findUnique({
where: { id: workflowId },
});
return {
nodes: workflow?.nodes as Node[],
edges: workflow?.edges as Edge[],
};
}
Decision Guide
Building a workflow/automation tool (like n8n, Zapier):
→ React Flow (@xyflow/react)
→ 150K+ downloads/week, battle-tested
→ Custom node types for each integration
Building a landing page / email template builder:
→ Craft.js for React-based builder
→ GrapesJS for HTML-output website builder
→ Unlayer for email builder specifically
Building a form builder:
→ React JSON Schema Form (@rjsf/core)
→ Formbuilder.js for drag-and-drop form UX
→ Tripetto or Feathery for complex conditional logic
Building a dashboard builder:
→ GridStack.js for resizable widget layouts
→ Tremor or React Grid Layout for simpler dashboards
Building a database/spreadsheet UI (like Airtable):
→ AG Grid (enterprise) or TanStack Table (free)
→ Baserow (open source Airtable clone) to fork
Find no-code/low-code platform boilerplates at StarterPick.