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

@ilife-tech/react-application-flow-renderer

v1.2.71

Published

A dynamic application flow renderer for React applications with Universal API support(sqv2.0)

Readme

React Dynamic Form Renderer

A powerful and flexible React package for rendering dynamic forms with advanced validation, conditional logic, and third-party API integration capabilities.

npm version license

Table of Contents

Features

Core Capabilities

  • Schema-Driven Forms: Generate complex forms from JSON schema
  • Dynamic Validation: Client and server-side validation support
  • Conditional Logic: Show/hide fields based on advanced rule conditions
  • Third-Party API Integration: Seamless integration with external APIs
  • Nested Forms: Support for complex, nested form structures
  • Responsive Design: Mobile-friendly with customizable breakpoints
  • Accessibility: WCAG 2.1 compliant components

Prerequisites

  • React 18+
  • Node.js 20+
  • npm 10+ or Yarn
  • Browser support: Latest versions of Chrome, Firefox, Safari, and Edge

Installation

# Using npm
npm install @ilife-tech/react-application-flow-renderer

# Using yarn
yarn add @ilife-tech/react-application-flow-renderer

Quick Start

Basic Implementation

import React, { useState } from 'react';
import DynamicForm from '@ilife-tech/react-application-flow-renderer';

const App = () => {
  // Form schema definition
  const questionGroups = [
    {
      groupId: 'personalInfo',
      groupName: 'Personal Information',
      questions: [
        {
          questionId: 'fullName',
          questionType: 'text',
          label: 'Full Name',
          validation: { required: true },
        },
        {
          questionId: 'email',
          questionType: 'email',
          label: 'Email Address',
          validation: { required: true, email: true },
        },
      ],
    },
  ];

  // Action buttons configuration
  const actionSchema = [
    { text: 'Submit', action: 'submit' },
    { text: 'Cancel', action: 'cancel' },
  ];

  // Form submission handler
  const handleSubmit = (formState, action) => {
    console.log('Form submitted:', formState.values);
    console.log('Action:', action);
  };

  return (
    <DynamicForm
      questionGroups={questionGroups}
      actionSchema={actionSchema}
      onSubmit={handleSubmit}
    />
  );
};

Real-World Example

Below is a comprehensive real-world implementation showcasing advanced features like API integration, state management, and theming:

import React, { useState, useEffect, useCallback, useMemo } from 'react';
import SectionListRenderer from '@ilife-tech/react-section-renderer';
import { DynamicForm } from '@ilife-tech/react-application-flow-renderer';

// Constants and configuration
const API_CONFIG = {
  BASE_URL: process.env.REACT_APP_API_BASE_URL || '<API_BASE_URL>',
  ENDPOINTS: {
    STORE_VENDOR: '<API_ENDPOINT>',
  },
  MAX_API_ATTEMPTS: 8,
};

const INITIAL_QUESTIONS = [
  {
    name: 'userToken',
    value: process.env.REACT_APP_USER_TOKEN || '<USER_TOKEN>',
  },
  { name: 'user', value: '<USER_TYPE>' },
  { name: 'productName', value: '<PRODUCT_NAME>' },
  { name: 'stateName', value: '<STATE_NAME>' },
];

// API configuration for third-party integrations
const apiConfig = {
  googlePlaces: {
    apiKey: process.env.REACT_APP_GOOGLE_PLACES_API_KEY,
    options: {
      minSearchLength: 3,
      debounceTime: 300,
      componentRestrictions: {
        country: process.env.REACT_APP_GOOGLE_PLACES_REGION || 'US',
      },
      types: ['address'],
      fields: ['address_components', 'formatted_address', 'geometry', 'place_id'],
      maxRequestsPerSecond: 10,
      maxRequestsPerDay: 1000,
      language: process.env.REACT_APP_GOOGLE_PLACES_LANGUAGE,
      region: process.env.REACT_APP_GOOGLE_PLACES_REGION,
    },
  },
  customApi: {
    baseUrl: process.env.REACT_APP_CUSTOM_API_BASE_URL,
    headers: {
      Authorization: process.env.REACT_APP_AUTH_TOKEN,
      'Carrier-Auth': process.env.REACT_APP_CARRIER_AUTH,
    },
  },
};

// Theme configuration
const THEME = {
  breakpoints: {
    mobile: '@media (max-width: 480px)',
    tablet: '@media (min-width: 481px) and (max-width: 768px)',
    desktop: '@media (min-width: 769px)',
  },
  body: {
    backgroundColor: '#FFFFFF',
    color: '#333333',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    fontSize: '16px',
    lineHeight: '1.5',
  },
  form: {
    backgroundColor: '#FFFFFF',
    padding: '20px',
    maxWidth: '800px',
    margin: '0 auto',
    fieldSpacing: '1.5rem',
    groupSpacing: '2rem',
  },
  colors: {
    primary: '#1D4ED8',
    secondary: '#6B7280',
    success: '#059669',
    danger: '#DC2626',
    warning: '#D97706',
    info: '#2563EB',
    light: '#F3F4F6',
    dark: '#1F2937',
    error: '#DC2626',
    border: '#D1D5DB',
    inputBackground: '#FFFFFF',
    headerBackground: '#EFF6FF',
  },
  field: {
    marginBottom: '1rem',
    width: '100%',
  },
  label: {
    display: 'block',
    marginBottom: '0.5rem',
    fontSize: '0.875rem',
    fontWeight: '500',
    color: '#374151',
  },
  input: {
    width: '100%',
    padding: '0.5rem',
    fontSize: '1rem',
    borderRadius: '0.375rem',
    border: '1px solid #D1D5DB',
    backgroundColor: '#FFFFFF',
    '&:focus': {
      outline: 'none',
      borderColor: '#2563EB',
      boxShadow: '0 0 0 2px rgba(37, 99, 235, 0.2)',
    },
  },
  error: {
    color: '#DC2626',
    fontSize: '0.875rem',
    marginTop: '0.25rem',
  },
};

function App() {
  // State Management
  const [formState, setFormState] = useState({
    storeVenderData: null,
    questionGroups: [],
    pageRules: [],
    pageRuleGroup: [],
    validationSchema: [],
    actionSchema: [],
    sectionSchema: [],
    caseId: null,
    pageId: null,
    apiCallCounter: 0,
    selectedAction: '',
    error: null,
    isLoading: false,
  });

  // Memoized API headers
  const headers = useMemo(
    () => ({
      'Content-Type': 'application/json',
      Authorization: process.env.REACT_APP_AUTH_TOKEN || '<AUTH_TOKEN>',
      CarrierAuthorization: process.env.REACT_APP_CARRIER_AUTH || '<CARRIER_AUTH_TOKEN>',
    }),
    []
  );

  // Transform form data with error handling
  const transformFormData = useCallback((formData, schema) => {
    try {
      // Handle empty array or non-object formData
      const formDataObj = Array.isArray(formData) || typeof formData !== 'object' ? {} : formData;
      const { values = {}, visibility = {}, disabled = {} } = formDataObj;
      const transformedData = [];

      // Process form data based on schema
      // ... (transform logic omitted for brevity)

      return transformedData;
    } catch (error) {
      console.error('Error transforming form data:', error);
      throw new Error('Failed to transform form data');
    }
  }, []);

  // API call with error handling
  const fetchStoreQuestionVendor = useCallback(
    async (questions, action = '', nextPageId = '') => {
      try {
        const { caseId, pageId } = formState;
        setFormState((prev) => ({ ...prev, isLoading: true, error: null }));

        const requestPayload = {
          caseId,
          pageId,
          questions,
          user: '<USER_TYPE>',
          agentId: '<AGENT_ID>',
          templateName: '<PRODUCT_NAME>',
          ...(action && { action }),
          ...(nextPageId && { nextPageId }),
        };

        const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.STORE_VENDOR}`, {
          method: 'POST',
          headers,
          body: JSON.stringify(requestPayload),
        });

        if (!response.ok) {
          throw new Error(`API Error: ${response.status}`);
        }

        const { data: result } = await response.json();
        const { pageDefinition, sectionList, caseId: newCaseId, pageId: newPageId } = result || {};

        const {
          questionGroups = [],
          pageRules: rawPageRules = null,
          pageRuleGroup: rawPageRuleGroup = null,
          validationErrors = [],
          actionButtons = [],
        } = pageDefinition || {};

        // Process and update state with API response
        setFormState((prev) => ({
          ...prev,
          storeVenderData: result,
          questionGroups,
          pageRules: Array.isArray(rawPageRules) ? rawPageRules : [],
          pageRuleGroup: Array.isArray(rawPageRuleGroup) ? rawPageRuleGroup : [],
          validationSchema: validationErrors,
          actionSchema: actionButtons,
          sectionSchema: sectionList || [],
          caseId: newCaseId || '',
          pageId: newPageId || '',
          selectedAction: action,
          isLoading: false,
        }));
      } catch (error) {
        console.error('API Error:', error);
        setFormState((prev) => ({
          ...prev,
          error: error.message,
          isLoading: false,
        }));
      }
    },
    [formState.caseId, formState.pageId, headers]
  );

  // Form submission handler
  const handleSubmit = useCallback(
    (newFormState, action) => {
      const { questionGroups } = formState;
      const transformedData = transformFormData(newFormState, questionGroups);
      fetchStoreQuestionVendor(transformedData, action);
    },
    [fetchStoreQuestionVendor, formState.questionGroups, transformFormData]
  );

  // Section change handler
  const handleSectionChange = useCallback(
    (pageId) => {
      const { questionGroups } = formState;
      const transformedData = transformFormData([], questionGroups);
      fetchStoreQuestionVendor(transformedData, '', pageId);
    },
    [fetchStoreQuestionVendor, formState.questionGroups, transformFormData]
  );

  // Initial data fetch
  useEffect(() => {
    fetchStoreQuestionVendor(INITIAL_QUESTIONS);
  }, []);

  // Extract commonly used state values
  const {
    error,
    isLoading,
    storeVenderData,
    sectionSchema,
    questionGroups,
    pageRules,
    pageRuleGroup,
    validationSchema,
    actionSchema,
    pageId,
  } = formState;

  // Handle API triggers from form fields
  const handleApiTrigger = (apiRequest) => {
    // Implementation for API trigger handling
    console.log('API request:', apiRequest);
    // Return promise with response structure
    return Promise.resolve({
      success: true,
      data: {
        // Example: update options for a dropdown
        options: [
          { name: 'Option 1', value: 'option1' },
          { name: 'Option 2', value: 'option2' },
        ],
      },
    });
  };

  return (
    <div className="app-container">
      <header className="app-header">
        <h1>Dynamic Form Example</h1>
      </header>

      <div className="app-main">
        {error && (
          <div className="error-message" role="alert">
            {error}
          </div>
        )}

        {isLoading && (
          <div className="loading-indicator" role="status">
            Loading...
          </div>
        )}

        {storeVenderData && (
          <>
            <aside className="app-sidebar">
              {!isLoading && sectionSchema?.length > 0 && (
                <SectionListRenderer
                  sectionLists={sectionSchema}
                  onSectionClick={handleSectionChange}
                  customStyles={{ backgroundColor: '#f5f5f5' }}
                  pageId={pageId || sectionSchema[0]?.pageId}
                />
              )}
            </aside>

            <main className="app-content">
              {questionGroups?.length > 0 && (
                <DynamicForm
                  questionGroups={questionGroups}
                  pageRules={pageRules}
                  pageRuleGroup={pageRuleGroup}
                  validationErrors={validationSchema}
                  actionButtons={actionSchema}
                  theme={THEME}
                  onSubmit={handleSubmit}
                  onApiTrigger={handleApiTrigger}
                  apiConfig={apiConfig}
                />
              )}
            </main>
          </>
        )}
      </div>
    </div>
  );
}

export default App;

Form Schema Structure

The form structure is defined by a JSON schema that specifies question groups and their fields. Each form consists of one or more question groups, each containing multiple field definitions.

Question Group Structure

{
  groupId: string,           // Unique identifier for the group
  groupName: string,         // Display name for the group (optional)
  description: string,       // Group description (optional)
  columnCount: number,       // Number of columns for layout (default: 1)
  visible: boolean,          // Group visibility (default: true)
  questions: Array<Question> // Array of question objects
}

Question (Field) Structure

{
  questionId: string,       // Unique identifier for the question/field
  questionType: string,      // Field type (text, dropdown, checkbox, etc.)
  label: string,             // Field label
  placeholder: string,       // Field placeholder (optional)
  helpText: string,          // Help text below the field (optional)
  defaultValue: any,         // Default field value (optional)
  validation: Object,        // Validation rules (optional)
  visible: boolean,          // Field visibility (default: true)
  disabled: boolean,         // Field disabled state (default: false)
  required: boolean,         // Whether field is required (default: false)
  options: Array<Option>,    // Options for select/dropdown/radio fields
  layout: Object,            // Layout configuration (optional)
  style: Object,             // Custom styling (optional)
  apiTrigger: Object         // API integration configuration (optional)
}

Action Schema Structure

The action schema defines form buttons and their behaviors:

[
  {
    text: string, // Button text
    action: string, // Action identifier (e.g., 'submit', 'back', 'next')
    position: string, // Button position (optional, default: 'right')
  },
];

API Integration

The package provides robust third-party API integration capabilities, allowing forms to fetch data, populate options, and submit data to external services.

Basic API Integration

API integration is handled through the onApiTrigger prop, which receives API trigger events from form fields:

import React from 'react';
import DynamicForm from '@ilife-tech/react-application-flow-renderer';
import { handleApiTrigger } from './services/apiService';

const MyForm = () => {
  // Form schema with API trigger configuration
  const questionGroups = [
    {
      groupId: 'locationInfo',
      questions: [
        {
          questionId: 'country',
          questionType: 'dropdown',
          label: 'Country',
          options: [
            { name: 'United States', value: 'US' },
            { name: 'Canada', value: 'CA' },
          ],
        },
        {
          questionId: 'state',
          questionType: 'dropdown',
          label: 'State/Province',
          placeholder: 'Select a state',
          // API trigger configuration
          apiTrigger: {
            type: 'dropdown',
            dependsOn: ['country'], // Fields this API call depends on
            apiUrl: 'getStatesByCountry', // API identifier
            triggerEvents: ['onChange', 'onLoad'], // When to trigger API
          },
        },
      ],
    },
  ];

  // API configuration
  const apiConfig = {
    baseUrl: process.env.REACT_APP_API_BASE_URL || 'https://api.example.com',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.REACT_APP_API_TOKEN}`,
    },
    timeout: 5000,
  };

  return (
    <DynamicForm
      questionGroups={questionGroups}
      onApiTrigger={handleApiTrigger}
      apiConfig={apiConfig}
    />
  );
};

export default MyForm;

Implementing the API Handler

The API handler function processes API triggers and returns updates to the form. Here's a practical example based on the package's example implementation:

// services/apiService.js
import { createApiResponse } from '../utils/apiUtils';

/**
 * Handle API trigger events from form fields
 * @param {Object} triggerData - Data from the form's onApiTrigger event
 * @returns {Promise<Object>} - Response with form updates
 */
export const handleApiTrigger = async (triggerData) => {
  const { question, value, triggerType, formState } = triggerData;

  // Extract API configuration
  const { apiTrigger, questionId } = question;

  // Skip if no API trigger is configured
  if (!apiTrigger || !apiTrigger.apiUrl) {
    return null;
  }

  try {
    // For dropdown fields that depend on other fields
    if (question.questionType === 'dropdown' && triggerType === 'onChange') {
      // Example: Fetch states based on selected country
      if (apiTrigger.apiUrl === 'getStatesByCountry') {
        const countryValue = formState.values[apiTrigger.dependsOn[0]];

        // Don't proceed if no country is selected
        if (!countryValue) {
          return createApiResponse({
            options: { [questionId]: [] },
            values: { [questionId]: '' },
          });
        }

        // Make API request
        const response = await fetch(`${apiConfig.baseUrl}/states?country=${countryValue}`, {
          headers: apiConfig.headers,
        });

        const data = await response.json();

        // Map API response to dropdown options
        const stateOptions = data.map((state) => ({
          name: state.name,
          value: state.code,
        }));

        // Return updates for the form
        return createApiResponse({
          options: { [questionId]: stateOptions },
          visibility: { [questionId]: true },
          disabled: { [questionId]: false },
        });
      }
    }

    return null;
  } catch (error) {
    console.error('API error:', error);

    // Return error response
    return createApiResponse({
      apiErrors: { [questionId]: 'Failed to fetch data. Please try again.' },
    });
  }
};

API Response Structure

API responses should be formatted as an object with field updates:

// utils/apiUtils.js

/**
 * Create formatted API response for form updates
 * @param {Object} updates - Updates to apply to the form
 * @returns {Object} - Formatted response
 */
export const createApiResponse = (updates = {}) => {
  return {
    // Field values to update
    values: updates.values || {},

    // Dropdown options to update
    options: updates.options || {},

    // Field visibility updates
    visibility: updates.visibility || {},

    // Field disabled state updates
    disabled: updates.disabled || {},

    // Field required state updates
    required: updates.required || {},

    // Error messages
    apiErrors: updates.apiErrors || {},

    // Loading states
    apiLoading: updates.apiLoading || {},
  };
};

Advanced API Integration Example

Here's a more comprehensive example showing how to handle complex API integration with request queuing and response mapping:

// services/api/handlers/dropdownApiHandler.js

import { createApiResponse } from '../../../utils/apiUtils';

// Request queue for managing concurrent API calls
class ApiRequestQueue {
  constructor() {
    this.queue = new Map();
  }

  async enqueue(questionId, apiCall) {
    if (this.queue.has(questionId)) {
      return this.queue.get(questionId);
    }

    const promise = apiCall().finally(() => {
      this.queue.delete(questionId);
    });

    this.queue.set(questionId, promise);
    return promise;
  }
}

const requestQueue = new ApiRequestQueue();

/**
 * Handle API calls for dropdown fields
 * @param {Object} triggerData - API trigger data
 * @returns {Promise<Object>} Response with options and updates
 */
export const handleDropdownApi = async (triggerData) => {
  const { question, value, triggerType, formState } = triggerData;
  const { questionId, apiTrigger } = question;

  // Example: Handle different API endpoints based on apiUrl
  switch (apiTrigger?.apiUrl) {
    case 'getHouseholdMembers':
      return requestQueue.enqueue(questionId, () =>
        getHouseholdMembers(apiTrigger.apiUrl, formState)
      );

    case 'getStatesByCountry':
      // Get dependent field value
      const country = formState.values[apiTrigger.dependsOn[0]];
      if (!country) {
        return createApiResponse({
          options: { [questionId]: [] },
          disabled: { [questionId]: true },
        });
      }

      return requestQueue.enqueue(questionId, () => getStatesByCountry(apiTrigger.apiUrl, country));

    default:
      return null;
  }
};

/**
 * Example API implementation for fetching states by country
 */
async function getStatesByCountry(apiUrl, country) {
  try {
    // Here we'd normally implement our API call
    const response = await fetch(`https://api.example.com/states?country=${country}`);
    const data = await response.json();

    // Map response to options format
    const options = data.map((state) => ({
      name: state.stateName || state.name,
      value: state.stateCode || state.code,
    }));

    return createApiResponse({
      options: { state: options },
      disabled: { state: false },
      visibility: { state: true },
    });
  } catch (error) {
    console.error('API error:', error);
    return createApiResponse({
      apiErrors: { state: 'Failed to fetch states. Please try again.' },
      options: { state: [] },
    });
  }
}

Props Reference

The DynamicForm component accepts the following props:

| Prop | Type | Required | Default | Description | | ------------------ | ---------- | -------- | ------- | ------------------------------------------------- | | questionGroups | Array | Yes | [] | Array of question groups defining form structure | | actionSchema | Array | No | [] | Array of action buttons configuration | | onSubmit | Function | No | - | Form submission handler | | onApiTrigger | Function | No | - | API trigger event handler | | apiConfig | Object | No | {} | API configuration options | | theme | Object | No | {} | Theme customization | | validationSchema | Array | No | [] | Additional validation rules | | pageRules | Array | No | [] | Page rules for conditional logic | | pageRuleGroup | Array | No | [] | Page rule groups with complex boolean expressions | | debug | Boolean | No | false | Enable debug mode | | formApiRef | Object | No | - | Ref object to access form API methods | | toastConfig | Object | No | {} | Toast configuration |

Props Details

questionGroups

An array of question group objects that defines the form structure. Each group contains questions (fields) to render.

const questionGroups = [
  {
    groupId: 'group1',
    groupName: 'Personal Information',
    questions: [
      /* question objects */
    ],
  },
];

actionSchema

An array of action button configurations for the form.

const actionSchema = [
  { text: 'Submit', action: 'submit' },
  { text: 'Previous', action: 'back' },
];

onSubmit

Callback function called when a form action is triggered. Receives the form state and action identifier.

const handleSubmit = (formState, action) => {
  // formState contains values, errors, etc.
  // action is the identifier of the action button clicked
  if (action === 'submit') {
    // Handle form submission
    console.log('Form values:', formState.values);
  }
};

onApiTrigger

Callback function for handling API trigger events from form fields. Receives trigger data object.

const handleApiTrigger = async (triggerData) => {
  // triggerData contains question, value, triggerType, and formState
  // Return formatted API response with updates to the form
};

apiConfig

Object containing API configuration options passed to the onApiTrigger handler.

const apiConfig = {
  baseUrl: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'Bearer YOUR_TOKEN',
  },
  timeout: 5000,
};

Field Types

The package supports a wide range of field types to handle various input requirements:

Basic Field Types

| Field Type | Component | Description | | ---------- | --------------- | ------------------------------------- | | text | TextField | Standard text input field | | textarea | TextAreaField | Multi-line text input | | email | EmailField | Email input with validation | | password | PasswordField | Password input with visibility toggle | | number | NumberField | Numeric input with constraints | | phone | PhoneField | Phone number input with formatting | | currency | CurrencyField | Monetary value input with formatting | | date | DateField | Date picker with calendar | | datetime | DateTimeField | Combined date and time picker |

Selection Field Types

| Field Type | Component | Description | | ----------------- | ---------------------- | ------------------------------------------- | | dropdown | SelectField | Dropdown selection menu | | combobox | ComboboxField | Searchable dropdown with multiple selection | | radio | RadioField | Single-selection radio buttons | | radioGroup | RadioGroupField | Group of radio buttons with custom layout | | radioSwitch | RadioSwitchField | Toggle-style radio selection | | radioTabButtons | RadioTabButtonsField | Tab-style radio selection | | checkbox | CheckboxField | Single checkbox | | checkboxGroup | CheckboxGroupField | Group of checkboxes with multiple selection |

Specialized Field Types

| Field Type | Component | Description | | ---------------- | --------------------- | ---------------------------------------------------------- | | address | AddressField | Address input with validation and auto-complete | | beneficiary | BeneficiaryField | Complex beneficiary information with percentage validation | | identification | IdentificationField | SSN/ITIN input with masking and validation | | file | FileField | File upload with preview | | pdf | PDFField | PDF file upload and display | | cardList | CardListField | Dynamic array of nested form fields | | display | DisplayTextField | Read-only text display | | hyperlink | HyperlinkField | Clickable hyperlink field | | button | ButtonField | Action button field | | spinner | SpinnerField | Loading indicator field |

Input Masking and Security

The React Dynamic Form Renderer includes advanced dual masking system for sensitive data fields, providing both input formatting and security masking capabilities.

Dual Masking System

The package implements a sophisticated dual masking approach:

  1. Input Masking: Real-time formatting during user input (e.g., 123-45-6789)
  2. Security Masking: Hide sensitive characters for privacy (e.g., ***-**-6789)

Supported Field Types

The following field types support dual masking:

| Field Type | Input Formatting | Security Masking | Eye Icon Toggle | | ------------- | ---------------- | ---------------- | --------------- | | ssn | ✅ | ✅ | ✅ | | itin | ✅ | ✅ | ✅ | | phonenumber | ✅ | ✅ | ✅ | | phone | ✅ | ✅ | ✅ | | currency | ✅ | ❌ | ❌ | | number | ✅ | ❌ | ❌ | | percentage | ✅ | ❌ | ❌ |

Configuration Options

Input Masking

{
  "questionId": "ssn_field",
  "label": "Social Security Number",
  "questionType": "ssn",
  "maskInput": "###-##-####",  // Input formatting pattern
  "required": true
}

Security Masking

{
  "questionId": "ssn_field",
  "label": "Social Security Number",
  "questionType": "ssn",
  "maskInput": "###-##-####",     // Input formatting
  "maskingType": "prefix",        // "prefix" or "suffix"
  "maskingLength": 6,             // Number of characters to mask
  "required": true
}

Masking Patterns

Common Patterns

| Field Type | Pattern | Example Input | Formatted | Security Masked | | ---------- | ---------------- | ------------- | -------------- | ------------------ | | SSN | ###-##-#### | 123456789 | 123-45-6789 | *--6789 | | Phone | (###) ###-#### | 1234567890 | (123) 456-7890 | (123) 456-**** | | ITIN | ###-##-#### | 912456789 | 912-45-6789 | *--6789 |

Custom Patterns

You can define custom masking patterns using:

  • # for digits (0-9)
  • * for any character
  • Special characters (hyphens, parentheses, etc.) are preserved

Eye Icon Toggle

Fields with security masking automatically display an eye icon (👁️/🙈) that allows users to toggle between:

  • Masked View: Shows security masked value (e.g., ***-**-6789)
  • Original View: Shows formatted value (e.g., 123-45-6789)

Accessibility Features

  • Keyboard Navigation: Eye icon supports Tab navigation
  • Keyboard Activation: Toggle with Enter or Space keys
  • Screen Reader Support: Proper ARIA labels and descriptions
  • Focus Management: Clear focus indicators

Advanced Configuration

Prefix vs Suffix Masking

// Prefix masking (mask first N characters)
{
  "maskingType": "prefix",
  "maskingLength": 6,
  // "123-45-6789" → "***-**-6789"
}

// Suffix masking (mask last N characters)
{
  "maskingType": "suffix",
  "maskingLength": 4,
  // "123-45-6789" → "123-45-****"
}

Validation with Masking

The masking system integrates seamlessly with validation:

{
  "questionId": "ssn_field",
  "maskInput": "###-##-####",
  "maskingType": "prefix",
  "maskingLength": 6,
  "minLength": 9,           // Validates unmasked length
  "maxLength": 9,           // Validates unmasked length
  "required": true,
  "validation": {
    "pattern": "^\\d{9}$",  // Validates digits only
    "message": "Please enter a valid 9-digit SSN"
  }
}

Implementation Notes

  • Real-time Processing: Masking is applied during user input, not just on blur
  • Form Integration: Masked values are properly integrated with form state
  • Performance: Optimized for large forms with multiple masked fields
  • Browser Compatibility: Works across all modern browsers
  • Mobile Support: Touch-friendly eye icon and responsive design

Troubleshooting

Common Issues

  1. Masking Not Applied: Ensure maskInput pattern is correctly formatted
  2. Eye Icon Missing: Verify both maskingType and maskingLength are set
  3. Validation Errors: Check that validation patterns match unmasked values
  4. Performance Issues: Avoid overly complex masking patterns in large forms

Debug Tips

// Enable console logging for masking operations
console.log('Masked Value:', maskedValue);
console.log('Display Value:', displayValue);
console.log('Mask Config:', maskConfig);

Conditional Logic

The Dynamic Form Renderer provides powerful conditional logic capabilities through the pageRules and pageRuleGroup props.

PageRules

Page rules define individual conditions and actions for dynamic form behavior:

const pageRules = [
  {
    ruleId: 'rule1',
    triggerQuestionIds: ['101'],
    comparison: 'equals',
    compareValue: 'yes',
    action: 'showquestion',
    targetQuestionIds: ['201'],
  },
  // more rules...
];

Enhanced PageRuleGroup

The enhanced pageRuleGroup feature provides sophisticated boolean expressions for complex conditional logic:

// New array-based format with complex boolean expressions
const pageRuleGroup = [
  {
    questionId: '40374',
    evaluationExpression: 'rule1 AND rule2',
  },
  {
    questionId: '40555',
    evaluationExpression: '(rule1 AND rule2) OR rule3',
  },
];

Key Features

  • Complex Boolean Logic: Combine multiple rules with AND/OR operations
  • Parentheses Support: Control operator precedence for complex evaluations
  • Backward Compatibility: Supports legacy object-based format

Expression Syntax

  • Operators: AND, OR (case-insensitive)
  • Rule References: Use ruleId values from the pageRules array
  • Parentheses: Group expressions to control evaluation order

Examples

// Simple AND condition
{
  questionId: "40374",
  evaluationExpression: "rule1 AND rule2"
}

// Complex nested conditions
{
  questionId: "40890",
  evaluationExpression: "(rule1 AND rule2) OR (rule3 AND rule4)"
}

For detailed documentation on the enhanced pageRuleGroup feature, see the PageRuleGroupEnhancement.md file.

Advanced Configuration

Performance & Engine Options

When using the provider-level API, you can tune performance and engine behavior.

FormProvider props

  • performanceConfig

    • debounceDelayMs: number (default: 120) – Debounce for textual input processing
  • engineOptions

    • enablePerActionGating: boolean (default: true) – Toggle per-action gating via questionRules
    • maxEvaluationDepth: number (default: 20) – Reflexive evaluation depth guard

Example

import { FormProvider } from '@ilife-tech/react-application-flow-renderer/context/FormContext';
import DynamicFormInner from '@ilife-tech/react-application-flow-renderer/src/components/core/components/DynamicFormInner';

<FormProvider
  formSchema={questionGroups}
  questionRuleDetails={questionRuleDetails}
  questionRules={questionRules}
  performanceConfig={{ debounceDelayMs: 150 }}
  engineOptions={{ enablePerActionGating: true, maxEvaluationDepth: 20 }}
>
  <DynamicFormInner schema={questionGroups} actionSchema={actionSchema} onSubmit={handleSubmit} />
</FormProvider>;

Note: The top-level DynamicForm abstraction may not expose these knobs directly. Use FormProvider for fine-grained control.

Toast Notifications

The Dynamic Form Renderer uses react-toastify for displaying notifications such as error messages, warnings, and success confirmations.

Toast Configuration

The form renderer integrates with your application's react-toastify setup. Important: Your host application MUST include a <ToastContainer /> component for notifications to appear.

// Required: Import react-toastify in your host application
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

const App = () => {
  return (
    <>
      {/* Required: Add ToastContainer at the root level */}
      <ToastContainer />

      <DynamicForm
        questionGroups={questionGroups}
        toastConfig={{
          disableToasts: false, // Disable toast notifications completely (default: false)
          useHostToastify: true, // Whether to use host app's react-toastify (default: true)
        }}
        onSubmit={handleSubmit}
        onApiTrigger={handleApiTrigger}
      />
    </>
  );
};

API Response Format for Toast Notifications

When implementing the onApiTrigger handler, you can return success and error messages that will be displayed as toast notifications:

// Example of API trigger handler with toast notifications
const handleApiTrigger = async (triggerData) => {
  try {
    // Your API logic here
    const response = await myApiCall(triggerData);

    // Return success message (will display as green toast)
    return {
      success: {
        message: 'Operation completed successfully',
        // You can also include field-specific success messages
        // fieldName: 'Field updated successfully'
      },
      // Other response properties like updates, options, visibility...
    };
  } catch (error) {
    // Return error message (will display as red toast)
    return {
      errors: {
        error: 'An error occurred during the operation',
        // You can also include field-specific error messages
        // fieldName: 'Invalid value for this field'
      },
    };
  }
};

Integration with Host Applications

If your application already uses react-toastify, the renderer will:

  1. Use your application's react-toastify instance
  2. Respect your toast styling and configuration

Important: Only one <ToastContainer /> should be rendered in your application tree. The package will not render its own ToastContainer - this must be provided by the host application.

Toast Configuration

The Dynamic Form Renderer uses react-toastify for displaying notifications. You can configure toast behavior using the toastConfig prop:

<DynamicForm
  // Other props...
  toastConfig={{
    disableToasts: false, // Set to true to disable all toast notifications
    useHostToastify: true, // Set to true to use host application's ToastContainer
    // Additional toast options can be passed here
  }}
/>

ToastContainer Requirements

To properly display toast notifications:

  1. Import ToastContainer and CSS in your host application:

    import { ToastContainer } from 'react-toastify';
    import 'react-toastify/dist/ReactToastify.css';
  2. Add the ToastContainer to your app's root component:

    function App() {
      return (
        <>
          <YourAppComponents />
          <ToastContainer position="top-right" autoClose={5000} />
        </>
      );
    }

API Response Format for Toast Messages

When implementing custom API triggers with onApiTrigger, your handler should return a promise that resolves to an object with the following structure to properly display toast notifications:

const handleApiTrigger = async (triggerData) => {
  // Your API call implementation
  try {
    // Process API call
    return {
      // Success messages that will trigger toast notifications
      success: {
        message: 'General success message', // General success toast
        fieldId: 'Field-specific success', // Field-specific success toast
      },
      // Optional field updates
      updates: {
        fieldId1: 'new value', // Update field values
        fieldId2: [{ name: 'Option 1', value: 'opt1' }], // Update field options
      },
    };
  } catch (error) {
    return {
      // Error messages that will trigger toast notifications
      errors: {
        error: 'General error message', // General error toast
        fieldId: 'Field-specific error', // Field-specific error toast
      },
    };
  }
};

Compatibility Notes

  • The Dynamic Form Renderer is compatible with react-toastify v9.1.x for React 17 applications
  • For React 18+ applications, you can use react-toastify v9.x or higher
  • Toast styles will follow your application's theme if available
  • Error and success messages from API responses and form validation will use appropriate toast types

Troubleshooting

  • No toasts appear: Ensure your application has a <ToastContainer /> component and has imported the CSS
  • _useSyncExternalStore error: This usually indicates a version compatibility issue between React and react-toastify. For React 17, use react-toastify v9.1.x
  • Multiple toasts appear: Ensure you only have one <ToastContainer /> in your application

Theme Customization

The DynamicForm component supports deep theme customization through the theme prop:

const customTheme = {
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
    fontSize: '16px',
    lineHeight: 1.5,
    headings: {
      h1: { fontSize: '2rem', fontWeight: 700 },
      h2: { fontSize: '1.5rem', fontWeight: 600 },
      h3: { fontSize: '1.25rem', fontWeight: 600 },
    },
  },
  palette: {
    primary: {
      main: '#0066cc',
      light: '#3383d6',
      dark: '#004c99',
      contrastText: '#ffffff',
    },
    error: {
      main: '#d32f2f',
      light: '#ef5350',
      dark: '#c62828',
      contrastText: '#ffffff',
    },
    success: {
      main: '#2e7d32',
      light: '#4caf50',
      dark: '#1b5e20',
      contrastText: '#ffffff',
    },
  },
  spacing: {
    unit: '8px',
    form: '24px',
    field: '16px',
  },
  shape: {
    borderRadius: '4px',
  },
  breakpoints: {
    xs: 0,
    sm: 600,
    md: 960,
    lg: 1280,
    xl: 1920,
  },
  fields: {
    input: {
      height: '40px',
      padding: '8px 12px',
      fontSize: '16px',
      borderRadius: '4px',
      borderColor: '#cccccc',
      focusBorderColor: '#0066cc',
      errorBorderColor: '#d32f2f',
      background: '#ffffff',
    },
    label: {
      fontSize: '14px',
      fontWeight: 600,
      marginBottom: '4px',
      color: '#333333',
    },
    helperText: {
      fontSize: '12px',
      color: '#666666',
      marginTop: '4px',
    },
    error: {
      fontSize: '12px',
      color: '#d32f2f',
      marginTop: '4px',
    },
  },
  buttons: {
    primary: {
      background: '#0066cc',
      color: '#ffffff',
      hoverBackground: '#004c99',
      fontSize: '16px',
      padding: '8px 24px',
      borderRadius: '4px',
    },
    secondary: {
      background: '#f5f5f5',
      color: '#333333',
      hoverBackground: '#e0e0e0',
      fontSize: '16px',
      padding: '8px 24px',
      borderRadius: '4px',
    },
  },
};

// Usage
<DynamicForm
  questionGroups={questionGroups}
  theme={customTheme}
  // Other props
/>;

Validation Configuration

Client-side Validation

Validation can be defined directly in the form schema or through a separate validation schema:

// In form schema
const questionGroups = [
  {
    groupId: 'personalInfo',
    questions: [
      {
        questionId: 'email',
        questionType: 'email',
        label: 'Email Address',
        validation: {
          required: true,
          email: true,
          requiredMessage: 'Email is required',
          errorMessage: 'Please enter a valid email address',
          pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
          patternMessage: 'Email format is invalid',
        },
      },
      {
        questionId: 'password',
        questionType: 'password',
        label: 'Password',
        validation: {
          required: true,
          minLength: 8,
          minLengthMessage: 'Password must be at least 8 characters',
          pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
          patternMessage:
            'Password must include uppercase, lowercase, number and special character',
        },
      },
    ],
  },
];

// Separate validation schema
const validationSchema = [
  {
    field: 'confirmPassword',
    rules: [
      'required',
      {
        name: 'matchesField',
        params: ['password'],
        message: 'Passwords must match',
      },
    ],
  },
];

Available Validation Message Types

| Message Type | Description | | ------------------ | --------------------------------------------------- | | requiredMessage | Displayed when a required field is empty | | errorMessage | General error message for field validation | | lengthMessage | Error for overall length validation | | minLengthMessage | Error for minimum length validation | | maxLengthMessage | Error for maximum length validation | | patternMessage | Error for regex pattern validation | | typeMessage | Error for type validation (email, number, etc.) | | formatMessage | Error for format validation (phone, currency, etc.) |

CardList Configuration

The CardListField component enables dynamic form arrays with complete validation and state management:

const cardListQuestion = {
  questionId: 'beneficiaries',
  questionType: 'cardList',
  label: 'Beneficiaries',
  addCardLabel: 'Add Beneficiary',
  cardSchema: [
    {
      groupId: 'beneficiaryInfo',
      questions: [
        {
          questionId: 'name',
          questionType: 'text',
          label: 'Full Name',
          validation: { required: true },
        },
        {
          questionId: 'relationship',
          questionType: 'dropdown',
          label: 'Relationship',
          options: [
            { name: 'Spouse', value: 'spouse' },
            { name: 'Child', value: 'child' },
            { name: 'Parent', value: 'parent' },
            { name: 'Other', value: 'other' },
          ],
          validation: { required: true },
        },
        {
          questionId: 'percentage',
          questionType: 'currency',
          label: 'Percentage',
          validation: {
            required: true,
            min: 0,
            max: 100,
            minMessage: 'Percentage must be positive',
            maxMessage: 'Percentage cannot exceed 100%',
          },
        },
      ],
    },
  ],
  validation: {
    minCards: 1,
    maxCards: 5,
    minCardsMessage: 'At least one beneficiary is required',
    maxCardsMessage: 'Maximum of 5 beneficiaries allowed',
    totalValidator: (cards) => {
      // Calculate total percentage across all cards
      const total = cards.reduce((sum, card) => {
        const percentage = parseFloat(card.values.percentage) || 0;
        return sum + percentage;
      }, 0);

      // Validate total is 100%
      if (Math.abs(total - 100) > 0.01) {
        return 'Total percentage must equal 100%';
      }

      return null; // No error
    },
  },
};

CardList State Management

Each card in a CardList manages its own state, including:

  • Form values
  • Validation errors
  • Visibility rules
  • Disabled states

You can access the CardList state through the form context:

const MyForm = () => {
  const formApiRef = useRef(null);

  const handleSubmit = (formState) => {
    // Access cardList data as an array of card states
    const beneficiaries = formState.values.beneficiaries;
    console.log('Beneficiary cards:', beneficiaries);
  };

  return (
    <DynamicForm questionGroups={questionGroups} onSubmit={handleSubmit} formApiRef={formApiRef} />
  );
};

Troubleshooting

Common Issues

API Integration Issues

| Issue | Solution | | ------------------------------- | ---------------------------------------------------------------------------------- | | API calls not triggering | Ensure apiTrigger configuration in field schema includes correct triggerEvents | | API responses not updating form | Verify that the onApiTrigger handler returns the correct response structure | | Multiple concurrent API calls | Use request queuing as shown in the advanced API integration example | | CORS errors | Configure proper CORS headers in your API or use a proxy service |

Validation Issues

| Issue | Solution | | ------------------------------ | -------------------------------------------------------------------------------- | | ValidationSchema not applying | Ensure field names in validation schema match questionIds in form schema | | Custom validation not working | Check that validation functions return error message strings, not boolean values | | Form submitting with errors | Verify that onSubmit handler checks formState.errors before proceeding | | Cross-field validation failing | Use the validationSchema approach for dependencies between fields |

Rendering Issues

| Issue | Solution | | ------------------------------------ | -------------------------------------------------------------- | | Fields not respecting column layout | Check columnCount setting in group configuration | | Theme customizations not applying | Ensure theme prop structure matches expected format | | Responsive layout issues | Verify breakpoints in theme configuration | | Fields rendering in unexpected order | Check if columns are properly balanced in group configurations |

Debugging Tips

Using Debug Mode

Enable debug mode to get verbose console output of form state changes:

<DynamicForm
  questionGroups={questionGroups}
  debug={true}
  // Other props
/>

Accessing Form API

Use the formApiRef prop to access form methods imperatively:

const MyForm = () => {
  const formApiRef = useRef(null);

  const handleClick = () => {
    // Access form API methods
    const api = formApiRef.current;

    // Get current form state
    const state = api.getState();
    console.log('Form values:', state.values);

    // Set field value
    api.setValue('email', '[email protected]');

    // Validate specific field
    api.validateField('email');

    // Validate entire form
    api.validateForm().then((errors) => {
      console.log('Validation errors:', errors);
    });
  };

  return (
    <>
      <DynamicForm questionGroups={questionGroups} formApiRef={formApiRef} />
      <button onClick={handleClick}>Debug Form</button>
    </>
  );
};

Performance Optimization

Large Forms

For large forms with many fields:

  • Split form into multiple pages or sections
  • Use the visibleGroups prop to only render visible question groups
  • Implement form sections with conditional rendering
  • Consider lazy loading for complex field components

API Efficiency

Improve API integration performance:

  • Implement response caching as shown in API integration examples
  • Use debounce for onChange API triggers
  • Batch API calls when possible
  • Implement request cancellation for outdated requests

Examples & Best Practices

Real-world Implementation Patterns

Multi-step Form Wizard

Implement a form wizard with progress tracking:

import React, { useState } from 'react';
import DynamicForm from '@ilife-tech/react-application-flow-renderer';

const MultiStepForm = () => {
  const [step, setStep] = useState(0);
  const [formData, setFormData] = useState({});

  // Define steps with their schemas
  const steps = [
    {
      title: 'Personal Information',
      schema: [
        {
          groupId: 'personal',
          questions: [
            /* Personal info questions */
          ],
        },
      ],
      actions: [{ text: 'Next', action: 'next' }],
    },
    {
      title: 'Contact Details',
      schema: [
        {
          groupId: 'contact',
          questions: [
            /* Contact info questions */
          ],
        },
      ],
      actions: [
        { text: 'Back', action: 'back' },
        { text: 'Next', action: 'next' },
      ],
    },
    {
      title: 'Review & Submit',
      schema: [
        {
          groupId: 'review',
          questions: [
            /* Review fields */
          ],
        },
      ],
      actions: [
        { text: 'Back', action: 'back' },
        { text: 'Submit', action: 'submit' },
      ],
    },
  ];

  const currentStep = steps[step];

  const handleAction = (state, action) => {
    if (action === 'next' && step < steps.length - 1) {
      // Store current step data
      setFormData((prev) => ({ ...prev, ...state.values }));
      setStep(step + 1);
    } else if (action === 'back' && step > 0) {
      setStep(step - 1);
    } else if (action === 'submit') {
      // Combine all step data and submit
      const finalData = { ...formData, ...state.values };
      console.log('Submitting form:', finalData);
      // Submit to server
    }
  };

  return (
    <div className="multi-step-form">
      <div className="progress-bar">
        {steps.map((s, i) => (
          <div key={i} className={`step ${i <= step ? 'active' : ''}`}>
            {s.title}
          </div>
        ))}
      </div>

      <DynamicForm
        questionGroups={currentStep.schema}
        actionSchema={currentStep.actions}
        initialValues={formData}
        onSubmit={handleAction}
      />
    </div>
  );
};

Dynamic API-driven Form

Create a form that dynamically loads its schema from an API:

import React, { useState, useEffect } from 'react';
import DynamicForm from '@ilife-tech/react-application-flow-renderer';
import { handleApiTrigger } from './services/apiService';

const DynamicApiForm = ({ formId }) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [formSchema, setFormSchema] = useState(null);

  useEffect(() => {
    async function fetchFormSchema() {
      try {
        setLoading(true);

        // Fetch form schema from API
        const response = await fetch(`https://api.example.com/forms/${formId}`);

        if (!response.ok) {
          throw new Error(`Failed to load form: ${response.statusText}`);
        }

        const data = await response.json();

        setFormSchema({
          questionGroups: data.groups,
          actionSchema: data.actions,
          initialValues: data.initialValues || {},
        });
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchFormSchema();
  }, [formId]);

  const handleSubmit = async (formState, action) => {
    if (action === 'submit') {
      try {
        // Submit form data to API
        const response = await fetch('https://api.example.com/submissions', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            formId,
            values: formState.values,
          }),
        });

        const result = await response.json();
        console.log('Submission result:', result);
      } catch (err) {
        console.error('Submission error:', err);
      }
    }
  };

  if (loading) {
    return <div className="loading">Loading form...</div>;
  }

  if (error) {
    return <div className="error">{error}</div>;
  }

  return (
    <DynamicForm
      questionGroups={formSchema.questionGroups}
      actionSchema={formSchema.actionSchema}
      initialValues={formSchema.initialValues}
      onSubmit={handleSubmit}
      onApiTrigger={handleApiTrigger}
    />
  );
};

Security Best Practices

  1. API Key Management:

    • Never hardcode API keys in your JavaScript code
    • Use environment variables for all sensitive credentials
    • Consider using token exchange services for third-party APIs
  2. Sensitive Data Handling:

    • Use the built-in masking features for fields like SSN and credit card numbers
    • Avoid storing sensitive data in localStorage or sessionStorage
    • Clear sensitive form data after submission
  3. Input Validation:

    • Always validate both on client and server side
    • Use strong validation rules for sensitive fields
    • Implement proper error handling for failed validations

Accessibility Guidelines

  1. Screen Reader Support:

    • All form fields include proper ARIA attributes
    • Error messages are announced to screen readers
    • Focus management follows a logical flow
  2. Keyboard Navigation:

    • All interactive elements are focusable
    • Tab order follows a logical sequence
    • Custom components support keyboard interactions
  3. Visual Considerations:

    • Color is not the only means of conveying information
    • Sufficient color contrast for text and UI elements
    • Form supports browser zoom and text resizing

Conclusion

The React Dynamic Form Renderer package provides a comprehensive solution for creating dynamic, interactive forms with advanced validation, conditional logic, and third-party API integration. By leveraging the JSON schema approach, you can rapidly develop complex forms without sacrificing flexibility or control.

For more detailed documentation, refer to the user guide or check out the example implementations included with the package.

We welcome contributions and feedback! Please file issues or submit pull requests on our GitHub repository.