data-grow
Auto-expand textareas as the user types. Uses CSS field-sizing where supported with a JavaScript scrollHeight fallback for older browsers.
Overview
The data-grow attribute makes a textarea automatically expand its height as the user types. It uses the modern CSS field-sizing: content property where supported, falling back to JavaScript scrollHeight measurement for older browsers.
<form-field>\n <label for="comment">Comment</label>\n <textarea id="comment" data-grow rows="2"></textarea>\n</form-field>
How It Works
Add data-grow to any <textarea>. The init script detects browser support and applies the appropriate strategy:
Modern Browsers (CSS field-sizing)
- Sets
field-sizing: contenton the textarea - If
data-max-rowsis set, appliesmax-block-sizeusing thelhunit (line height) - When content exceeds
max-block-size, the textarea stops growing and scrolls
Fallback (JavaScript scrollHeight)
- Sets
resize: noneandoverflow: hiddenon the textarea - On each
inputevent, resets height toautothen sets it toscrollHeight - If
data-max-rowsis set, calculates max height from line height and caps growth - Switches to
overflow-y: autowhen content exceeds the max height
In both cases, data-grow-init is set to prevent double-binding.
/* Modern approach — single CSS property */textarea { field-sizing: content; max-block-size: 10lh; /* from data-max-rows */} /* Fallback approach — JavaScript measurement */function resize(textarea) { textarea.style.height = 'auto'; textarea.style.height = textarea.scrollHeight + 'px';}
Attributes
| Attribute | Type | Description |
|---|---|---|
data-grow |
boolean | Enables auto-grow behavior on a textarea. |
data-max-rows |
number | Maximum number of rows the textarea can grow to. After this, the textarea scrolls. |
rows |
number | Standard HTML attribute. Sets the minimum number of visible rows. The textarea never shrinks below this. |
data-grow-init |
boolean | Set automatically to prevent double-binding. Do not set manually. |
Basic Auto-Grow
Without data-max-rows, the textarea grows indefinitely to fit its content. The rows attribute sets the initial minimum height.
<form-field>\n <label for="comment">Comment</label>\n <textarea id="comment" data-grow rows="2"></textarea>\n</form-field>
Max Rows
Set data-max-rows to cap the textarea height. Once it reaches this height, the textarea stops growing and the content scrolls instead.
<textarea data-grow data-max-rows="10" rows="2"></textarea>
Min and Max Rows
Combine rows (minimum) and data-max-rows (maximum) to define a growth range. The textarea starts at the minimum and grows up to the maximum.
<form-field>\n <label for="description">Description</label>\n <textarea id="description" data-grow rows="4" data-max-rows="12"></textarea>\n</form-field>
Combined with data-count
Use data-grow alongside data-count for a textarea that grows while showing a live character or word count. Both attributes work independently on the same element.
<form-field>\n <label for="msg">Message</label>\n <textarea id="msg" data-grow data-count maxlength="500" rows="2"></textarea>\n</form-field>
In a Form
Auto-grow textareas work seamlessly in forms. The textarea submits its value normally regardless of its visual height.
<form class="stacked"> <form-field> <label for="subject">Subject</label> <input type="text" id="subject"> </form-field> <form-field> <label for="content">Content</label> <textarea id="content" data-grow data-max-rows="15" rows="3"></textarea> </form-field> <button type="submit">Send</button></form>
Shrinking Behavior
The textarea shrinks back down when content is removed. It never shrinks below the initial rows height:
- CSS field-sizing — the browser handles shrinking automatically based on content
- JS fallback — on each input event, the height resets to
autobefore measuringscrollHeight, allowing the textarea to shrink
Events
No custom events are dispatched. The textarea fires standard input and change events. You can observe height changes by reading scrollHeight or style.height on the textarea.
const textarea = document.querySelector('[data-grow]'); // The textarea fires standard events — no custom events neededtextarea.addEventListener('input', () => { console.log('Height:', textarea.style.height || 'auto (CSS field-sizing)'); console.log('Scroll height:', textarea.scrollHeight);});
Styling
All styles are gated on [data-grow-init]. Without JavaScript, the textarea renders at its normal rows height with the default resize handle.
/* Modern browsers: CSS field-sizing */textarea[data-grow][data-grow-init] { field-sizing: content;} /* Max height via max-rows */textarea[data-grow][data-grow-init][data-max-rows] { max-block-size: calc(var(--max-rows) * 1lh); overflow-y: auto;} /* Fallback browsers: JS-driven sizing */textarea[data-grow][data-grow-init].grow-fallback { resize: none; overflow: hidden;} textarea[data-grow][data-grow-init].grow-fallback.grow-scrollable { overflow-y: auto;}
The modern CSS path uses field-sizing: content and lh units for clean, declarative sizing. The fallback path uses JavaScript-driven inline styles.
Dynamic Elements
Textareas added to the DOM after page load are automatically enhanced via a MutationObserver. No manual initialization is needed.
<section> <h2>Accessibility</h2> <ul> <li>Content remains fully accessible — the textarea grows to show all text, reducing the need for scrolling</li> <li>No manual resize is needed — the textarea adapts to content automatically, improving usability for all users</li> <li>The native <code><textarea></code> element is preserved, maintaining full screen reader and keyboard support</li> <li><code>rows</code> sets a visible minimum, ensuring the textarea is never unexpectedly small</li> <li>When <code>data-max-rows</code> caps growth, <code>overflow-y: auto</code> enables scrolling with standard scroll accessibility</li> <li>Without JavaScript, the textarea renders at its <code>rows</code> height with the native resize handle (progressive enhancement)</li> </ul> </section>