Skip to main content
FormEngine is a React library that renders forms from JSON schemas. Custom components are how you extend that schema with your own React components β€” signature pads, branded inputs, third-party widgets, anything your product needs. The key design choice: registering a custom component is a single define() call on a plain React function. No provider, no class extension, no parallel schema, no widget registry β€” the component is immediately addressable from JSON, participates in data binding and validation, and works in the visual Designer automatically.

The 10-line story

This is the entire surface area of registering a valued custom input that reads and writes form data:
import { define, event, string } from '@react-form-builder/core'

const MyInput = ({ value, onChange }) => (
  <input value={value ?? ''} onChange={e => onChange(e.target.value)} />
)

export const myInput = define(MyInput, 'MyInput')
  .props({
    value: string.valued,
    onChange: event
  })
  .build()
Add myInput.model to your view (createView([myInput.model, ...])) and the component is now a first-class citizen of the form:
  • Any JSON schema can reference it as "type": "MyInput".
  • Data binding is automatic because value: string.valued marks the bound property.
  • The FormEngine Designer picks it up in the component palette β€” non-developers can drag it into forms.
  • Validation, error display, conditional logic, and events all work without extra wiring.
Compare this against the equivalent registration in Formik (useField hook + manual error rendering + no JSON), RJSF (JSON Schema + parallel uiSchema + widgets registry), and SurveyJS (Question class + Serializer + ElementFactory + ReactQuestionFactory) in the Custom Components Comparison β€” side-by-side code, API surface, and portability analysis. A useful page to forward to your tech lead.

What Are Custom Components?

Custom components are React components that you wrap with FormEngine’s define function to make them available in the form viewer. Once defined, they can be used in JSON form definitions just like built-in components. Example: Simple Custom Button

// simple custom button component
const MyButton = (props: any) => (
  <button {...props}/>
)

// defining the metadata of a simple button via the define function
const myButton = define(MyButton, 'MyButton')
  .props({
    children: string.default('Click Me'),
    onClick: event
  })
  .build()
Now you can use it in your form JSON:
{
  "key": "submitButton",
  "type": "MyButton",
  "props": {
    "children": {
      "value": "Submit Form"
    }
  }
}
live
function App() {
  // simple custom button component
  const MyButton = (props) => (
    <button {...props}/>
  )

  // defining the metadata of a simple button via the define function
  const myButton = define(MyButton, 'MyButton')
    .props({
      children: string.default('Click Me'),
      onClick: event
    })
    .build()

  // creating a view, set of components for displaying a form
  const myView = createView([myButton.model])

  const form = {
    form: {
      key: 'Screen',
      type: 'Screen',
      children: [
        {
          key: 'submitButton',
          type: 'MyButton',
          props: {
            children: {
              value: 'Submit Form'
            }
          }
        }
      ]
    }
  }

  const getForm = useCallback(() => JSON.stringify(form), [form])

  return <FormViewer
    view={myView}
    getForm={getForm}
  />
}

Why Create Custom Components?

1. Unique UI Requirements

Your application may need specialized UI elements that aren’t available in standard component libraries.
// A signature pad component for digital signatures
const SignaturePad = ({value, onChange}) => {
  const canvasRef = useRef()

  // Signature drawing logic...
  return <canvas ref={canvasRef}/>
}

export const signaturePad = define(SignaturePad, 'SignaturePad')
  .props({
    value: string.valued, // Data binding support
    onChange: event
  })

2. Business Logic Encapsulation

Embed business logic directly in components for consistency.
// A product selector that fetches products from your API
const ProductSelector = ({category, onProductSelect}) => {
  const [products, setProducts] = useState([])

  useEffect(() => {
    fetch(`/api/products?category=${category}`)
      .then(res => res.json())
      .then(setProducts);
  }, [category])

  return (
    <select onChange={e => onProductSelect(e.target.value)}>
      {products.map(p => (
        <option key={p.id} value={p.id}>{p.name}</option>
      ))}
    </select>
  )
}

3. Third-Party Integration

Wrap third-party React components to use them in your forms.

const WrappedDatePicker = ({value, onChange, ...props}) => (
  <DatePicker
    value={value}
    onDateChange={onChange}
    {...props}
  />
)

export const customDatePicker = define(WrappedDatePicker, 'CustomDatePicker')
  .props({
    value: date.valued,
    onChange: event,
    minDate: string,
    maxDate: string
  })

4. Consistent Branding

Ensure all form elements match your design system.
// A branded button that matches your company's design
const BrandedButton = ({children, variant, ...props}) => (
  <button
    className={`branded-btn branded-btn--${variant}`}
    {...props}
  >
    <span className="branded-btn__icon">πŸ”·</span>
    {children}
  </button>
);

export const brandedButton = define(BrandedButton, 'BrandedButton')
  .props({
    children: string,
    variant: oneOf('primary', 'secondary', 'danger').default('primary')
  });

Component Development Journey

This guide will walk you through creating custom components of increasing complexity:

πŸ“– Section 1: Getting Started

  • Setting up your development environment
  • Creating your first simple component
  • Understanding the define() function
  • Adding components to your view

πŸ“– Section 2: Simple Components

  • Components without data binding
  • Working with basic props (string, number, boolean)
  • Adding visual customization (CSS, styling)
  • Component categories and organization

πŸ“– Section 3: Valued Components (Data Binding)

  • Understanding data binding concepts
  • Creating components that read/write form data
  • Using valued properties
  • Two-way data synchronization

πŸ“– Section 4: Components with Events

  • Handling user interactions
  • Creating custom events
  • Event parameters and data
  • Common action integration

πŸ“– Section 5: Components with Validation

  • Adding validation rules to components
  • Displaying validation errors
  • Custom validation logic
  • Validation schemas

πŸ“– Section 6: Advanced Patterns

  • Composite components (components with children)
  • Accessing form context
  • Cross-component communication
  • Performance optimization techniques

πŸ“– Section 7: Component Lifecycle

  • Mount/unmount events
  • Data change detection
  • Cleanup and resource management
  • Using React hooks with FormEngine

πŸ“– Section 8: Styling Custom Components

  • Inline styles vs CSS classes
  • Responsive design
  • Theme integration
  • CSS-in-JS approaches

πŸ“– Section 9: Testing

  • Unit testing components
  • Integration testing with forms
  • Mocking form context
  • Test utilities and helpers

Prerequisites

Before creating custom components, you should be familiar with:

Getting Help

If you get stuck:

Next Steps

Ready to start? Use the examples in this guide to create your first custom component.
Documentation Structure:
  • Getting Started β†’ Basic setup and first component
  • Simple Components β†’ Static, display-only components
  • Valued Components β†’ Components with data binding
  • Components with Events β†’ Interactive components
  • Components with Validation β†’ Validated components
  • Advanced Patterns β†’ Complex component patterns
  • Component Lifecycle β†’ Lifecycle management
  • Styling β†’ Visual customization
  • Testing β†’ Quality assurance
Let’s build something amazing! πŸš€
Last modified on April 16, 2026