An auto-growing textarea used to need an input listener. Each keystroke reset the height, read scrollHeight, and wrote a new pixel value. The code was short enough to copy everywhere and awkward enough to break in slightly different ways everywhere.

field-sizing: content asks the form control to size itself from its content. On a textarea, text wraps when the inline size is constrained, then the block size grows until it reaches a limit.

The resize loop we are replacing

const textarea = document.querySelector("textarea");

function resize() {
  textarea.style.height = "auto";
  textarea.style.height = `${textarea.scrollHeight}px`;
}

textarea.addEventListener("input", resize);
resize();

The height reset lets the field shrink when text is deleted. Reading scrollHeight after the write asks the browser for an up-to-date layout. One field is rarely a disaster, but the handler still exists only because CSS could not previously express the sizing rule.

The CSS version starts with one declaration

textarea {
  field-sizing: content;
}

The default value is fixed. Switching to content removes the control's usual preferred size and lets its current value or placeholder contribute to intrinsic sizing.

One declaration proves the feature, but it is not enough for a production form. An empty field can become too small, and a long message can make the page keep growing. The limits are part of the component.

Set a useful starting size and a stopping point

.message-field {
  field-sizing: content;
  inline-size: 100%;
  min-block-size: 4lh;
  max-block-size: 14lh;
  overflow-y: auto;
}

The lh unit follows the field's line height, so the minimum and maximum read like approximate line counts rather than unrelated pixels. Once the maximum is reached, the textarea scrolls instead of pushing everything below it farther down the page.

A fixed height or block-size would reintroduce the fixed box. Use minimum and maximum constraints around the content-sized value.

Try the fixed and content-sized fields

Live example: add and remove lines

If your browser does not support field-sizing, both controls remain ordinary textareas. That is already a functional fallback.

Rows, placeholders, and other form controls

The rows and cols attributes define a textarea's preferred size. Once field-sizing: content takes over, they no longer determine that preferred size. Keep rows in the markup for the fallback, then use min-block-size for the enhanced version.

A placeholder also counts as content for intrinsic sizing. A long instruction can make an input or textarea start much larger than expected. Keep placeholders short and use a visible <label> for the field's name. Supporting instructions belong beside the control, not inside a disappearing placeholder.

The property also applies to text inputs, file inputs, and select controls. Their behavior is not identical:

  • A text input grows in the inline direction until a maximum width stops it.
  • A select can resize to the currently selected option rather than the longest option.
  • A file input can grow to include the selected filename.

That flexibility is useful in compact controls, but applying field-sizing: content to every form field at once can make the layout jump as values change. Start with the specific control that needs content sizing.

Choose the fallback from the product requirement

Browser support has changed quickly, with current Chromium, Firefox, and Safari releases adding the property at different times. A normal fixed textarea remains usable everywhere, so many sites can ship the CSS as an enhancement and delete the resize script.

.message-field {
  inline-size: 100%;
}

@supports (field-sizing: content) {
  .message-field {
    field-sizing: content;
    min-block-size: 4lh;
    max-block-size: 14lh;
  }
}

If automatic growth is a firm product requirement in an unsupported browser, keep the JavaScript only in that branch.

if (!CSS.supports("field-sizing", "content")) {
  document.querySelectorAll("[data-autogrow]").forEach((field) => {
    const resize = () => {
      field.style.height = "auto";
      field.style.height = `${field.scrollHeight}px`;
    };

    field.addEventListener("input", resize);
    resize();
  });
}

This avoids running two sizing systems in browsers that already handle the behavior.

Measure the code you removed

Deleting a resize listener removes a synchronous layout read and style write from the input path. That is a concrete simplification. It does not guarantee a visible change to Interaction to Next Paint on a small form, and the browser still has to lay out the growing field.

The stronger reason to prefer the native property is ownership. The sizing rule stays beside the component's other size constraints, reacts to font and layout changes, and does not need a lifecycle hook after content is restored or inserted.

  • Keep a real label and an ordinary textarea in the markup.
  • Set minimum and maximum sizes for the enhanced state.
  • Test empty values, long unbroken text, pasted content, zoom, and restored form data.
  • Check that surrounding content does not jump into an unusable position.
  • Run the JavaScript fallback only where auto-growth is required and CSS support is absent.

Primary references