@dmitryvim/form-builder
v0.2.15
Published
A reusable JSON schema form builder library
Downloads
481
Maintainers
Readme
Form Builder
JSON Schema → Dynamic Forms → Structured Output
A comprehensive, zero-dependency form generation library that converts JSON Schema v0.3 into dynamic, interactive HTML forms with advanced file handling, real-time validation, internationalization, and extensive field type support.
Live Demo
Try it now: https://picazru.github.io/form-builder/dist/index.html
Quick Start
CDN Integration
<!-- Embed via iframe -->
<iframe
src="https://picazru.github.io/form-builder/dist/index.html"
width="100%"
height="600px"
frameborder="0"
></iframe>
<!-- With custom schema -->
<iframe
src="https://picazru.github.io/form-builder/dist/index.html?schema=BASE64_SCHEMA"
width="100%"
height="600px"
></iframe>NPM Installation
npm install @dmitryvim/form-builderTypeScript Setup
This package ships with complete TypeScript definitions. Ensure your tsconfig.json is configured for modern module resolution:
{
"compilerOptions": {
"moduleResolution": "NodeNext", // or "Bundler" for Vite/Webpack
"esModuleInterop": true,
"skipLibCheck": false
}
}No need for custom .d.ts files - types are auto-resolved from the package at dist/types/index.d.ts.
Core Features
- 🎯 Schema-driven forms: JSON Schema v0.3 → Interactive forms with live preview
- 📁 Advanced file handling: Images, videos, documents with drag-and-drop and grid preview
- ✅ Real-time validation: Client-side validation with visual feedback and error display
- 🌍 Internationalization: Built-in English/Russian support with extensible translation system
- 🎨 Rich field types: Text, textarea, number, select, file, files, and nested groups
- 👁️ Read-only mode: Display form data without editing capabilities
- 🔘 Action buttons: Configurable buttons in readonly mode for custom interactions
- 💾 Draft saving: Save incomplete forms without validation
- 🔧 Framework agnostic: Works with any web stack (React, Vue, Angular, vanilla JS)
- 📦 Zero dependencies: Self-contained HTML/CSS/JavaScript
- 📱 Responsive design: Mobile-friendly with Tailwind CSS styling
- 🏗️ Instance-based architecture: Multiple independent forms with isolated state
New in v0.2.0
- 🔄 onChange Events: Real-time change notifications with debouncing (eliminates polling)
- 🎨 CSS Theming: 43 configurable theme properties (no
!importantoverrides) - 📝 Partial Updates: Update fields without re-rendering (
setFormData,updateField) - ⚡ Async Thumbnails: Dynamic thumbnail loading with async-only
getThumbnail
Impact: Eliminates ~280+ lines of workaround code for Klein integration
Quick Examples
Root-Level Properties (v0.2.9+)
The root schema now supports container-like properties:
columns- Grid layout for root-level fields (1-4 columns)prefillHints- Quick-fill templates for entire form
Example:
{
"version": "0.3",
"title": "Contact Form",
"columns": 2,
"prefillHints": [
{"label": "Work", "values": {"email": "[email protected]"}, "icon": "💼"},
{"label": "Personal", "values": {"email": "[email protected]"}, "icon": "🏠"}
],
"elements": [
{"type": "text", "key": "name", "label": "Name"},
{"type": "text", "key": "email", "label": "Email"}
]
}See CLAUDE.md for complete documentation.
Simple Contact Form
{
"title": "Contact Form",
"elements": [
{
"type": "text",
"key": "name",
"label": "Full Name",
"required": true,
"minLength": 2,
"maxLength": 50
},
{
"type": "textarea",
"key": "message",
"label": "Message",
"placeholder": "Your message here...",
"required": true,
"rows": 4
},
{
"type": "files",
"key": "attachments",
"label": "Attachments",
"description": "Upload supporting documents or images",
"maxCount": 3,
"maxSizeMB": 10,
"accept": {
"extensions": ["pdf", "jpg", "png", "docx"]
}
}
]
}Advanced Product Form with Actions
{
"title": "Product Registration",
"elements": [
{
"type": "file",
"key": "mainImage",
"label": "Product Image",
"required": true,
"accept": {
"extensions": ["jpg", "png", "webp"]
},
"actions": [
{ "key": "enhance", "label": "Enhance Quality" },
{ "key": "crop", "label": "Auto Crop" },
{ "key": "retry", "label": "Try Again" }
]
},
{
"type": "group",
"key": "specifications",
"label": "Product Specifications",
"repeat": { "min": 1, "max": 10 },
"elements": [
{
"type": "text",
"key": "name",
"label": "Specification Name",
"required": true
},
{
"type": "text",
"key": "value",
"label": "Value",
"required": true
}
]
}
]
}Integration Methods
1. NPM Package (Recommended)
npm install @dmitryvim/form-builder// ES6 imports
import { createFormBuilder } from "@dmitryvim/form-builder";
// Create form instance with v0.3.0 features
const formBuilder = createFormBuilder({
uploadFile: async (file) => {
// Your upload logic - return resource ID
return "resource-123";
},
downloadFile: (resourceId, fileName) => {
// Handle file download
window.open(`/api/files/${resourceId}`, "_blank");
},
getThumbnail: async (resourceId) => {
// v0.2.0: Async-only thumbnail loading
const url = await fetchThumbnailUrl(resourceId);
return url;
},
actionHandler: (value, key, field) => {
// Handle action button clicks
console.log("Action clicked:", { value, key, field });
},
// v0.2.0: Real-time change events
onChange: (formData) => {
console.log("Form changed:", formData);
if (formData.valid) autoSave(formData.data);
},
// v0.2.0: CSS theming
theme: {
primaryColor: "#0066cc",
borderRadius: "4px",
},
});
// Render form
const rootElement = document.getElementById("form-container");
formBuilder.renderForm(rootElement, schema, prefillData);
// Get form data
const result = formBuilder.getFormData();
if (result.valid) {
console.log("Form data:", result.data);
}
// v0.2.0: Update fields without re-rendering
formBuilder.updateField("email", "[email protected]");
formBuilder.setFormData({ name: "John", email: "[email protected]" });2. CDN Integration
<!-- Direct script include (npm CDN) -->
<script src="https://cdn.jsdelivr.net/npm/@dmitryvim/form-builder@latest/dist/form-builder.js"></script>
<script>
const { createFormBuilder } = window.FormBuilder;
const formBuilder = createFormBuilder({
uploadFile: async (file) => "resource-id",
downloadFile: (resourceId) => {
/* download */
},
});
const rootElement = document.getElementById("form-container");
formBuilder.renderForm(rootElement, schema);
</script>
<!-- Or use our S3 CDN -->
<script src="https://picaz-form-builder.website.yandexcloud.net/form-builder/latest/form-builder.js"></script>
<!-- Embed complete demo -->
<iframe
src="https://picaz-form-builder.website.yandexcloud.net/form-builder/latest/index.html"
width="100%"
height="600px"
frameborder="0"
>
</iframe>3. Self-Hosted Deployment
Download and serve the dist/ folder contents.
See Integration Guide for detailed setup instructions.
Complete Feature Set
Field Types
- Text: Single-line with pattern validation, length limits
- Textarea: Multi-line with configurable rows
- Number: Numeric input with min/max/step/decimals
- Select: Dropdown with options and default values
- Colour: Colour picker with hex values (single or palette)
- Slider: Range slider with linear/exponential scales (v0.2.7+)
- File: Single file upload with preview and type restrictions
- Files: Multiple file upload with grid layout and drag-and-drop
- Container: Nested containers with repeatable array support
File Handling
- Supported formats: Images (jpg, png, gif, webp), Videos (mp4, webm, mov), Documents (pdf, docx, etc.)
- Preview system: Thumbnails for images, video players, document icons
- Drag-and-drop: Visual feedback and multi-file support
- Validation: File size, type, and count restrictions
- Resource management: Metadata tracking and automatic cleanup
Validation & UX
- Real-time validation: As-you-type with visual feedback
- Schema validation: Comprehensive error reporting
- Internationalization: English/Russian built-in, extensible
- Tooltips: Field descriptions and hints
- Responsive design: Mobile-friendly interface
- Read-only mode: Display data without editing
- Action buttons: Custom buttons in readonly mode with configurable handlers
API Reference
Core Methods
import { createFormBuilder } from '@dmitryvim/form-builder';
// Create form instance
const form = createFormBuilder({
// Required handlers
uploadFile: async (file: File) => string, // Upload file, return resource ID
downloadFile: (resourceId: string, fileName: string) => void,
// Optional handlers
getThumbnail?: async (resourceId: string) => string | null, // v0.2.0: Async-only, for preview thumbnails
getDownloadUrl?: (resourceId: string) => string | null, // Optional: URL for downloading full file
actionHandler?: (value: any, key: string, field: any) => void,
// v0.2.0: onChange events
onChange?: (formData: FormResult) => void,
onFieldChange?: (fieldPath: string, value: any, formData: FormResult) => void,
debounceMs?: number, // Default: 300ms
// v0.2.4: Verbose error logging
verboseErrors?: boolean, // Default: false (memory leak warnings, validation details)
// v0.2.0: CSS theming
theme?: {
primaryColor?: string,
borderRadius?: string,
fontSize?: string,
fontFamily?: string,
// ... 39 more theme properties
}
});
// Render form
form.renderForm(rootElement, schema, prefillData);
// Get form data
const result = form.getFormData(); // { valid, errors, data }
// v0.2.0: Update data without re-rendering
form.setFormData({ name: 'John', email: '[email protected]' });
form.updateField('email', '[email protected]');
form.updateField('address.city', 'New York'); // Nested paths
// Switch modes
form.setMode('readonly'); // or 'edit'
// Cleanup
form.destroy();Migration from v0.1.x
Breaking Changes in v0.2.0:
- Instance-Only API - Global static methods removed
// OLD (v0.1.x)
FormBuilder.configure({ uploadFile: async (file) => "id" });
FormBuilder.renderForm(root, schema);
// NEW (v0.2.0)
const form = createFormBuilder({ uploadFile: async (file) => "id" });
form.renderForm(root, schema);- Async getThumbnail - Must return Promise
// OLD (v0.1.x)
getThumbnail: (resourceId) => `/thumbs/${resourceId}.jpg`;
// NEW (v0.2.0)
getThumbnail: async (resourceId) => `/thumbs/${resourceId}.jpg`;See CHANGELOG.md for complete migration details.
Theming
Customize appearance with 43 theme properties:
const form = createFormBuilder({
theme: {
// Colors
primaryColor: "#0066cc",
borderColor: "#e0e0e0",
errorColor: "#d32f2f",
// Typography
fontSize: "16px",
fontFamily: '"Roboto", sans-serif',
// Spacing
borderRadius: "4px",
inputPaddingX: "12px",
// Buttons
buttonBgColor: "#0066cc",
buttonTextColor: "#ffffff",
},
});Most Common Properties:
primaryColor- Primary brand colorborderRadius- Border radius for inputs/buttonsfontSize- Base font sizefontFamily- Font familyborderColor- Input border color
No !important overrides needed. See CHANGELOG.md for all 43 properties.
File Handling
The form builder provides flexible file handling with separate hooks for uploads, downloads, and previews:
const form = createFormBuilder({
// Required: Upload handler
uploadFile: async (file) => {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
const data = await response.json();
return data.resourceId; // Return unique resource ID
},
// Required: Download handler
downloadFile: (resourceId, fileName) => {
// Option 1: Direct download
window.open(`/api/files/${resourceId}/download`, "_blank");
// Option 2: Programmatic download
fetch(`/api/files/${resourceId}/download`)
.then((response) => response.blob())
.then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
});
},
// Optional: Thumbnail URLs for preview (async-only in v0.2.0)
getThumbnail: async (resourceId) => {
const response = await fetch(`/api/files/${resourceId}/thumbnail`);
const data = await response.json();
return data.thumbnailUrl; // Return URL or null
},
// Optional: Full file download URL (used instead of getThumbnail for downloads)
getDownloadUrl: (resourceId) => {
return `/api/files/${resourceId}/download`;
},
});Handler Priority for Downloads:
When a user clicks the download button in readonly mode, the form builder uses this priority:
getDownloadUrl- If provided, this URL is used for downloadsgetThumbnail- IfgetDownloadUrlis not provided, falls back to thumbnail URLdownloadFile- If neither URL hook is provided, calls the download handler
Use Cases:
- Separate thumbnail and download URLs: Provide both
getThumbnail(for optimized previews) andgetDownloadUrl(for full files) - Same URL for both: Provide only
getThumbnailif thumbnail URL is also the download URL - Programmatic download: Provide only
downloadFilefor custom download logic
Important Note on Async Handlers (v0.2.4):
getThumbnail must be async (return Promise). TypeScript enforces this at compile-time. If you provide a synchronous handler, the error will occur at first file usage (not at form instantiation). This is intentional - we avoid validation calls that would trigger unnecessary API requests. Follow the FAIL FAST principle: errors surface naturally at usage.
Conditional Field Visibility
Show or hide fields based on form data values using the enableIf property:
interface EnableCondition {
key: string; // Field key to check (supports nested paths)
equals?: any; // Value to compare (initial operator)
scope?: "relative" | "absolute"; // v0.2.9: Evaluation context (default: "relative")
// Future: notEquals, in, exists, greaterThan, lessThan, and/or
}Scope Behavior (v0.2.9+):
scope: "relative"(default): Checks fields within current containerscope: "absolute": Checks fields in root-level form data- Use absolute scope when referencing root-level fields from inside containers
Simple Condition:
{
"type": "select",
"key": "background_mode",
"label": "Background Mode",
"options": [
{ "value": "use_color", "label": "Solid Color" },
{ "value": "use_image", "label": "Image" }
]
},
{
"type": "text",
"key": "background_image",
"label": "Background Image URL",
"enableIf": {
"key": "background_mode",
"equals": "use_image"
}
}Nested Path:
{
"type": "text",
"key": "city_details",
"label": "City-Specific Information",
"enableIf": {
"key": "address.city",
"equals": "New York"
}
}Array Indices:
{
"type": "text",
"key": "first_item_note",
"label": "Note for First Item",
"enableIf": {
"key": "items[0].quantity",
"equals": 1
}
}Absolute Scope (v0.2.9+):
{
"type": "container",
"key": "shipping_options",
"elements": [
{
"type": "text",
"key": "express_note",
"label": "Express Shipping Note",
"enableIf": {
"key": "shipping_type",
"equals": "express",
"scope": "absolute"
}
}
]
}Hide Entire Sections:
{
"type": "container",
"key": "advanced_settings",
"label": "Advanced Settings",
"enableIf": {
"key": "mode",
"equals": "advanced"
},
"elements": [
// All child elements are hidden when container is hidden
]
}How It Works:
- Fields with unmet conditions are not rendered and excluded from form data
- Conditions are evaluated before rendering each element
- Automatic re-evaluation when dependency fields change (integrated with onChange system)
- Works in both edit and readonly modes
- Safe handling of undefined/null values
Path Resolution:
- Dot notation:
user.profile.name,settings.theme.primaryColor - Array indices:
items[0].name,addresses[1].city - Mixed:
users[0].profile.email
Future Extensibility:
The enableIf system is designed for future expansion:
// Future operators (planned)
{
"enableIf": {
"key": "age",
"greaterThan": 18
}
}
{
"enableIf": {
"key": "status",
"in": ["active", "pending"]
}
}
// Future complex conditions (planned)
{
"enableIf": {
"and": [
{ "key": "role", "equals": "admin" },
{ "key": "verified", "equals": true }
]
}
}Documentation
- API Reference - See above for core methods and theming
- Development Guide - CLAUDE.md for architecture and workflow
- Changelog - CHANGELOG.md for version history and migration
Live Demo
Try it now: https://picaz-form-builder.website.yandexcloud.net/form-builder/latest/index.html
Version Index: https://picaz-form-builder.website.yandexcloud.net/index.html
Features a 3-column interface:
- Schema Editor: Edit JSON schema with validation
- Live Preview: See form render in real-time
- Data Output: View/export form data as JSON
Support & Contributing
- GitHub: picazru/form-builder
- NPM Package: @dmitryvim/form-builder
- CDN: picaz-form-builder.website.yandexcloud.net
- Version: 0.2.0 (Unreleased - Breaking Changes + New Features)
- License: MIT - see LICENSE file
Built with ❤️ for the web development community.
