Skip to content

SurveyJS Form Builder in Next.js - Add sample app #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions blogpost-apps/nextjs-form-builder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
27 changes: 27 additions & 0 deletions blogpost-apps/nextjs-form-builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SurveyJS Form Builder in Next.js

This repository contains the source code for a complete tutorial on integrating the [SurveyJS Form Builder](https://surveyjs.io/open-source) with a Next.js application.

## Features

* SurveyJS Form Builder (drag-and-drop interface)
* Live preview with PDF export
* Save/load survey definitions from a local storage
* Custom Creator theme

## Related Blog Post

Read the full tutorial here: [Build Dynamic Forms with SurveyJS Form Builder in Next.js](http://surveyjs.io/stay-updated/blog/nextjs-form-builder).

## Getting Started

```
npm install
npm run dev
```

Visit http://localhost:3000 to start using the builder.

## See Also

For more advanced examples and backend integration options, check out: [SurveyJS + Next.js Quickstart Template](https://github.com/surveyjs/surveyjs-nextjs).
16 changes: 16 additions & 0 deletions blogpost-apps/nextjs-form-builder/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;
7 changes: 7 additions & 0 deletions blogpost-apps/nextjs-form-builder/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
};

export default nextConfig;
35 changes: 35 additions & 0 deletions blogpost-apps/nextjs-form-builder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "surveyjs-form-builder",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@tailwindcss/postcss": "^4.1.11",
"ace-builds": "^1.43.2",
"next": "15.4.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"survey-core": "2.2.5",
"survey-creator-core": "2.2.5",
"survey-creator-react": "2.2.5",
"survey-pdf": "2.2.5",
"survey-react-ui": "2.2.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"autoprefixer": "^10.4.21",
"eslint": "^9",
"eslint-config-next": "15.4.1",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.11",
"typescript": "^5"
}
}
5 changes: 5 additions & 0 deletions blogpost-apps/nextjs-form-builder/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};

export default config;
1 change: 1 addition & 0 deletions blogpost-apps/nextjs-form-builder/public/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions blogpost-apps/nextjs-form-builder/public/globe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions blogpost-apps/nextjs-form-builder/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions blogpost-apps/nextjs-form-builder/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions blogpost-apps/nextjs-form-builder/public/window.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
44 changes: 44 additions & 0 deletions blogpost-apps/nextjs-form-builder/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@import "tailwindcss";

:root {
--background: #ffffff;
--foreground: #171717;
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

html,
body {
max-width: 100vw;
overflow-x: hidden;
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
}

a {
color: inherit;
text-decoration: none;
}

@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
32 changes: 32 additions & 0 deletions blogpost-apps/nextjs-form-builder/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
{children}
</body>
</html>
);
}
167 changes: 167 additions & 0 deletions blogpost-apps/nextjs-form-builder/src/app/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
.page {
--gray-rgb: 0, 0, 0;
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);

--button-primary-hover: #383838;
--button-secondary-hover: #f2f2f2;

display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
min-height: 100svh;
padding: 80px;
gap: 64px;
font-family: var(--font-geist-sans);
}

@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);

--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}

.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}

.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}

.main li:not(:last-of-type) {
margin-bottom: 8px;
}

.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}

.ctas {
display: flex;
gap: 16px;
}

.ctas a {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: 1px solid transparent;
transition:
background 0.2s,
color 0.2s,
border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}

a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}

a.secondary {
border-color: var(--gray-alpha-200);
min-width: 158px;
}

.footer {
grid-row-start: 3;
display: flex;
gap: 24px;
}

.footer a {
display: flex;
align-items: center;
gap: 8px;
}

.footer img {
flex-shrink: 0;
}

/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}

a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}

.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}

@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}

.main {
align-items: center;
}

.main ol {
text-align: center;
}

.ctas {
flex-direction: column;
}

.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}

a.secondary {
min-width: auto;
}

.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}

@media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
}
Loading
Loading