30% offEnding soon
useTextareaAutosizeLoading saved progress…

useTextareaAutosize

useTextareaAutosize grows and shrinks a <textarea> so its height always matches the text inside it — the "expanding comment box" you have used on GitHub, Slack, and every modern message composer. A plain textarea has a fixed height and scrolls its overflow; there is no CSS that makes a classic textarea hug its content, so the height has to be measured and set in JavaScript. You will build the hook that does it, re-measuring on every change to the value.

Signature

function useTextareaAutosize(
  value: string,                 // the same value you bind to the <textarea>
  options?: {
    minRows?: number;            // clamp: never shorter than this many rows
    maxRows?: number;            // clamp: never taller — past this it scrolls
  },
): React.RefObject<HTMLTextAreaElement>; // attach to the <textarea>

Attach the returned ref to a <textarea> whose value prop is the same value you pass the hook. It measures on mount and every time value changes.

Examples

function Composer() {
  const [text, setText] = useState('');
  const ref = useTextareaAutosize(text, { minRows: 2, maxRows: 8 });
  return (
    <textarea ref={ref} value={text} onChange={(e) => setText(e.target.value)} />
  );
}
// Given a textarea whose one row is 20px tall (line-height 20, no padding):
//   value = 'hi'                 -> height 20px   (1 row)
//   value = 'line1\nline2\nline3'-> height 60px   (grows to 3 rows)
//   value = 'hi'  (deleted back) -> height 20px   (shrinks back down)
//   value = '', minRows: 3       -> height 60px   (floored at 3 rows)
//   value = 12 rows, maxRows: 8  -> height 160px + overflow-y: auto (scrolls)

Notes

  • Measure, then set — read the content height from the element's scrollHeight and write it to style.height. There is a catch in scrollHeight that decides whether the box can shrink; finding it is the point of the question.
  • Grow and shrink — deleting text must make the box shorter, not just leave it tall. Adding text must make it taller.
  • Measure before paint — do the resize in useLayoutEffect, not useEffect, or the user sees a one-frame flash of the wrong height on every keystroke.
  • box-sizing — assume box-sizing: border-box on the textarea and say so; the number you write to height depends on the box model.
  • minRows / maxRows — clamp the height to a row range using the computed line-height. Past maxRows, set overflow-y: auto so extra lines scroll instead of growing the box forever.
  • This is a controlled hook — you do not manage the textarea's value; the caller does. The hook only reads the element and sets its height.