On this page I, Fabian Hiller, as the author of this library, share the philosophy behind it and also go into features and design decisions. If you want to quickly put a form together and the details are secondary for now, you can just skip this page.
Started in 2018, I created my first complex form for automatically payout customers of my SaaS business. At that time, I created all the form handling and validation by hand. A dynamic form with 10 fields quickly resulted in more than 500 lines of code. In the process, I often repeated myself, making the implementation exhausting and error-prone.
The learning I took away from this was that I needed to offload the repetitive code, the business logic of the form, and make it reusable. Since I was working with React at the time, I created my own
useForm hook. However, I quickly realized that the implementation was not that simple and I was also unsatisfied with the development experience.
I then compared different form libraries and also tested one of them in a larger project. The development experience was now much better. However, I encountered occasional problems and unexpected behavior. With the switch from React to SolidJS, I looked again at the functionality and design decisions of different form libraries and saw the opportunity to create my own library that could incorporate all my experience.
You can expect that every decision made when implementing Modular Forms has a well thought out reason. Below, I'd like to discuss a handful of those decisions. Feel free to ask me your questions about this via the issues on GitHub.
Let's start with performance. For libraries like React, performance plays an important role when implementing forms because the entire app is re-rendered on every user input when using controlled fields. Without manual optimizations to improve the re-render process, your site can feel slow on low-powered devices. Of course, you can create them uncontrolled, but then you have to add special magic or you lose the advantages of your declarative UI library.
Preact Signals are perfect for forms due to its mainly fine-grained reactivity. Without any opimizations, only the code that is really needed for the respective action is executed. Also, the flexible use of the signals, as opposed to the strict rule of hooks, offers a great advantage, as it allows the state of the form, no matter how complex, to be easily mapped.
To make a long story short, Modular Forms is built natively on React with Preact Signals and therefore enjoys all the benefits that the UI library brings. Unlike some other form libraries, Modular Forms requires only little special magic and therefore contains almost exclusively code that is mandatory for form handling, regardless of the underlying UI library.
With other form libraries, the bundle size has always been a thorn in my side. Since almost the entire logic is in a single component or hook, a simple login form starts at least with 7 KB (minified + gzipped) in many cases. However, since I didn't want to miss the benefits of the user and developer experience in input validation, I mostly put up with it.
With Modular Forms I thought a lot about keeping the bundle size as small as possible. On the other hand, extensive functionality was also important to me. It should be possible to implement any form, no matter how complex, with Modular Forms. Therefore, it was clear that the bundle size must be based on the required functionality.
The reason why many form libraries put everything into a single component or hook is that basically every functionality needs access to the state of the form. Therefore, it is natural to locate all the functionality next to the state and return it together. Modular Forms breaks exactly this approach with a fully modular design. By using
useForm to create only the state of the form and simply passing it as a parameter to various methods, you only need to import the code you actually need.
An important reason for many developers to use a form library is the benefits in validating user input to improve the user experience. Validating each field individually and displaying a separate error message depending on the error can be very time-consuming without a form library.
Modular Forms takes user input validation to the next level with its own validation system. Using small validation functions that can be imported as needed, you can validate almost anything with a single line of code. Be it an email, URL or the MIME type of a file. This keeps the bundle size low and ensures maximum performance.
And for all TypeScript users who love Zod, Modular Forms also offers the possibility to validate inputs using a schema. You can read more about it here.
Last but not least, Modular Forms is implemented with TypeScript and puts a lot of emphasis on type safety. We have all seen the advantages of this in the past few years and hardly anyone wants to do without it. Nevertheless, many form libraries still struggle with this.
With Modular Forms you can define the types of your fields via a generic. The library can then derive all relevant information to provide you with type safety and autocompletion. Be it the names of your fields or the values that are passed to the validation functions or schema. This way, when changes are made, you'll quickly become aware of errors and be able to fix them before your users have to deal with them.