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 ?
How about just changing the value of the rows attribute? This would respect the line-height, font-size etc dynamically.
Yes, we’d still need to keep a tab on number of characters, but there’d be two less elements in the DOM?
Seems harder to me, to know exactly the moment you need to add another row. But try it!
Thanks for the excellent solution, Chris! For someone who is relatively new to CSS grid, in this case, what is the best way to prevent the grid from growing horizontally in the case that a really long “word” is entered into the textarea? Essentially, I want the textarea’s “overflow-wrap: break-word” property to kick in.
A couple of helpful articles, I hope:
Amazing, just what I needed. I dont like to use jQuery and like nice clean html, css and javascript.
How do I change textarea’s initial height?
@Gellert: good question. This demo is nice, but it’s not working out of the box for textareas that are populated with multi-line or wrapping content already. So you’ll have to do what the JavaScript does on page load: iterate over all your textarea’s and set the parent’s dataset value to the value of the textarea:
parent.dataset.replicatedValue = textarea.value
Can you please provide a example for the initial height?
How do I set the initial textarea to 100% width and limit the growth to rows only and not have it spill off the page?
Basically how do I get the textarea to only grow in height and not in width. I tried overflow-wrap which is the only thing that I can think of.
@Mike,
See Chris’ comment above about Handling Long Words and URLs:
Pasting the CSS referenced in that article (minus the hyphen related ones ones) in the CSS:
Seems to solve the issue for it wrapping when there are no breaks/spaces in the text. (At least in chrome, didnt test anything else), though I think it only needs to be applied to the ::after technically.
@Chris Coyier any reason this shouldn’t be the default behavior in your demo? Seems like what most people would want, as growing unlimitedly horizontally would likely lead to breaking a layout, and max-width can be controlled by the div this is put inside of allowing some horizontal growth, if required, without letting it go off screen.
This is so nice!!!! Thank you so much!!
Is there also an implementation with placeholder?
This is brilliant!!
What if you were started with a fixed height for the textarea say lesser height size than default?
Here’s my variation:
It uses flexbox instead of css grid and has a hidden DIV behind the text area.
Tested on Chrome, FireFox and Safari
This is a very complex solution, I figured out an easier one.
On the “input” event you change the height of the textarea to the value “auto” and later change it to textarea.scrollHeight + “px”.
Remember to put the following properties in css:
resize: none;
overflow-y: hidden;
Hope it helps.
Quick look in Chrome here… didn’t seem to expand for me.
I’m sorry, I did it wrong, now there you go:
This solution works for me in Chrome. I like the simplicity.
The only thing is that after I start to type the textarea grows a couple of pixels.
When I change:
textArea.style.height = textArea.scrollHeight + “px”
to:
textArea.style.height = textArea.scrollHeight – 4 + “px”
it works correctly for me.
Did anyone test this solution in other browsers?
And is this a good solution to use in production or should I keep using https://www.npmjs.com/package/autosize?
I would like the replace the use of the NPM autosize package with a simple solution to decrease the amount of dependencies.
RJ, I came to realize the “magical” amount you are subtracting “-4”, is the vertical padding. My textarea comes with a 2px padding around so once I did
textArea.style.height = textArea.scrollHeight – 4 + “px” resolved.
Hence, the value you have to subtract the sum of pt+pb
https://codepen.io/matthias-hermsdorf/embed/PoOVYaY
The neccessary height is stored in the textarea.scrollHeight.
But to get it, the height must be set to inherit.
While setting the height of the element to inherit, some browsers set it to the
realy small initial height and shortens the document body by that way.
To prevent scrolling due to the height change to inherit, the textarea gets wrapped in the flexbox with an second and invisible
Element side by side. The Heightkepper does its job an sets the height of the flexbox.
Just for fun as it’s probably a bug here’s a version that works in Chrome only without any js at all.
Don’t use it!!
I just thought it might be of interest even if it can’t be used.
For Safari (iOS) I had to add this to the end because there is an inherent 1px margin on a textarea:
trying to implement this in react, and not using the pseudo element… i couldn’t figure out how to replicate what the extra ” ” is doing…
Hi! I follow the CSS recipe and get, as the demo has, double line/row height.. I’d really like a single, clean row before a line shift starts the auto grow. How do I set the initial height to be no higher than the needed line of text?
Thanks, It worked