Last fall, FiftyThree introduced Mix, a new way for users to collaborate on sketches and ideas in Paper. Though it’s only been a few months since launch, the Mix feature set has grown by leaps and bounds. One of the most requested features was the ability to search for friends. Unsurprisingly, Mix users wanted to find and collaborate with people they already followed on Tumblr, Twitter and Facebook. Just before the new year we launched creator search in Paper and on Mix.

The biggest challenge we faced while building our web-based search was implementing a fluid text input. A fluid text input has a width that is defined by its content. If you have experience with web programming, you’ll recall that HTML text inputs are not naturally fluid. This post explores how we implemented a text input with no explicit width.

No Detail Too Small

There are quite a few custom user interface features in our iPad app Paper, and text input is no different. When you edit the title of a Paper journal or enter text in the search field, you’ll notice that the type is centered in the UI. Not only are these text inputs visually appealing, they function to balance the on-screen elements.

Text inputs in Paper are center aligned.

The FiftyThree design and engineering teams recognize the importance of adhering to a design language that is intuitive, beautiful and, above all, consistent. Thus, keeping the Mix aesthetic and experience in step with all our products, especially Paper, has been a goal from the beginning.

Necessity is the Mother of Invention

As design-focused engineers and engineer-minded designers, we’re no strangers to constraints from both sides. Ultimately, well-defined constraints help guide us toward workable solutions and more quickly discard ill-conceived approaches. We had a general idea of what a fluid text input needed to do, but in order to differentiate between must-haves and nice-to-haves, we created a list to help gauge success:

Must resize dynamically.

Must allow for text selection.

Must use placeholder copy consistently across browsers.

Must work with pointer and touch events.

Must behave predictably.

Let’s review each constraint in more detail.

1. Must resize dynamically.

Native HTML text fields must have a defined size. If you don’t provide a size, the browser will render the element at a default width. In the figure below, x represents the width of the text input. x can be defined in one of three ways:

Percentage width (via CSS)

Fixed width (via CSS)

Size attribute (showing x number of characters)

A native HTML text input has a non-fluid width.

This was the toughest constraint of all because we needed to “break the rules” when it came to the way that browsers rendered text fields. We wanted the native behavior of a standard text input with the flexibility of an inline element.

2. Must allow for text selection.

We understand how text fields work because we have been using search engines for years. So we knew if we were going to tinker with a native HTML text field, the final version would have to work exactly like we expected in regards to text selection. We’d have to be able to insert our cursor anywhere within the typed content, use the keyboard to move it, and use the shift and arrow keys to start/move the selection area.

3. Must use placeholder copy consistently across browsers.

The placeholder attribute for text fields has had a rough upbringing. Initially, browser vendors implemented placeholder behavior as they wished; some vendors didn’t support it at all. In the past few years browsers have been kinder to placeholders and the behavior has normalized.

Although the interactions for placeholder attributes have become consistent, we still wanted to ensure that the placeholder copy would behave similarly to those in Paper text inputs. For style consistency and to future-proof our implementation, we decided to create our own custom placeholder text.

4. Must work with pointer and touch events.

As one would probably expect, most Mix users also use Paper, which means that they own or have access to iPads. We know that usage of mobile devices is huge and will continue to grow. Thus, supporting touch screens and mobile devices has been a priority since the birth of Mix. Touch interactions are as important to us as mouse and keyboard interactions.

5. Must behave predictably.

This constraint may seem obvious, but for us it functioned as a catch-all. Predictable behavior included enforcing a single line of text (no line-breaks) and scrolling long text correctly. For example, a native text fields with overflowing text snaps back to the beginning of the string when keyboard/pointer focus leaves the field.

Mirror Mirror

As mentioned previously, we needed our fluid text input to behave like an inline element. This led us to explore a span with a contentEditable attribute. At first, an inline contentEditable element seemed like a silver bullet because it dynamically resized based on content length. But once we considered the complications of line breaks and formatted content, the appeal quickly vanished. contentEditable certainly was intriguing but not very practical.

We took a step back and remembered that native HTML text inputs strip formatting when you paste content. This was our breakthrough moment. We didn’t have to choose a native input over a span — we could use both. If our span mirrored the value of our text input and the text input sanitized the user’s input, we could experience the euphoria of an inline element without the contentEditable hangover. Eureka!

Building Out a Solution with React

The Mix web app is authored using React. This “mirrored value” solution is not unique to React, but React makes it easy to implement. Here’s the basic idea:

When a user changes the value in a text <input>, React triggers the <input>’s onChange event.

The updated value of the <input> becomes a state of our FluidTextInput component.

The component is re-rendered based on the state change. The state is passed into <input>’s value attribute.

Since value is part of the component state, it can also be used elsewhere in the component.

Mirror the value of the <input> into the string inside a <span>.

The <span> resizes fluidly based on content changes in the <input>.

In our final solution, the span acts as a spacer and the content inside is styled invisibly using CSS. As more characters are added to the input, the mirrored value inside the span pushes the search icon svg to the left and the clear icon svg to the right.

The SPAN pushes the icons outward from the center.

Adding a Coat of Paint

The above markup is nested inside a fieldset element. We used some crafty CSS to bring it all together.

Allow the <svg> icons and <span> to flow “naturally” in the container.

Set the <fieldset> container to position: relative.

Set the <input> to position: absolute.

Use text-align: center for the <input> and set it to width: 100%.

Set the <svg> clear icon to position: relative and z-index: 1 to “float” above the <input>. This allows the <svg> clear icon to receive touch and pointer events.

Remember Our Custom Placeholder?

The last piece of the puzzle was adding the “Search creators” placeholder. The process of rolling our own placeholder attribute was fairly straightforward: the value of the <input> was “Search creators” until a user started typing. But there were a few wrinkles to iron out:

A user could click anywhere in the field and the insertion cursor would be placed near the closest letter.

A user could type anywhere within the custom placeholder text creating a situation like “Search creaHELLOtors”.

A user could move the insertion cursor with the keyboard arrows.

A user could select the placeholder text with a mouse and keyboard.

Preventing arrow key navigation was accomplished by intercepting (and discarding) the keypress events. To stop all the other unwanted behaviors, we needed to intercept taps/clicks and only allow the insertion cursor to start at the front of the placeholder text.

The magic is in the layering of the DOM elements.

We implemented a mask using a <div> and positioned it over the <input> when the placeholder text was present. When a tap or click is intercepted by the mask, the cursor is placed at the front of the <input> using setSelectionRange(0, 0). The mask is removed from the DOM when the user begins typing so that all normal touch and pointer behaviors are handled by the <input>.

And That’s Not All…

If you dig into the the markup and styles on mix.fiftythree.com/search you’ll find the production implementation to be a bit more complex than what we’ve outlined here. For example, we have a request (loading) indicator that replaces the svg clear icon when waiting for an outstanding API request. This indicator and the clear icon share a container and that markup is not represented in the diagrams and discussion above.

Send Out the Search Party

Just before the holidays, we launched creator search in Paper and on mix.fiftythree.com, enabling user search by first name, last name and email address. From design to usability, each new Mix feature presents its own set of challenges. If you’re someone who’s passionate about building delightful interfaces for touch devices and the web, let us know. We’re always searching for great people.