npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dmitryvim/form-builder

v0.2.15

Published

A reusable JSON schema form builder library

Downloads

481

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-builder

TypeScript 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 !important overrides)
  • 📝 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:

  1. 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);
  1. 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 color
  • borderRadius - Border radius for inputs/buttons
  • fontSize - Base font size
  • fontFamily - Font family
  • borderColor - 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:

  1. getDownloadUrl - If provided, this URL is used for downloads
  2. getThumbnail - If getDownloadUrl is not provided, falls back to thumbnail URL
  3. downloadFile - If neither URL hook is provided, calls the download handler

Use Cases:

  • Separate thumbnail and download URLs: Provide both getThumbnail (for optimized previews) and getDownloadUrl (for full files)
  • Same URL for both: Provide only getThumbnail if thumbnail URL is also the download URL
  • Programmatic download: Provide only downloadFile for 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 container
  • scope: "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

Built with ❤️ for the web development community.