When adding a gadget, you should consider all of the following items.
When creating a new gadget, there are a few main places to pay attention to:
Use Functional Components
New gadgets should use functional components with hooks, not class components:
useState for local state managementuseRef for tracking previous values and DOM referencesuseEffect unless absolutely necessary (external system sync only)app/src/formbot/gadgets/phone-number/edit.jsxAvoid Unnecessary Debouncing
Only debounce if you have a specific performance reason. Most gadgets don't need it. Immediate feedback is better for user experience.
Use Tailwind CSS
All new gadgets should use Tailwind utility classes:
className='w-full border-none bg-none px-4 pb-4 outline-none'All gadgets must include proper accessibility features:
ARIA Attributes
aria-labelledby - References the field labelaria-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 fieldsaria-invalid - Set to "true" when validation errors existError 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 numberstype='email' for email addressestype='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
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
Abstract business logic into utility files - this pattern is used by Currency, Checkboxes, Table, and other gadgets:
Why utils.js?
currency/utils.js, checkboxes/utils.jsx, table/utils.jsx, phone-number/utils.jsUtility functions should be:
// Good: Pure functionexport const formatPhoneNumber = (digits, country) => {// formatting logicreturn formattedNumber}// Bad: Side effectsexport const formatPhoneNumber = (digits, country) => {setGlobalState(digits) // ❌ Side effectreturn formattedNumber}
All gadgets should have thorough test coverage:
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.jsxapp/src/formbot/gadgets/your-gadget/__tests__/utils.test.jsxWhen gadget functionality requires complex logic (phone formatting, date handling, etc.), prefer industry-standard libraries over custom implementations.
ONLY use libraries with permissive licenses:
Before adding a dependency, check node_modules/[package]/package.json for the "license" field.
libphonenumber-js (MIT)date-fns (MIT)lodash (MIT)Document library choices and their purposes in code comments.
When getting started, reference these gadgets:
For basic structure:
app/src/formbot/gadgets/text/) - Simple text input, shows file organizationFor modern implementation patterns:
app/src/formbot/gadgets/phone-number/) - Demonstrates 2025 best practices:For complex features:
app/src/formbot/gadgets/currency/) - Complex formatting and calculationsapp/src/formbot/gadgets/table/) - Advanced data structuresapp/src/formbot/gadgets/repeater/) - Dynamic nested contentGadgets live in app/src/formbot/gadgets. The manifest file ties everything together, and gadgets are registered in app/src/formbot/index.jsx.
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.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.