erikras.com
HomeAbout

🏁 Ripple Final Form

Implementing forms in future UI frameworks

Posted in Coding, 🏁 Final Form, Ripple, TypeScript
October 4, 2025 - 3 min read
🏁 Ripple Final Form

The epiphany that sparked the creation of Final Form back in 2017, was that form state has nothing to do with frontend frameworks. It was always my intention that Final Form should outlive React, the seemingly untoppleable ubiquitous framework. Sure enough, it easiliy survived the transition to hooks, and people built wrappers for Vue and Svelte, just as I'd hoped.

A few years ago, I got excited about a potential React killer in SolidJS and wrote a proof of concept showing how easily Final Form could be plugged into Solid. Indeed, it was delightfully easy.

I have not really been that excited about a "new framework" until this year when Dominic Gannaway, who has recently become a colleague of mine at Attio, released a very pre-alpha proof of concept frontend framework, called Ripple, where he has taken all the best parts of React, Vue, Solid, and Svelte, and put them together into a framework that should not feel too foreign to the developers of those frameworks. It's pretty clear that future frontend frameworks will all have some sort of compile step; the days of "It's just JavaScript!" are long gone; so why not lean into the compiler for maximum developer experience?

It's like Esperanto for frontend.

When playing with Ripple, every time I went to the docs with an "I wonder if it supports...?" question, the answer was not just "Yes", but "Yes, and that API is surprisingly nice!"

The API, however, is still in constant flux. The code I wrote a week ago working on this no longer works, and likely what I'm showing today will not work next week. THIS IS NOT PRODUCTION READY! The metaphor that comes to mind is a polar bear walking on ice that has not yet solidified.

Polar bear walking on ice

That said, let's look at what I've built!

Proof of Concept

The primary component is one called RippleFinalForm, which creates a Final Form instance, saves it to the context, and then renders a <form> component with its children inside it. Then the inputs and error components are connected to the Final Form instance via the ref syntax.

export component App() {
<RippleFinalForm {onSubmit} {validate}>
<label>
<input name="firstName" placeholder="First Name" {ref field} />
<span {ref error("firstName")} />
</label>
<label>
<input name="lastName" placeholder="Last Name" {ref field} />
<span {ref error("lastName")} />
</label>
<label>
<select name="favoriteColor" {ref field}>
<option value="">{"Choose a color"}</option>
<option value="#FF0000">{"Red"}</option>
<option value="#00FF00">{"Green"}</option>
<option value="#0000FF">{"Blue"}</option>
</select>
<span {ref error("favoriteColor")} />
</label>
<button type="submit">{"Submit"}</button>
<Values />
</RippleFinalForm>
}

Notice that a "component" is not a "function", and it doesn't "return" anything, it just declares JSX. Also spot the refs that allow attaching logic directly to the DOM nodes.

I was able to implement a proof of concept that connects Ripple inputs to Final Form in 86 lines of code (Stackblitz).

Cool, now what?

My instincts tell me that Ripple might actually be capable of toppling the React Empire, but it's going to take a lot of work to get there.

DO check out the docs and learn how Ripple does things.

DO play around with it and submit issues and pull requests.

DO NOT (yet, in October 2025) use this anywhere near production.

Remember, form state – and much of frontend state – has nothing to do with UI frameworks. Choose a headless solution and it just might outlast your UI framework.

Thank you for reading.

Discuss on Twitter

© 2025 Erik Rasmussen