Auto-Expanding Text Boxes: What works, what doesn’t and everything in between
The winner of this week’s “I thought this would be simpler” award goes to auto-expanding textareas.
Our team is currently working on a suite of features for qz.com that will add interactivity and social components to our editorial content. We realized that when a user writes a longer comment it’s a headache to have to proofread it by scrolling in a small pane. Users are also prone to submitting text that they forgot about, hidden from view.
A textarea that expanded and contracted line by line with a user’s input seemed like a quick add-on; a nice perk that would improve UX. We ended up taking four different approaches before finding a solution that was as reliable, flexible and accessible as we needed.
If you’d like a TL;DR on which approach we used, feel free to skip to the end, but I’m going to walk through the benefits and pitfalls of each technique we explored.
🚫 Textarea with Scroll Height
Using a textarea’s scrollHeight property to set the height seemed like the most straightforward solution. It worked when expanding the box, but unfortunately if a user removed a large chunk of text, there was no good way to shrink the box back down to an appropriate size.
🚫 Textarea with Rows attribute
The ‘rows’ attribute on a textarea let us expand our pane to a custom height, but calculating the number of rows needed turned out to be unreliable.
We had to make assumptions about how the browser would render the text in order to calculate the average amount of possible characters in a row. This approach was especially problematic in terms of accessibility – it would very easily break down if a user had a screen magnifier or other assistive technology that would affect the amount of real-estate the text occupied, such as font size, weight or kerning.
🚫 Div with contentEditable
Alright – what if we ditched textarea completely, and used a div instead? The contentEditable property is a powerful tool for making any HTML element editable, and allowed us to use a div that auto-sizes to the text within it.
That approach took care of our sizing issue, but unfortunately, we lost all of the built-in functionality that textarea provides. Stripping input text, adjusting cursor placement, and handling newlines, spaces, and special characters all had to be handled manually, which felt unnecessarily complicated and labor-intensive for such a small feature.
(That being said, if you are interested in taking the contentEditable approach to build a table or something similar, I highly recommend reading Tania Rascia’s blog post on the topic).
✅ Textarea with Div
Finally, we settled on a solution that used both a textarea and a div. We used a visually hidden div with all of the same padding and styling as our textarea, setting the input text as innerHTML. This hidden div gave us the height we needed, allowing us to use `position: absolute` and `height: 100%` on our visible textarea, which had all of the input functionality necessary.
While it took us a bit of experimentation to reach this solution, it’s exciting to bring a flexible and accessible feature to our site that makes our users’ lives easier. To see this in action, take a look at the commenting feature on one of our articles (paywall).
A quick follow-up note on React’s forwardRef
Because we have a delightful, custom TextArea component now, we can re-use it across our site in multiple ways. This also makes it a bit difficult to perform certain operations like managing focus, selection, or animations.
Luckily, we can set a ref value in our form using React.createRef() and pass it through TextArea to the underlying DOM element using React’s forwardRef API.Their documentation is excellent and I highly recommend this approach when it comes to building customized versions of DOM elements like inputs, buttons or textareas.