Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Auto-expand textareas as the user types. Uses CSS field-sizing where supported with a JavaScript scrollHeight fallback for older browsers.
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> <label for="comment">Comment</label> <textarea id="comment" data-grow rows="2"></textarea></form-field>
Add data-grow to any <textarea>. The init script detects browser support and applies the appropriate strategy:
field-sizing: content on the textareadata-max-rows is set, applies max-block-size using the lh unit (line height)max-block-size, the textarea stops growing and scrollsresize: none and overflow: hidden on the textareainput event, resets height to auto then sets it to scrollHeightdata-max-rows is set, calculates max height from line height and caps growthoverflow-y: auto when content exceeds the max heightIn 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';}
| 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. |
Without data-max-rows, the textarea grows indefinitely to fit its content. The rows attribute sets the initial minimum height.
<form-field> <label for="comment">Comment</label> <textarea id="comment" data-grow rows="2"></textarea></form-field>
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>
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> <label for="description">Description</label> <textarea id="description" data-grow rows="4" data-max-rows="12"></textarea></form-field>
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> <label for="msg">Message</label> <textarea id="msg" data-grow data-count maxlength="500" rows="2"></textarea></form-field>
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>
The textarea shrinks back down when content is removed. It never shrinks below the initial rows height:
auto before measuring scrollHeight, allowing the textarea to shrinkNo 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);});
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.
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>