Earlier this year I wrote a bit about autogrowing textareas and inputs. The idea was to make a <textarea>
more like a <div>
so it expands in height as much as it needs to in order to contain the current value. It’s almost weird there isn’t a simple native solution for this, isn’t it? Looking back at that article, none of my ideas were particularly good. But Stephen Shaw’s idea that I linked to toward the end of it is actually a very good idea for this, so I wanted to shine some light on that and talk through how it works, because it seems like it’s the final answer to how this UX can be done until we get something native and better.
Here’s the demo in case you just want a working example:
<textarea>
in an element that can auto expand height, and match its sizing.
The trick is that you exactly replicate the content of the So you’ve got a <textarea>
, which cannot auto expand height.
Instead, you exactly replicate the look, content, and position of the element in another element. You hide the replica visually (might as well leave the one that’s technically-functional visible).
Now all three elements are tied to each other. Whichever of the children is tallest is will push the parent to that height, and the other child will follow. This means that the minimum height of the <textarea>
will become the “base” height, but if the replicated text element happens to grow taller, everything will grow taller with it.
So clever. I love it so much.
You need to make sure the replicated element is exactly the same
Same font, same padding, same margin, same border… everything. It’s an identical copy, just visually hidden with visibility: hidden;
. If it’s not exactly the same, everything won’t grow together exactly right.
We also need white-space: pre-wrap;
on the replicated text because that is how textareas behave.
This is the weirdest part
In my demo, I’m using ::after
for the replicated text. I’m not sure if that’s the best possible approach or not. It feels clean to me, but I wonder if using a <div aria-hidden="true">
is safer for screen readers? Or maybe the visibility: hidden;
is enough for that? Anyway, that’s not the weird part. This is the weird part:
content: attr(data-replicated-value) " ";
Because I am using a pseudo-element, that’s the line that takes the data
attribute off the element and renders the content to the page with that extra space (that’s the weird part). If you don’t do that, the end result feels “jumpy.” I can’t say I entirely understand it, but it seems like it respects the line break behavior across the textarea and text elements better.
If you don’t want to use a pseudo-element, hey, fine with me, just watch for the jumpy behavior.
Special high fives to Will Earp and Martin Tillmann who both randomly emailed on the same exact day to remind me how clever Shaw’s technique is. Here’s an example Martin made with Alpine.js and Tailwind that also ends up kinda like a one-liner (but note how it’s got the jumpy thing going on).
I’m sure ya’ll could imagine how to do this with Vue and React and whatnot in a way that can very easily maintain state across a textarea and another element. I’m not going to include examples here, partially because I’m lazy, but mostly because I think you should understand how this works. It will make you smarter and understand your site better.
Thanks for sharing such an innovation, the textarea is showing its vertical scrollbar as it becomes taller on Firefox though.
I tossed an
overflow: hidden;
on the<textarea>
. That seems fine for all browsers and fixes the Firefox visible scrollbar.Just noting that Jim Nielsen covered resizing textareas lately:
https://blog.jim-nielsen.com/2020/automatically-resize-a-textarea-on-user-input/
It’s a little library though, rather than the very-little-JavaScript needed technique in this post. Here’s his demo that I forked to use an import from Skypack:
And also check out his post for a note about an interesting approach with Web Components.
Been doing similar trick 2 years ago.
The difference was that I used
position: absolute
withleft: 0; right: 0; top: 0; bottom: 0
to match the size of textarea to its parent element; and used<br />
to keep the line breaksThis is one of those things that would ideally be implemented by browsers (maybe behind some attribute, e.g. ).
For the longest time, I’ve been using the size attribute to auto-grow
<input/>
s. You can do a smipleonchange
listener and then set the size to thevalue.length
:You can see a demo of this in my codepen.
I had done something similar in EmberJS with handlebars:
Hey, can’t you just drop a div with a contenteditable attribute?
Maybe! The original article goes into that a smidge. I just don’t know ALL the accessibility and UX implications of that. I have doubts that it behaves the same well enough (and certainly it doesn’t “submit” correctly).
Set the height equal to its scrollheight?
scrollheight + 2px ;)
Neato, looks awesome. I was wondering how one would go about limiting the maximum amount of rows with this ?