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

finform-react-builder

v1.12.4

Published

A powerful, flexible React form builder with dynamic field rendering, custom validation, multi-step forms, Material-UI integration, image component support, toggle/radio buttons, switches, autocomplete, and advanced button positioning

Readme

finform-react-builder

A powerful, flexible React form builder with dynamic field rendering, custom validation, and Material-UI integration.

npm version License: MIT

🚨 Migration Notice

If you're currently using this package from GitHub Packages, you'll need to update your installation:

# Remove the old package
npm uninstall @finflow-analytics/finform-react-builder

# Install from public npm registry
npm install finform-react-builder

If you have a .npmrc file with GitHub Packages configuration, remove the line:

@finflow-analytics:registry=https://npm.pkg.github.com

✨ Features

  • 🎨 Material-UI Integration - Beautiful, responsive form components
  • 🔧 Dynamic Field Rendering - text, email, password (with eye toggle), number, select, checkbox, radio, switch, autocomplete (single/multiple with checkboxes and +N more), date, textarea, image
  • Advanced Validation - Built-in validation with custom regex patterns and validation functions
  • 📱 Responsive Grid System - Flexible column-based layout system
  • 🎯 TypeScript Support - Full type safety and IntelliSense support
  • 🚀 Easy Integration - Simple API with sensible defaults
  • 🔄 Real-time Validation - Instant feedback with react-hook-form
  • 🎨 Customizable - Highly configurable with custom validation rules
  • 🔄 Multi-Step Forms - Support for step-by-step form completion with smart navigation
  • 🧩 Custom Components - Inject any React node via type: 'component'
  • 💾 Stateless Design - Works with data from backend/config without local storage dependencies
  • 📎 Document Upload - Drag & drop uploader with validation and preview

📦 Installation

npm install finform-react-builder

Peer Dependencies

Make sure you have the required peer dependencies installed:

npm install react react-dom @mui/material @emotion/react @emotion/styled

🚀 Quick Start

import React from 'react';
import { FinForm, FieldConfig } from 'finform-react-builder';

const fields: FieldConfig[] = [
  {
    name: 'firstName',
    label: 'First Name',
    type: 'text',
    placeholder: 'Enter your first name',
    required: true,
    col: 6
  },
  {
    name: 'lastName',
    label: 'Last Name',
    type: 'text',
    placeholder: 'Enter your last name',
    required: true,
    col: 6
  },
  {
    name: 'email',
    label: 'Email',
    type: 'email',
    placeholder: 'Enter your email',
    required: true,
    col: 12
  }
];

function App() {
  const handleSubmit = (data: any) => {
    console.log('Form submitted:', data);
  };

  return (
    <FinForm
      fields={fields}
      onSubmit={handleSubmit}
      submitButtonText="Submit Form"
    />
  );
}

export default App;

📋 Field Types

Password (with visibility toggle)

{ name: 'password', label: 'Password', type: 'password', placeholder: 'Enter password', required: true, helperText: 'Use at least 8 characters.' }

Behavior: An eye icon toggles visibility between •••• and plain text.

Text Fields

{
  name: 'username',
  label: 'Username',
  type: 'text',
  placeholder: 'Enter username',
  validation: {
    pattern: /^[a-zA-Z0-9_]+$/,
    message: 'Username can only contain letters, numbers, and underscores'
  }
}

Email Fields

{
  name: 'email',
  label: 'Email Address',
  type: 'email',
  placeholder: 'Enter your email',
  required: true
}

Number Fields

{
  name: 'age',
  label: 'Age',
  type: 'number',
  placeholder: 'Enter your age',
  validation: {
    min: 18,
    max: 120,
    message: 'Age must be between 18 and 120'
  }
}

Select Fields

{
  name: 'country',
  label: 'Country',
  type: 'select',
  options: [
    { label: 'United States', value: 'us' },
    { label: 'Canada', value: 'ca' },
    { label: 'United Kingdom', value: 'uk' }
  ],
  required: true
}

Date Fields

{
  name: 'birthDate',
  label: 'Birth Date',
  type: 'date',
  validation: {
    custom: (value) => {
      const today = new Date();
      const birthDate = new Date(value);
      return birthDate < today;
    },
    message: 'Birth date must be in the past'
  }
}

Image Fields

{
  name: 'profileImage',
  label: 'Profile Image',
  type: 'image',
  src: 'https://example.com/image.jpg',
  alt: 'User profile image',
  width: 300,
  height: 200,
  style: {
    border: '2px solid #e0e0e0',
    borderRadius: '8px'
  },
  onClick: () => {
    console.log('Image clicked!');
  }
}

Textarea Fields

{
  name: 'bio',
  label: 'Biography',
  type: 'textarea',
  placeholder: 'Tell us about yourself...',
  validation: {
    maxLength: 500,
    message: 'Biography must be less than 500 characters'
  }
}

Checkbox Fields

{ name: 'agree', label: 'Agree to terms', type: 'checkbox', required: true }

Autocomplete (Single)

{
  name: 'country',
  label: 'Country',
  type: 'autocomplete',
  placeholder: 'Search country',
  options: [
    { label: 'United States', value: 'us' },
    { label: 'Canada', value: 'ca' },
  ],
  // For API-driven:
  // api_endpoint: '/common/countries?format=dropdown',
  // api_method: 'GET',
  // value_field: 'id',
  // label_field: 'label',
}

Autocomplete (Multiple) with Checkboxes and +N More Chips

{
  name: 'skills',
  label: 'Skills',
  type: 'autocomplete',
  multiple: true,
  options: [
    { label: 'JavaScript', value: 'javascript' },
    { label: 'React', value: 'react' },
    { label: 'TypeScript', value: 'typescript' },
  ],
  // Stored value is an array of primitive values: ['javascript', 'react']
}

Behavior:

  • Renders checkboxes in the dropdown when multiple: true.
  • Only the first 2 selections are rendered as chips; if more, shows a +N more chip.
  • Works with API-driven data using api_endpoint, value_field, label_field.

Copy icon for disabled fields

For any disabled field, a small copy icon appears to the right, allowing users to copy the field value to clipboard.

{ name: 'readonly_id', label: 'User ID', type: 'text', value: 'USR-7842-AB', disabled: true }

Toggle (Segmented Radios)

{
  name: 'gender',
  label: 'Gender',
  type: 'toggle',
  options: [
    { label: 'Male', value: 'male' },
    { label: 'Female', value: 'female' },
    { label: 'Other', value: 'other' },
  ],
  required: true,
}

Radio (Vertical)

{
  name: 'contactPreference',
  label: 'Contact Preference',
  type: 'radio',
  options: [
    { label: 'Email', value: 'email' },
    { label: 'Phone', value: 'phone' },
  ],
}

Switch

{
  name: 'notifications',
  label: 'Enable Notifications',
  type: 'switch',
}

Title and Section Headings

Custom Component Field

Render arbitrary React content inside the form layout.

{ name: 'custom_block', type: 'component', col: 12, step: 2, content: <div>Any JSX here</div> }
{ name: 'formTitle', type: 'title', label: 'Create User', variant: 'h4', col: 12 }
{ name: 'detailsSection', type: 'section', label: 'Details', variant: 'h6', col: 12 }

🔗 API-Driven Fields and Dependencies

Basic API-Driven Select

{
  name: 'country_id',
  label: 'Country',
  type: 'select',
  api_endpoint: '/common/countries?format=dropdown',
  api_method: 'GET',
  value_field: 'id',
  label_field: 'label',
}

Dependent Field (Conditional + depends_on)

{
  name: 'org_type_id',
  label: 'Entity Type',
  type: 'autocomplete',
  api_endpoint: '/company/org-types/active/list',
  api_method: 'GET',
  value_field: 'id',
  label_field: 'name',
},
{
  name: 'parent_id',
  label: 'Parent Entity',
  type: 'autocomplete',
  api_endpoint: '/company/offices/potential-parents/{org_type_id}?format=dropdown',
  api_method: 'GET',
  depends_on: 'org_type_id',
  conditional: true,
  value_field: 'id',
  label_field: 'label',
}

Behavior:

  • The dependent field (parent_id) is disabled until org_type_id has a value.
  • When the dependency value changes, the dependent field is reset (clears previous selection).

✅ Validation and Submit Button Behavior

  • Required fields must be filled; for arrays (e.g., multi-select), value must be non-empty.
  • Required checkboxes must be true.
  • Any validation error (including on non-required fields) disables submit.
  • Single-step forms: built-in Submit button is disabled until valid.
  • Multi-step forms: Next/Submit are disabled until the current step is valid.
  • Custom buttons passed via buttons or buttonGroup are also auto-disabled when they are flow actions (Next/Submit/Save/Finish/Complete) and the step/form is invalid.

Number validation example (number type):

{
  name: 'longitude',
  label: 'Longitude',
  type: 'number',
  validation: { min: -180, max: 180, message: 'Longitude must be between -180 and 180' },
}

For type: 'text' number-like validation, use pattern or custom in validation.

🧭 Buttons and Button Groups

Custom Buttons

<FinForm
  fields={fields}
  buttons={[
    { text: 'Cancel', type: 'button', onClick: () => navigate(-1) },
    { text: 'Submit', type: 'submit', color: 'primary' },
  ]}
/>
  • Submit (and Next) buttons are auto-disabled when the form (or current step) is invalid.
  • Non-submit buttons (e.g., Cancel) remain clickable.

Button Group

<FinForm
  fields={fields}
  buttonGroup={{
    position: 'right',
    buttons: [
      { text: 'Previous', type: 'button' },
      { text: 'Next', type: 'button' },
      { text: 'Submit', type: 'submit' },
    ],
  }}
/>
  • Buttons with text containing "Next", "Submit", "Save", "Finish", "Complete" are treated as flow actions and auto-disabled appropriately.

🎨 Theming

<FinForm
  theme={{
    primaryColor: '#2196f3',
    secondaryColor: '#f50057',
    backgroundColor: '#fafafa',
    textColor: '#333',
    borderRadius: 8,
    spacing: 16,
    typography: { fontFamily: 'Roboto, Arial, sans-serif', fontSize: 16 },
  }}
  fields={fields}
  onSubmit={...}
/>

🖼️ Image Field

{
  name: 'profileImage',
  label: 'Profile Image',
  type: 'image',
  defaultValue: 'https://via.placeholder.com/300x200',
  alt: 'User profile image',
  width: 300,
  height: 200,
  onClick: () => console.log('Image clicked')
}

🔁 onChange

onChange is fired with current form values on every change, without creating render loops, and accounts for dependent resets.

{
  name: 'newsletter',
  label: 'Subscribe to Newsletter',
  type: 'checkbox',
  required: true
}

🔧 Advanced Validation

Custom Regex Patterns

{
  name: 'phoneNumber',
  label: 'Phone Number',
  type: 'text',
  placeholder: '+1 (555) 123-4567',
  validation: {
    pattern: /^\+?[1-9]\d{1,14}$/,
    message: 'Please enter a valid phone number'
  }
}

Custom Validation Functions

{
  name: 'password',
  label: 'Password',
  type: 'password',
  placeholder: 'Enter password',
  validation: {
    minLength: 8,
    custom: (value) => {
      const hasUpperCase = /[A-Z]/.test(value);
      const hasLowerCase = /[a-z]/.test(value);
      const hasNumbers = /\d/.test(value);
      const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value);
      
      return hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar;
    },
    message: 'Password must contain uppercase, lowercase, number and special character'
  }
}

📐 Grid System

Use the responsive grid system to control field layout:

// Full width field
{ name: 'description', type: 'textarea', col: 12 }

// Half width fields
{ name: 'firstName', type: 'text', col: 6 }
{ name: 'lastName', type: 'text', col: 6 }

// Responsive breakpoints
{
  name: 'address',
  type: 'text',
  xs: 12,  // Full width on extra small screens
  md: 8,   // 8/12 width on medium screens
  lg: 6    // Half width on large screens
}

🔄 Multi-Step Forms

FinForm supports multi-step forms with smart navigation and completion tracking. Perfect for complex forms that need to be broken down into manageable steps.

Basic Multi-Step Form

const multiStepFields: FieldConfig[] = [
  // Step 1: Personal Information
  {
    name: 'firstName',
    label: 'First Name',
    type: 'text',
    required: true,
    step: 1,
    col: 6,
  },
  {
    name: 'lastName',
    label: 'Last Name',
    type: 'text',
    required: true,
    step: 1,
    col: 6,
  },
  {
    name: 'email',
    label: 'Email Address',
    type: 'email',
    required: true,
    step: 1,
    col: 12,
  },

  // Step 2: Address Information
  {
    name: 'street',
    label: 'Street Address',
    type: 'text',
    required: true,
    step: 2,
    col: 12,
  },
  {
    name: 'city',
    label: 'City',
    type: 'text',
    required: true,
    step: 2,
    col: 6,
  },
  {
    name: 'state',
    label: 'State',
    type: 'select',
    required: true,
    step: 2,
    col: 3,
    options: [
      { label: 'California', value: 'CA' },
      { label: 'New York', value: 'NY' },
      { label: 'Texas', value: 'TX' },
    ],
  },
  {
    name: 'zipCode',
    label: 'ZIP Code',
    type: 'text',
    required: true,
    step: 2,
    col: 3,
    validation: {
      pattern: /^\d{5}(-\d{4})?$/,
      message: 'Please enter a valid ZIP code',
    },
  },

  // Step 3: Preferences
  {
    name: 'newsletter',
    label: 'Subscribe to Newsletter',
    type: 'checkbox',
    step: 3,
    col: 12,
  },
  {
    name: 'preferences',
    label: 'Communication Preferences',
    type: 'select',
    step: 3,
    col: 6,
    options: [
      { label: 'Email', value: 'email' },
      { label: 'Phone', value: 'phone' },
      { label: 'Both', value: 'both' },
    ],
  },
];

function MultiStepForm() {
  const [currentStep, setCurrentStep] = useState(1);

  const handleStepChange = (step: number, totalSteps: number) => {
    console.log(`Moving to step ${step} of ${totalSteps}`);
    setCurrentStep(step);
  };

  return (
    <FinForm
      fields={multiStepFields}
      onSubmit={(data) => console.log('Form submitted:', data)}
      isMultiStep={true}
      currentStep={currentStep}
      onStepChange={handleStepChange}
      submitButtonText="Complete Registration"
      stepNavigationProps={{
        showStepNumbers: true,
        showStepTitles: true,
        stepTitles: ['Personal Info', 'Address', 'Preferences'],
      }}
    />
  );
}

Smart Step Navigation with Backend Data

The form automatically detects completed steps and starts from the first incomplete step when data is provided:

// Data from backend showing user has completed step 1
const backendData = {
  firstName: 'John',
  lastName: 'Doe',
  email: '[email protected]',
  // Step 2 and 3 are empty
};

function SmartMultiStepForm() {
  return (
    <FinForm
      fields={multiStepFields}
      onSubmit={(data) => console.log('Form submitted:', data)}
      defaultValues={backendData}
      isMultiStep={true}
      // Form will automatically start at step 2 since step 1 is complete
      submitButtonText="Complete Registration"
    />
  );
}

Multi-Step Form Props

| Prop | Type | Description | Default | |------|------|-------------|---------| | isMultiStep | boolean | Enable multi-step functionality | false | | currentStep | number | Current step (controlled by parent) | 1 | | onStepChange | (step: number, totalSteps: number) => void | Step change callback | - | | showStepNavigation | boolean | Show step navigation | true | | stepNavigationProps | object | Step navigation configuration | {} |

Step Navigation Configuration

stepNavigationProps={{
  showStepNumbers: true,    // Show step numbers
  showStepTitles: true,     // Show step titles
  stepTitles: ['Step 1', 'Step 2', 'Step 3'], // Custom step titles
}}

Field Step Assignment

Add a step property to any field to assign it to a specific step:

{
  name: 'fieldName',
  label: 'Field Label',
  type: 'text',
  step: 1, // This field will appear in step 1
  required: true,
}

🎛️ API Reference

FinForm Props

| Prop | Type | Description | Required | |------|------|-------------|----------| | fields | FieldConfig[] | Array of field configurations | ✅ | | onSubmit | (data: any) => void | Form submission handler | ✅ | | onChange | (data: any) => void | Form change handler | ❌ | | submitButtonText | string | Submit button text | ❌ | | defaultValues | Record<string, any> | Default form values | ❌ | | isMultiStep | boolean | Enable multi-step functionality | ❌ | | currentStep | number | Current step (controlled by parent) | ❌ | | onStepChange | (step: number, totalSteps: number) => void | Step change callback | ❌ | | showStepNavigation | boolean | Show step navigation | ❌ | | stepNavigationProps | object | Step navigation configuration | ❌ |

Field Configuration

interface BaseField {
  name: string;
  label: string;
  type: 'text' | 'email' | 'password' | 'number' | 'select' | 'checkbox' | 'date' | 'textarea' | 'image';
  placeholder?: string;
  required?: boolean;
  disabled?: boolean;
  validation?: ValidationRule;
  step?: number; // Step number for multi-step forms (1-based)
  col?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
  xs?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
  sm?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
  md?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
  lg?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
  xl?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
}

interface ImageField extends BaseField {
  type: 'image';
  src?: string; // Image source URL
  alt?: string; // Alt text for accessibility
  width?: number | string; // Image width
  height?: number | string; // Image height
  style?: React.CSSProperties; // Custom styles
  className?: string; // Custom CSS class
  onClick?: () => void; // Click handler
}

Validation Rules

interface ValidationRule {
  pattern?: string | RegExp;
  message?: string;
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  min?: number;
  max?: number;
  custom?: (value: any) => boolean | string;
}

💡 Examples

Complete Registration Form

const registrationFields: FieldConfig[] = [
  {
    name: 'firstName',
    label: 'First Name',
    type: 'text',
    placeholder: 'Enter first name',
    required: true,
    col: 6
  },
  {
    name: 'lastName',
    label: 'Last Name',
    type: 'text',
    placeholder: 'Enter last name',
    required: true,
    col: 6
  },
  {
    name: 'email',
    label: 'Email',
    type: 'email',
    placeholder: 'Enter email address',
    required: true,
    col: 12
  },
  {
    name: 'password',
    label: 'Password',
    type: 'password',
    placeholder: 'Enter password',
    required: true,
    col: 6,
    validation: {
      minLength: 8,
      pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
      message: 'Password must be at least 8 characters with uppercase, lowercase, number and special character'
    }
  },
  {
    name: 'confirmPassword',
    label: 'Confirm Password',
    type: 'password',
    placeholder: 'Confirm password',
    required: true,
    col: 6
  },
  {
    name: 'agreeToTerms',
    label: 'I agree to the Terms and Conditions',
    type: 'checkbox',
    required: true,
    col: 12
  }
];

🛠️ Development

Building the Library

# Build for production
npm run build:lib

# Build types only
npm run build:types

Running Development Server

npm run dev

📄 License

MIT © Ritik

🤝 Contributing

Contributions, issues and feature requests are welcome!

Feel free to check issues page.

📞 Support

If you like this project, please ⭐ star it on GitHub!

📦 Document Upload Component

Drag & drop upload with validation, preview, and optional submit button.

import { DocumentUpload } from 'finform-react-builder';

<DocumentUpload
  title="Drag & Drop Files"
  subtitle="Upload images or PDFs"
  buttonText="Select Files"
  fileSupportedText=".jpg, .jpeg, .png, .pdf up to 10MB"
  config={{
    id: 'kyc-docs',
    fileTypes: ['image/*', 'application/pdf'],
    maxSize: 10 * 1024 * 1024,
    multiple: true,
    required: true,
    submitButton: { text: 'Upload', position: 'right' },
  }}
  value={files}
  onChange={setFiles}
  onSubmit={(files) => console.log('Submitting', files)}
/>

🧭 Custom Stepper

Card-style stepper with titles, connectors, and progress bar.

<FinForm
  fields={fields}
  stepNavigationProps={{
    stepTitles: ['Basic Information', 'Permissions Matrix', 'Review'],
  }}
  onSubmit={...}
/>

📝 Helper Text

Provide guidance beneath inputs via helperText on any field.

{ name: 'first_name', label: 'First Name', type: 'text', helperText: 'Only alphabets, min 2 characters.' }

📏 Input Size

All inputs default to MUI size "small" for compact UI.