Elm Native UI in Production

Josh Steiner

In November, thoughtbot released a new app called Purple Train. The app was implemented in React Native, which we’ve been using to rapidly build cross-platform apps. We’ve grown to love React Native thanks to a number of improvements over traditional mobile dev, such as a (mostly) declarative API, quicker development cycles, familiarity to web developers and designers, and cross-platform builds. While React Native isn’t the best fit for every mobile app, it’s a great fit for many. That said, in our eyes, React Native still has a major flaw: JavaScript.

JavaScript has been improving as a language, and it has its benefits, most notably its ubiquity. However, it’s also well known for its strange behavior and weak type system (read “aggressive type coercion”).

Elm Native UI

Enter Elm Native UI — an Elm library that provides functions which compile down to React Native components. If you’ve heard of Elm, you probably know of the amazing error messages and have heard claims of zero runtime errors. Elm’s safety comes from its pure functions and strong static typing, which also makes Elm code easy to reason about.

I stumbled upon Elm Native UI around the same time that we released the React Native version of Purple Train. The GitHub repo clearly said “Not for production use”, but I wanted to put that to the test. I spiked something simple out, and kept pushing the boundaries. The app quickly approached feature parity with the React Native app. In December, we decided that while the library was immature, the environment it created was fantastic, and we decided to make the experimental spike the canonical version of Purple Train. We finished the remaining features, and shipped the first ever Elm Native UI app into production.

React vs Elm

The differences between developing with React and Elm are stark. In React, you have to think thoroughly about each step while developing a feature. If you forget a step, there’s little guidance as to what you missed, and it can take some time to backtrack and think about how the parts are wired together.

In Elm, you can make a simple change, then follow the friendly compiler errors until it works. You don’t have to worry about walking away in the middle of a feature, because when you return the compiler reminds you what’s next. Furthermore, you can quickly and easily make sweeping changes across the entire codebase with confidence that nothing is broken. Programming in Elm is like coding with a helpful robot.

React depends on human developers not to make mistakes. While static analysis tools like eslint can help, there’s a limit to how useful they can be with a dynamic language like JavaScript. When you do make mistakes, you are likely to see runtime errors which often manifest as “Red Screens of Death” in development mode.

React Native Red Screen of Death

Elm, on the other hand, relies on the compiler to catch bugs. I’m happy to say that while working on Purple Train Elm, I didn’t run into a single Red Screen of Death1.

Not Ready for Prime Time

While the experience so far has been great, Elm Native UI is not yet ready for most production apps. It’s still missing critical features like bundled images and a straightforward setup process — the current process requires you to jump through some hoops. thoughtbot is currently working to get it to a point where we can recommend it for more serious use. Until then, if you have questions about getting started you can hop into the #elm-native-ui channel in elmlang Slack.


  1. This isn’t entirely true. I did run into Red Screens of Death, but this was only when developing new features for the Elm Native UI library itself, not when I was writing features for Purple Train Elm. Assuming that there are no bugs in the JavaScript that backs Elm Native UI, I would expect this to hold true for other apps as well.