Skip to main content

Harshal V. LADHE

Auto-Growing Textareas with CSS field-sizing: content

Auto-grow textareas with one line of CSS, no JavaScript.
Published at:
Last updated:
Estimated reading time:7 min read

Introduction

An auto-growing textarea gets taller as you type, instead of scrolling inside a fixed box. Comment boxes, chat inputs and message fields all want one, and until recently every one of them needed JavaScript: listen for input, measure scrollHeight, and set the height to match.

field-sizing: content does it in CSS. It tells a textarea to size itself to its text, the way most elements already size to their content, so it grows as you type and shrinks when you delete. Like accent-color, it improves the browser's own controls instead of replacing them with custom widgets.

Basic Usage

textarea {
  field-sizing: content;
}

The textarea is now exactly as tall as its text. It grows as you type and shrinks again when you delete. In practice you'll almost always add a minimum and a maximum as well, covered in Setting Limits.

The property has two values. fixed is the default: the textarea keeps the size the browser gives it, such as the height from rows. content makes it size to its text instead.

The JavaScript It Replaces

// What auto-growing textareas have shipped until now
textarea.addEventListener("input", () => {
  textarea.style.height = "auto"; // let it shrink first
  textarea.style.height = `${textarea.scrollHeight}px`;
});

It's only two lines, but each has a cost:

  • A forced layout on every keystroke. Reading scrollHeight straight after changing height makes the browser recalculate the layout immediately, every time a key is pressed.
  • The page can jump. Setting height: auto briefly collapses a tall textarea. Inside a scrolled page or panel, that can shift the scroll position while someone is typing.
  • It ignores the border. scrollHeight covers the content and padding but not the border, so with box-sizing: border-box the box ends up slightly too short unless you add the border widths back.
  • It only runs on input. Text set from code, such as a saved draft or a form reset, doesn't fire the event, so the box stays the wrong size until you call the function yourself.

field-sizing: content has none of these problems. The browser sizes the control during normal layout, the same way it sizes a paragraph, so there's nothing to measure and nothing to keep in sync.

Setting Limits

Content sizing has no minimum or maximum of its own. An empty textarea is about one line tall, and a pasted page of text makes it as tall as the page. Set both limits with min-block-size and max-block-size:

textarea {
  field-sizing: content;
  min-block-size: 3lh;  /* at least three lines */
  max-block-size: 12lh; /* then it scrolls */
}

The lh unit is the element's line height, so 3lh means "three lines of text", whatever the font size. Past the maximum, the textarea stops growing and scrolls inside, just as an ordinary one does.

rows and cols Stop Working

With field-sizing: content, the rows and cols attributes no longer affect the size at all. They don't set a starting size or a limit. <textarea rows="3"> collapses to one line when it's empty, so use min-block-size for the starting height instead.

Padding and Border with lh

With box-sizing: border-box, which most CSS resets set, min-block-size includes the padding and border. 3lh then leaves a little less than three lines for the text. If the exact number of lines matters, add the padding and border back:

textarea {
  padding: 0.625rem;
  border: 1px solid;
  min-block-size: calc(3lh + 2 * 0.625rem + 2px);
}

Placeholder Text

An empty textarea sizes to its placeholder. A long placeholder therefore makes the box tall before anyone has typed, and it shrinks as soon as they start. Keep placeholders short, or let min-block-size set the empty size.

Try It Live

Both textareas have rows="3". Type past the edge of each: the left one uses field-sizing: content and grows, while the right one is a plain textarea that keeps its size and scrolls. Untick min-block-size to see rows ignored, and untick max-block-size to let the box grow without limit. Turn on resize: vertical, drag the handle, then keep typing to see the dragged height stop the growth. The readout shows how many lines tall each textarea is, and whether it's scrolling.

Auto-Growing Textareas with field-sizing

Type past the edge of two textareas to compare field-sizing: content with a fixed size, and toggle the min and max limits and resize to see how each changes the growth.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>field-sizing Playground</title>
  </head>
  <body>
    <main class="playground">
      <header class="playground-header">
        <h1>Auto-Growing Textareas with field-sizing</h1>
        <p>Both textareas have <code>rows="3"</code>. Type past the edge of each: the left one uses <code>field-sizing: content</code> and grows, the right one keeps its fixed size and scrolls. Untick the limits to see the textarea follow its text alone, and turn on <code>resize</code> to see a manual drag stop the growth.</p>
      </header>

      <p class="tip support-tip">Your browser doesn't support <code>field-sizing</code>, so the left textarea keeps its fixed size, just like the right one. It's supported in Chrome and Edge 123, Safari 26.2 and Firefox 152 or later.</p>

      <fieldset class="control-group">
        <legend>Limits</legend>
        <label class="checkbox-field">
          <input type="checkbox" id="min-toggle" checked>
          <code>min-block-size: 3lh</code>
        </label>
        <label class="checkbox-field">
          <input type="checkbox" id="max-toggle" checked>
          <code>max-block-size: 8lh</code>
        </label>
        <label class="checkbox-field">
          <input type="checkbox" id="resize-toggle">
          <code>resize: vertical</code> (untick to clear a dragged size)
        </label>
      </fieldset>

      <section class="demo-area" id="demo" aria-label="Auto-growing and fixed textareas">
        <div class="pair">
          <div class="field">
            <label for="auto-textarea"><code>field-sizing: content</code></label>
            <textarea id="auto-textarea" class="auto-size" rows="3">Keep typing. This box grows with its content and shrinks again when you delete it.</textarea>
          </div>

          <div class="field">
            <label for="fixed-textarea">Default (<code>rows="3"</code>)</label>
            <textarea id="fixed-textarea" rows="3">Keep typing. This box keeps its size and scrolls instead.</textarea>
          </div>
        </div>
      </section>

      <output class="readout" id="readout" aria-live="polite"></output>
    </main>

    <script src="./index.js"></script>
  </body>
</html>

Read-only
Ln –, Col –HTML2.3 KBUTF-8

Starting sandbox…

No console output yet.

Relationship to resize

textarea {
  field-sizing: content;
  resize: none; /* keep the size in the browser's hands */
}

resize adds a handle that lets someone drag the box to a new size. Dragging sets an explicit height on the element, and an explicit size always beats content sizing. After a drag, the textarea stops growing and shrinking in that direction and keeps the height it was dragged to.

So the two don't combine well. If the box should always follow its content, use resize: none. If people need to make it larger by hand, keep resize: vertical and accept that a drag fixes its height from then on.

Browser Support and Fallbacks

field-sizing became Baseline in June 2026, when Firefox 152 shipped it. It's supported in Chrome and Edge 123, Safari 26.2 and Firefox 152. Older browsers ignore the property, and the textarea keeps its usual fixed size and scrolls. That's exactly what the field did before, so adding it is safe.

If auto-growth matters in older browsers too, keep the old script as a fallback and run it only where the property isn't supported:

if (!CSS.supports("field-sizing", "content")) {
  for (const textarea of document.querySelectorAll("textarea")) {
    const fit = () => {
      textarea.style.height = "auto";
      textarea.style.height = `${textarea.scrollHeight}px`;
    };
    textarea.addEventListener("input", fit);
    fit();
  }
}

In CSS, @supports (field-sizing: content) does the same job, for styles that only make sense when the textarea grows.

Accessibility

Keep the rest of the form reachable. Without a maximum, a long message pushes the submit button below the fold, which is especially awkward on a phone with the on-screen keyboard open. A max-block-size keeps the button in view.

Keep the empty box inviting. A one-line textarea looks like a single-line input, so people may not realise they can write more. A min-block-size of two or three lines signals a longer answer and gives a larger target to tap.

Nothing to announce. Screen readers don't report a field's size, so growing and shrinking doesn't need an ARIA attribute or a live region.

Common Pitfalls

1. Expecting rows to Set a Starting Size

rows and cols are ignored once a control is content-sized. Use min-block-size: 3lh for the starting height.

2. Leaving Out a Maximum

A pasted wall of text makes the textarea as tall as the text, pushing everything after it down the page. Always pair field-sizing: content with max-block-size.

3. A Fixed Height Somewhere Else

height, block-size or an inline style set by an old auto-grow script all override content sizing. Remove the old script, or guard it as shown in Browser Support and Fallbacks, when you add the property.

4. A Textarea That Shrinks Sideways

Content sizing applies in both directions. A textarea that isn't stretched by its container, such as one inside a flex row or an inline layout, also narrows to its longest line. Give it a width, such as inline-size: 100%, if it should fill its container.

Where Should You Use This?

  • Comment, message and chat boxes, where the field should start small and grow with what's typed
  • Any textarea currently wired to a scrollHeight listener just to grow: remove the listener and add a maximum
  • Feedback, bio and description fields in forms, where answers range from one line to several paragraphs

Leave it out where the layout depends on the textarea having a fixed size, such as a code editor or a form whose fields must line up in a grid.

Key Takeaway

field-sizing: content lets a textarea size to its text, replacing the scrollHeight script that auto-growing textareas have needed for years. Pair it with min-block-size and max-block-size in lh units, since rows no longer sets a size, and use resize: none if the box should always follow its text. It's Baseline, and older browsers simply keep the fixed size they had before.

Changelog

  • — Initial publication.
This tutorial is licensed under CC BY 4.0 by the author.

Share this tutorial

Spotted a mistake or something outdated in this tutorial? Suggest a correction.

  • Styling a <select> Dropdown with CSS appearance: base-select

    Style a real select's dropdown, options and arrow.
    appearance: base-select opts a native <select> into a fully styleable mode — rich HTML in options, <selectedcontent> in the closed button, and ::picker(select), ::picker-icon and ::checkmark for the dropdown, arrow and tick — while keeping its keyboard support, accessibility and form behaviour, with a plain select as the fallback.
    Published at:
  • Theming Native Controls with accent-color

    Theme native checkboxes, radios and sliders with one property.
    The accent-color property themes the checked and filled parts of native checkboxes, radio buttons, switches, range sliders and progress bars — including their indeterminate states — while keeping real browser controls with full native behaviour and accessibility, plus how it pairs with color-scheme and what it leaves alone.
    Published at:
  • HTML Attributes as Real CSS Values with Typed attr()

    Read HTML attributes as CSS colours, lengths and numbers.
    Typed attr() reads an HTML attribute as a real CSS value — type(<color>), type(<integer>), a unit like % or px, or raw-string — for any property on any element, not just text in content. Covers fallbacks for missing or invalid values, patterns for layout, transitions and view transitions, safe fallbacks in browsers without support, and when a custom property is the better choice.
    Published at: