Textio is a word processor that aims to help businesses communicate more inclusively in job listings, brand materials, and other documents. As a writer types, the platform uses natural language processing and machine learning to compare the text with similar documents, analyzing how they resonated with their intended audience. The Textio editor takes this information, layers in additional guidance based on linguistic research, and surfaces insights on how the text is likely to be received by readers. It also makes suggestions to avoid exclusionary language, eliminate unintentional bias, or include phrases with broader appeal. To deliver these insights, Textio uses highlights and one-click alternatives on phrases that can be improved.
Our augmented writing experience began as a desktop-only web application in 2014. As we sought to expand to other platforms, we developed integrations with Outlook, LinkedIn, and Gmail, but we also wanted to support teams writing and collaborating on the go, making quick edits on mobile devices. This required bringing Textio to the mobile web, so in 2019, we set out to accomplish that.
While we knew it would be difficult to design for a wide variety of smaller screens, we weren’t expecting one of the most complicated facets of the project, particularly on Android, to be the act of writing itself. At the outset, the challenges we faced seemed nearly impossible to surmount, but through research, experimentation, and the use of careful constraints, our engineering team shipped a mobile experience that’s as polished and intuitive as our desktop platform.
Understanding Android in the browser
When we first tried our editor in the browser on Android, phrases wouldn’t highlight, autocorrect acted unpredictably, and characters would disappear as a user typed, sometimes mysteriously reappearing in other places. What’s more, as Textio’s guidance updated in response to text changes, editing fell out of sync and tapping on suggestions deleted words instead of replacing them. Some Android devices’ onscreen keyboards worked just fine, while others experienced these issues nonstop. In contrast, typing behaved totally predictably on iOS. We quickly discovered we weren’t alone: Rich text editors across the web were generally broken in Android browsers, and support remains uneven to this day.
So, why is text editing so difficult in Android browsers? The answer lies in how keyboard input events work on Android. Consider writing the word “cat.” If you’re using a desktop computer, you’ll probably type three letters on your keyboard, which the editor will recognize as three insertions. But on a phone, you might tap the letters, swipe your finger, accept a keyboard suggestion, or even speak the word out loud.
To ensure keyboard input in web apps works the same way between desktop and mobile, iOS emulates desktop browser behaviors: The browser receives three keyboard events for the word “cat,” even when the input is dictated. Android’s behavior, however, varies depending on the keyboard. Usually, interactions are represented as text composition events.
Composition is typically used when performing interactions to create a character or word that doesn’t have a direct analog on your device. For instance, on an American macOS keyboard, typing
E will insert the character
´, and pressing the
E key again will replace it with
é. On Android, these composition events are the building blocks of most text editing. Next time you type on an Android device, watch for when the text is temporarily underlined—that’s composition mode.
This was the heart of the problem. Textio uses a fork of the popular rich text editing library Draft, which makes assumptions that hold true for traditional keyboards but not for many Android keyboards. For example, because Android relies on text composition, keyboard events usually don’t reveal which key was pressed. This behavior caused Draft’s internal representation of writing to fall out of alignment with what a user typed, resulting in garbled documents. (The reason some onscreen keyboards performed better than others is because certain devices running Android, like Samsung phones, expose more useful event data.)
After realizing this, we did some digging and discovered a few promising changes that had been suggested to improve Draft’s Android support. The proposed approach observed how the HTML changed after text composition, then replicated those changes into its internal model. While this allowed users to write in Textio with Android devices, many common interactions still failed. For example, merging two paragraph blocks with backspace caused React to throw an error when the DOM changed unexpectedly, and deleting a space at the end of the paragraph deleted a different character instead. We had to rethink our strategy.
Finding the missing keys
For weeks, we researched every option imaginable. We explored swapping out our editor, building a native application, and even working through all the edge cases in the proposed changes to Draft. We eventually determined these would either require too much effort, introduce too much maintenance overhead, or risk destabilizing our current editing experience for desktop and iOS.
So we shifted course. Instead of observing how the HTML changed and patching Draft’s internal model, we explored hooking into input events provided by the browser that described the operation the user was performing (for example, text substitution, text composition, or deletion), as well as the exact ranges of text it would affect. This was an appealing solution, since it mapped cleanly to Draft’s internal editing commands. One major drawback was a lack of cross-browser support, but we weren’t looking for a general solution—we just needed it to work on mid-level Android devices. Through testing, we discovered we could support any device running Android 8.1 or higher. This meant our solution would work for a large set of users, and as people upgraded, support would continue to climb.
Given that the code was targeted specifically at Android devices, we decided to isolate it from all other input handling. As a result, we were able to address three major issues we’d run into with other approaches: First, we could implement a simple input handler that didn’t need to consider pain points in browsers such as Internet Explorer 11, which greatly decreased the effort involved. Second, we wouldn’t destabilize the desktop writing experience, since the Android code would run only on Android devices. Lastly, since we were unlikely to modify the input handler in the future, there would be little to no maintenance overhead.
It’s just work
While the direction was sound, progress was slow. Turning the prototype we started with into a reliable editing experience required a detailed understanding of which events triggered during editing, the order in which they fired, and the information that was consistently available across a wide range of Android keyboards. The permutations were overwhelming and occasionally disheartening, but three practices kept us on track: close collaboration, a focus on improving the developer experience, and continual simplification.
Every new character that appeared in the right place was cause for shared celebration; every garbled word, a shared lament.
Our product manager meticulously ran through test scenarios on a variety of devices to help us validate progress and catch regressions. Every new character that appeared in the right place was cause for shared celebration; every garbled word, a shared lament. Collectively experiencing these small achievements and setbacks meant we all knew where we stood, and it gave us a sense of momentum.
In fact, some of the most productive moments came from casual comments made while the team was working together. For instance, one problem we faced was that input events didn’t expose the specific text ranges being edited when the keyboard was in composition mode. An engineer casually observed that when entering composition mode, the selection temporarily changed from a “collapsed” caret to an expanded one, making it difficult to tell what text was being edited. When we investigated, we realized the selection was expanding to cover the text being composed, matching the underlined text on the screen—exactly the information we were missing from the input events. This offhand interaction was crucial to understanding how to process events and correctly reflect the user’s editor input.
Celebrating such advancements helped maintain momentum, but early progress remained sluggish because of the toil involved in running development builds through network proxies on our phones. Simulators and emulators weren’t accurate enough to capture the intricacies of how real devices represented user input. Luckily, Chrome’s remote debugging tools allowed us to use real phones and provided a way to project our screens to puzzle through each odd behavior together, improving our ability to iterate and test quickly on real-world devices.
The next major improvement in developer experience came from Storybook, a library often used to document and demonstrate an application’s UI components. We adapted it as a test bed to extract our editor into a small test case that would rebuild and reload more quickly. We then expanded our use of Storybook to create test cases for common editing scenarios, such as editing formatted text, lists, and Textio’s phrase highlights, which allowed us to test new ideas on real mobile devices more quickly.
Despite these improvements to our processes, each new bug felt like a unique challenge, and we were often tempted to write special cases for each one. Instead, we made a point of looking for deeper patterns present in several issues, an approach that allowed us to pare back our solution and keep it small. Ultimately, the strategy led to fewer and easier-to-diagnose bugs.
It just works
In January 2020, we launched Textio on the mobile web. Now, editing on Android “just works.” The effort taught us that the underlying details that make a UI look simple and work seamlessly can sometimes be the most challenging aspects to build—but when done correctly, you can craft an experience that gets out of the user’s way, allowing them to focus on their own work rather than the tool they’re using.
We also learned how to approach a technical problem without an apparent solution. Our journey shows that by collaborating, deeply investigating the challenges, and applying considered constraints, you can unlock novel solutions to seemingly insurmountable obstacles. In the process, you might pave the way for other teams to solve similar problems.