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)

  1. Sets field-sizing: content on the textarea
  2. If data-max-rows is set, applies max-block-size using the lh unit (line height)
  3. When content exceeds max-block-size, the textarea stops growing and scrolls

Fallback (JavaScript scrollHeight)

  1. Sets resize: none and overflow: hidden on the textarea
  2. On each input event, resets height to auto then sets it to scrollHeight
  3. If data-max-rows is set, calculates max height from line height and caps growth
  4. Switches to overflow-y: auto when 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 auto before measuring scrollHeight, 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 needed textarea.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.

// Dynamically added textareas are auto-enhanced via MutationObserver const textarea = document.createElement('textarea'); textarea.dataset.grow = ''; textarea.dataset.maxRows = '8'; textarea.rows = 2; document.querySelector('form-field').appendChild(textarea); // textarea auto-grows immediately — no manual init needed

Accessibility

  • Content remains fully accessible — the textarea grows to show all text, reducing the need for scrolling
  • No manual resize is needed — the textarea adapts to content automatically, improving usability for all users
  • The native <textarea> element is preserved, maintaining full screen reader and keyboard support
  • rows sets a visible minimum, ensuring the textarea is never unexpectedly small
  • When data-max-rows caps growth, overflow-y: auto enables scrolling with standard scroll accessibility
  • Without JavaScript, the textarea renders at its rows height with the native resize handle (progressive enhancement)