Auto-Growing Textareas With No JavaScript Using Tailwind v4 field-sizing

Tailwind v4's field-sizing-content makes a textarea auto-resize to its content with zero JavaScript. Bound the growth with min and max height and ship it.

Steven Richardson
Steven Richardson
· 6 min read

Every chat box and comment field I have ever built grows as you type. For years that meant the same chunk of JavaScript: listen for input, reset the textarea height to auto, read scrollHeight, write it back. It works until a paste blows past the max, or Livewire re-renders the component and the height snaps back to one line. Tailwind v4 makes the whole thing a single class. field-sizing-content is the utility, and it auto-resizes a textarea with no JavaScript at all.

The old JavaScript auto-resize hack#

Here is the pattern most of us have copied into a dozen projects. An Alpine island that recalculates height on every keystroke:

<textarea
    x-data
    x-on:input="$el.style.height = 'auto'; $el.style.height = $el.scrollHeight + 'px'"
    class="w-full resize-none"
></textarea>

It mostly works. The problems show up at the edges. You reset to auto first because otherwise scrollHeight only ever grows and never shrinks when you delete lines. Pasting a wall of text triggers a layout thrash on a single frame. And in a Livewire form the textarea is inside a chunk of DOM that Livewire morphs on every round trip, so after a network request the inline style="height:..." you wrote can get clobbered and the field jumps back to its starting size.

You can paper over that with x-init and wire:ignore, but now you are maintaining the same kind of Alpine-and-Livewire state plumbing you'd use for wire:entangle just to size a box. The browser already knows how tall the content is. It should not need our help.

Auto-resizing a textarea with field-sizing-content#

field-sizing: content tells a form control to size itself to what is inside it instead of to a fixed rows or cols value. Tailwind v4 exposes it as two utilities:

Class CSS
field-sizing-content field-sizing: content;
field-sizing-fixed field-sizing: fixed;

Drop field-sizing-content on the textarea and delete the Alpine entirely:

<textarea
    class="field-sizing-content w-full resize-none rounded-lg border-gray-300"
    placeholder="Write a comment..."
></textarea>

That is the whole feature. The textarea is one line tall when empty and grows a line at a time as the content wraps. No input listener, no scrollHeight, nothing for a Livewire morph to fight because there is no inline style being written by JavaScript. wire:model keeps working exactly as before.

These utilities only exist in Tailwind v4. If you are still on v3, the Tailwind v3 to v4 Laravel upgrade walkthrough is the path to get there; the field-sizing utility shipped early in the v4 line.

Bounding growth with min and max height#

A raw field-sizing-content textarea has two annoying extremes: empty, it collapses to a single cramped line, and pasted with an essay, it pushes the page down forever. Bound both ends with normal Tailwind height utilities:

<textarea
    class="field-sizing-content min-h-[3lh] max-h-40 w-full resize-none rounded-lg border-gray-300"
    placeholder="Write a comment..."
></textarea>

min-h-[3lh] sets the floor at three line-heights using the lh unit, so the box opens at a comfortable three rows even when empty. max-h-40 caps it at 10rem; once the content passes that, the textarea stops growing and scrolls internally, which is exactly what you want in a fixed layout. Set the floor explicitly rather than leaning on the rows attribute, because field-sizing: content sizes to content and an empty field drifts back toward one line regardless of rows.

The same bounding works for an auto height field inside a component whose width is driven by its container rather than the viewport; if that is your layout, it pairs naturally with container-query-based responsive components.

It also works on inputs and selects#

field-sizing is a form-control property, not a textarea trick. On a text <input> it grows the field horizontally as you type, which is handy for inline-edit chips and tag fields:

<input type="text" class="field-sizing-content min-w-[8ch] max-w-full rounded border-gray-300" />

On a <select> it sizes the control to the currently selected option instead of reserving space for the widest one in the list. And when you need to opt a single control back out, field-sizing-fixed restores classic fixed sizing, which is useful with a responsive variant:

<textarea class="field-sizing-content md:field-sizing-fixed w-full md:w-80" rows="2"></textarea>

Browser support and graceful fallback#

This is the one thing to check before you delete all your JavaScript. Per caniuse it sits around 79% global support as of mid-2026, but the split matters:

  • Chrome and Edge 123+, and Opera 109+: shipped in early 2024, so Chromium has had it for over two years.
  • Safari 26.2+, including iOS Safari: landed recently.
  • Firefox 152+: landed very recently, and Firefox for Android is still catching up.

The good news is that this degrades cleanly. A browser that does not understand field-sizing ignores the declaration and renders a perfectly normal textarea sized by your min-h-[3lh] floor. Nobody gets a broken field; they just get a fixed-height box instead of an auto-growing one. That makes it a textbook progressive enhancement: keep a sane minimum height and the form is fully usable everywhere, with the auto-grow as a bonus where it is supported.

Gotchas and edge cases#

A few things that bit me in real forms.

Without a width constraint, field-sizing-content grows the textarea horizontally as well as vertically. Add w-full (or a max-w-*) so it only grows in the direction you care about.

The default resize handle is redundant once the field sizes itself, and dragging it fights the auto-sizing. resize-none removes it.

If you find yourself repeating field-sizing-content min-h-[3lh] max-h-40 w-full resize-none across every form in the app, fold it into a custom utility with the @utility directive so the intent lives in one place.

And the lh unit in min-h-[3lh] has its own, slightly different support baseline than field-sizing. It is well supported in current browsers, but if you need to reach further back, swap to a rem floor like min-h-20.

Wrapping up#

Reach for field-sizing-content, bound it with min-h-[3lh] and max-h-40, add w-full resize-none, and delete the Alpine resize listener. Keep the minimum height sensible so the no-support browsers still get a usable field. From here, drop the textarea into a Livewire form backed by a form object so the validation and state stay server-side, or carry on trimming JavaScript out of your Blade with the rest of the Tailwind v4 utilities for dynamic Blade UIs.

FAQ#

How do I make a textarea auto-resize in Tailwind?

Add the field-sizing-content utility to the textarea in Tailwind v4. It maps to the CSS field-sizing: content property, which tells the browser to size the control to its content, so the field grows and shrinks as you type with no JavaScript. Pair it with min-h-[3lh] for a floor and max-h-40 for a ceiling, and add w-full resize-none to keep it growing vertically only.

What is field-sizing-content?

field-sizing-content is the Tailwind v4 utility for field-sizing: content, a CSS property that makes form controls size themselves to their content rather than to a fixed rows, cols, or width. On a textarea it grows the height as lines wrap; on an input it grows the width as you type; on a select it sizes to the chosen option. The opposite utility is field-sizing-fixed.

Which browsers support the field-sizing property?

Support is around 79% globally as of mid-2026. Chrome, Edge, and Opera have shipped it since early 2024 (Chrome and Edge 123+, Opera 109+). Safari added it in 26.2, including iOS, and Firefox added it in 152, both fairly recently. Browsers without support ignore the property and fall back to a normal fixed-height field, so it is safe to use as a progressive enhancement.

How do I set a minimum and maximum height with field-sizing?

Use ordinary Tailwind height utilities alongside field-sizing-content. min-h-[3lh] sets a floor of three line-heights so an empty textarea does not collapse to a single line, and max-h-40 caps growth at 10rem, after which the field scrolls internally instead of pushing the page down. Set the floor explicitly rather than relying on the rows attribute, because content sizing overrides it.

Does field-sizing-content cause problems with Livewire?

No, and that is the point. The old JavaScript approach wrote an inline height style that Livewire's DOM morphing could overwrite on each round trip, snapping the field back to its starting size. Because field-sizing-content is pure CSS with no inline style and no input listener, there is nothing for Livewire to clobber, and wire:model continues to bind the value normally.

Steven Richardson
Steven Richardson

CTO at Digitonic. Writing about Laravel, architecture, and the craft of leading software teams from the west coast of Scotland.