next-headless-page-builder
v1.0.1
Published
Headless drag-and-drop page builder for React and Next.js
Maintainers
Readme
next-headless-page-builder
A headless, fully-featured drag-and-drop page builder for React and Next.js. Drop it into any project as a dependency — no framework lock-in, no mandatory UI opinions.
Features
- 🧩 Component registry — register your own components or use the built-ins
- 🖱️ Drag & drop — powered by dnd-kit with nested containers and drop indicators
- 📱 Responsive styles — per-device (desktop / tablet / mobile) style overrides
- ↩️ Undo / redo — full document history via Zustand + Immer
- 🎨 Theme system — CSS variable-based theming
- 📤 Export — JSON, HTML, React component, or Next.js page
- 🧱 Layered architecture — use just the store + registry headlessly, or drop in the full
<BuilderShell />
Installation
npm install next-headless-page-builder
# or
pnpm add next-headless-page-builderThe package has peer dependencies on react and react-dom ≥ 18.
Quick start — full builder UI
1. Add the CSS
Copy node_modules/next-headless-page-builder/src/globals.css into your project (or import it directly) and make sure Tailwind CSS v4 is configured in your app.
// app/globals.css or your root CSS file
@import "next-headless-page-builder/src/globals.css";2. Wrap your app with the provider
// app/layout.tsx (Next.js App Router)
import { BuilderProvider } from "next-headless-page-builder";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<BuilderProvider>{children}</BuilderProvider>
</body>
</html>
);
}3. Render the builder
// app/builder/page.tsx
"use client";
import { BuilderShell } from "next-headless-page-builder";
// Register built-in components once
import "next-headless-page-builder"; // side-effect: calls registerBuiltins()
export default function BuilderPage() {
return <BuilderShell />;
}Headless usage — store + registry only
If you want to drive the builder entirely with your own UI, use the store and registry directly:
"use client";
import { useEditorStore } from "next-headless-page-builder/store";
import {
registerComponent,
getAllComponents,
} from "next-headless-page-builder/registry";
import { exportHTML } from "next-headless-page-builder/exporter";
// Register a custom component
registerComponent({
type: "my-card",
label: "Card",
icon: "Box",
category: "basic",
acceptsChildren: true,
defaultStyles: { desktop: { padding: "24px", border: "1px solid #e2e8f0" } },
render: ({ style, children }) => <div style={style}>{children}</div>,
});
export function MyBuilder() {
const document = useEditorStore((s) => s.document);
const addComponent = useEditorStore((s) => s.addComponent);
const handleExport = () => {
const html = exportHTML(document);
console.log(html);
};
return (
<div>
<button onClick={() => addComponent("my-card")}>Add Card</button>
<button onClick={handleExport}>Export HTML</button>
{/* your own canvas / tree rendering here */}
</div>
);
}Custom components
A ComponentDefinition is the minimal contract:
import { registerComponent } from "next-headless-page-builder/registry";
registerComponent({
type: "badge",
label: "Badge",
icon: "Star", // any key from lucide-react icon map
category: "basic", // "basic" | "layout" | "advanced" | "form"
acceptsChildren: false,
defaultContent: { text: "New" },
defaultStyles: {
desktop: {
display: "inline-block",
padding: "4px 12px",
borderRadius: "999px",
backgroundColor: "var(--pb-primary)",
color: "#fff",
fontSize: "12px",
},
},
settingsSchema: [
{ type: "text", key: "text", label: "Label", target: "content" },
{ type: "color", key: "backgroundColor", label: "Background", target: "style" },
],
render: ({ node, style }) => (
<span style={style}>{node.content?.text ?? "Badge"}</span>
),
});Export formats
import { exportAs } from "next-headless-page-builder/exporter";
const json = exportAs(document, "json"); // serialized BuilderDocument
const html = exportAs(document, "html"); // standalone HTML file
const react = exportAs(document, "react"); // React component string
const nextPage = exportAs(document, "next"); // Next.js page stringSub-path imports (tree-shaking)
| Import path | What you get |
| ------------------------------------- | -------------------------------- |
| next-headless-page-builder | Everything |
| next-headless-page-builder/store | Zustand stores only |
| next-headless-page-builder/registry | Component registry + built-ins |
| next-headless-page-builder/exporter | Export utilities |
| next-headless-page-builder/renderer | StaticNode, resolveStyles |
| next-headless-page-builder/theme | defaultTheme, themeToCssVars |
| next-headless-page-builder/types | TypeScript types only |
Tailwind CSS requirement
The builder UI (BuilderShell and all panel components) uses Tailwind CSS v4 utility classes. Your host project must have Tailwind configured and the package's source included in Tailwind's content paths:
// tailwind.config.ts
export default {
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/next-headless-page-builder/dist/**/*.js", // ← add this
],
};Architecture
src/
├── types/ — BuilderNode, ComponentDefinition, Theme, etc.
├── store/ — Zustand editor store (document, selection, history)
│ ├── editor-store.ts
│ ├── template-store.ts
│ └── history-store.ts
├── registry/ — Component registry (register / lookup / render)
├── lib/
│ ├── exporter/ — JSON / HTML / React / Next.js export
│ ├── renderer/ — StaticNode (server-safe), resolveStyles
│ ├── theme.ts — defaultTheme + CSS var helpers
│ └── utils/ — tree operations, content helpers
├── features/
│ ├── editor/
│ │ ├── canvas/ — Canvas, NodeRenderer, SelectionToolbar
│ │ └── components/ — Built-in component definitions
│ ├── drag-drop/ — DropZone, collision strategy
│ └── templates/ — Preset section templates
├── components/
│ ├── builder/ — BuilderShell, Toolbar, Sidebar, panels...
│ └── ui/ — shadcn/ui primitives
└── providers/ — BuilderProvider (theme, DnD, query, toast)