Gadget Requirements

When adding a gadget, you should consider all of the following items.

  • Config Mode (UI)
  • Edit Mode (UI)
  • View Mode (UI)
  • Mobile Support (UI)
  • Accessibility (UI)
  • Conditional Visibility (UI,API,Forms)
  • Clientside Validation (UI)
  • Serverside Validation (Forms)
  • Document List View (UI)
  • Document List Filter (UI)
  • Document List Sort (UI)
  • CSV Download Support (API)
  • Export to PDF Support (PDF)
  • Anonymous Forms Support (UI)
  • Workflow Simulator Support (UI)
  • Workflow Notification Support (UI,Workflows)
  • Workflow Conditional Logic Support (UI,Workflows)

Creating a New Gadget

When creating a new gadget, there are a few main places to pay attention to:

Modern Best Practices (2025)

React Component Patterns

Use Functional Components

New gadgets should use functional components with hooks, not class components:

  • Use useState for local state management
  • Use useRef for tracking previous values and DOM references
  • Avoid useEffect unless absolutely necessary (external system sync only)
  • Example: app/src/formbot/gadgets/phone-number/edit.jsx

Avoid Unnecessary Debouncing

Only debounce if you have a specific performance reason. Most gadgets don't need it. Immediate feedback is better for user experience.

Styling

Use Tailwind CSS

All new gadgets should use Tailwind utility classes:

  • Example: className='w-full border-none bg-none px-4 pb-4 outline-none'

Accessibility Requirements

All gadgets must include proper accessibility features:

ARIA Attributes

  • aria-labelledby - References the field label
  • aria-describedby - References all descriptive content (help text, format hints, error messages). Use space-separated IDs for multiple descriptions (e.g., aria-describedby="field-help field-error")
  • aria-required - Indicates required fields
  • aria-invalid - Set to "true" when validation errors exist

Error Announcements

Error messages must have role="alert" and an ID referenced in aria-describedby so screen readers can announce them properly.

Semantic HTML

Use appropriate input types for better mobile keyboard support:

  • type='tel' for phone numbers
  • type='email' for email addresses
  • type='number' for numeric input (when appropriate)

Keyboard Support

Standard keyboard navigation must work without requiring a mouse.

Example: See PhoneNumber gadget's ARIA implementation in app/src/formbot/gadgets/phone-number/edit.jsx

Code Organization

File Structure

app/src/formbot/gadgets/your-gadget/
├── edit.jsx # UI and user interaction
├── view.jsx # Display logic
├── config.jsx # Configuration UI
├── utils.js # Business logic (formatting, parsing, calculations)
├── validation.jsx # Validation rules
├── progressive-disclosure.jsx
├── icon.svg.jsx # Gadget icon
└── manifest.jsx # Ties everything together

Separation of Concerns

Abstract business logic into utility files - this pattern is used by Currency, Checkboxes, Table, and other gadgets:

Why utils.js?

  • Keeps components focused on UI
  • Makes business logic testable in isolation
  • Pure functions are easier to reason about
  • Examples: currency/utils.js, checkboxes/utils.jsx, table/utils.jsx, phone-number/utils.js

Pure Functions in Utils

Utility functions should be:

  • Side-effect free - No external state modification
  • Deterministic - Same input always produces same output
  • Testable - Easy to unit test in isolation
// Good: Pure function
export const formatPhoneNumber = (digits, country) => {
// formatting logic
return formattedNumber
}
// Bad: Side effects
export const formatPhoneNumber = (digits, country) => {
setGlobalState(digits) // ❌ Side effect
return formattedNumber
}

Testing

Comprehensive Coverage Required

All gadgets should have thorough test coverage:

  • Component tests - User interactions, rendering, props changes
  • Utility function tests - All pure functions in utils.js
  • Edge cases - Empty values, null, undefined, max length, special characters
  • Accessibility tests - External value sync, ARIA attributes, error states

Aim for 15-20+ tests for complex gadgets.

Example: PhoneNumber gadget has 19+ comprehensive tests covering formatting, deletion, digit limits, and accessibility.

Test files location:

  • app/src/formbot/gadgets/your-gadget/__tests__/edit.test.jsx
  • app/src/formbot/gadgets/your-gadget/__tests__/utils.test.jsx

Using Third-Party Libraries

When gadget functionality requires complex logic (phone formatting, date handling, etc.), prefer industry-standard libraries over custom implementations.

License Requirements ⚠️

ONLY use libraries with permissive licenses:

  • Approved: MIT, BSD, Apache 2.0, ISC
  • Prohibited: GPL, AGPL, LGPL, proprietary licenses, or any license that:
    • Requires disclosure of source code
    • Requires payment or royalties
    • Requires releasing derivative works under the same license
    • Has copyleft provisions

Before adding a dependency, check node_modules/[package]/package.json for the "license" field.

  • Phone formatting: libphonenumber-js (MIT)
  • Date handling: date-fns (MIT)
  • Utilities: lodash (MIT)

Document library choices and their purposes in code comments.

Example Gadgets

When getting started, reference these gadgets:

For basic structure:

  • Text gadget (app/src/formbot/gadgets/text/) - Simple text input, shows file organization

For modern implementation patterns:

  • PhoneNumber gadget (app/src/formbot/gadgets/phone-number/) - Demonstrates 2025 best practices:
    • Functional components with hooks
    • Tailwind CSS styling
    • Proper ARIA accessibility
    • Business logic in utils.js
    • Comprehensive test coverage (19+ tests)

For complex features:

  • Currency gadget (app/src/formbot/gadgets/currency/) - Complex formatting and calculations
  • Table gadget (app/src/formbot/gadgets/table/) - Advanced data structures
  • Repeater gadget (app/src/formbot/gadgets/repeater/) - Dynamic nested content

builder-ui

Gadgets live in app/src/formbot/gadgets. The manifest file ties everything together, and gadgets are registered in app/src/formbot/index.jsx.

builder-api-node

Gadgets are defined in server/gadgets and exported from server/gadgets/index.js. The main configuration options are as follows:

  • defaultValue: Pretty straightforward - a good place to put a sane default.
  • progressiveDisclosure: Defines the rules specific to this gadget for progressive disclosure.
  • validateShape: Defines the required structure of the value.

forms-api

Gadgets live in server/lib/gadgets and exported from server/lib/gadgets/index.js. There is unfortunately a lot of repetition in the gadget definition defined here and in builder-api-node. One big difference is that gadgets defined here accept a validations key for server-side validations. However, be forewarned that server-side validation error messages do not currently get rendered by the client app.