<textarea>

Renders a multi-line text box whose starting text comes from defaultValue, not children.

Signature
<textarea name="postContent" />

Parameters

ParameterTypeDescription
$valueoptionalstringControls the text inside the text area. Passing it makes the text area controlled and requires an onChange handler.
$defaultValueoptionalstringSpecifies the initial value for a text area. This is how you set starting text — children are not allowed.
$onChangeoptional(event: Event) => voidRequired for controlled text areas. Fires immediately when the value is changed by the user, on every keystroke. Behaves like the browser input event.
$rowsoptionalnumber= 2Specifies the default height in average character heights.
$colsoptionalnumber= 20Specifies the default width in average character widths.
$maxLengthoptionalnumberSpecifies the maximum length of text.
$minLengthoptionalnumberSpecifies the minimum length of text.
$wrapoptional'hard' | 'soft' | 'off'= 'soft'Specifies how the text should be wrapped when submitting a form.
$nameoptionalstringSpecifies the name for this input that is submitted with the form.
$placeholderoptionalstringDisplayed in a dimmed color when the text area value is empty.
$...propsoptionalHTMLTextAreaElement props<textarea> supports all common element props plus autoComplete ('on' or 'off'), autoFocus, disabled, form, readOnly, required, and the onInput, onInvalid and onSelect handlers with their capture-phase variants. It does not accept children.

Return value

ReactElementRenders a browser <textarea> element. Its text is owned by React when value is passed and by the DOM otherwise.

Errors

React errors on children — <textarea>text</textarea> is not allowed — and logs an error when a text area switches between controlled and uncontrolled during its lifetime.

<textarea> behaves like a text <input> with one difference that catches everyone: in HTML its initial text goes between the tags, and in React that is forbidden. Writing <textarea>Hello</textarea> is an error. Pass defaultValue="Hello" for an uncontrolled box, or value plus onChange for a controlled one. React made this change so the same prop names work for every field type.

When to reach for it

Control it when you need the text while the user types — a character counter, a live Markdown preview, a save button that enables once something is written. Leave it uncontrolled for a plain comment or feedback box read once on submit; a long controlled textarea re-renders its subtree on every keystroke, which is the usual cause of laggy typing in a big form.

What trips people up

  • rows and cols set the initial size only; the browser does not grow the box as text is added. Auto-sizing means measuring scrollHeight yourself.
  • Newlines are real characters in the value, so maxLength counts them.
  • Resetting a controlled textarea means setting state to '', not clearing the DOM node.

Example

Try it yourself
Loading editor…
Loading React…

When to use it

  • Add a comment box with a live character counter and a maxLength cap
  • Write a Markdown editor whose preview updates on every keystroke
  • Collect free-form feedback that is only read once when the form submits

Frequently asked questions

How do I set the initial text of a textarea in React?

Use the defaultValue prop: <textarea defaultValue="Hello" />. Children are not supported — <textarea>Hello</textarea> throws — because React unifies the API across all form fields. For a controlled text area, use value with an onChange handler instead.

Why is my textarea not resizing to fit its content?

rows and cols only set the initial size; browsers never auto-grow a textarea. Reset the element's height to 'auto' and then set it to its scrollHeight in a layout effect that runs whenever the text changes, or use a CSS grid trick that sizes a hidden mirror element behind the field.

Why does typing in a large form with a textarea feel slow?

A controlled textarea re-renders its whole parent tree on every keystroke. Either make the field uncontrolled and read it on submit, or move the field and its state into a small child component so only that component re-renders while the rest of the form stays untouched.

More examples

Uncontrolled, with the text read on submit

The box opens with two lines of text, set through defaultValue with an explicit \n — the only way to seed multi-line content, since children are not allowed. Submitting renders the same two lines inside a <pre>, showing that the newline survives into FormData intact. Nothing re-renders while the user types, which is what makes the uncontrolled form the cheaper choice for a long text area.

Example · jsx
import { useState } from "react";

export default function FeedbackForm() {
  const [sent, setSent] = useState(null);

  function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    setSent(String(data.get("feedback")));
  }

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        name="feedback"
        rows={3}
        defaultValue={"First line\nSecond line"}
        required
      />
      <button type="submit">Send</button>
      {sent !== null && <pre>{sent}</pre>}
    </form>
  );
}

A textarea that grows with its content

The box starts one row tall and gains a row each time the text wraps or a newline is typed. The height must be reset to "auto" before reading scrollHeight, otherwise the previous explicit height keeps scrollHeight from ever shrinking and the box only ever grows. useLayoutEffect rather than useEffect runs the measurement before the browser paints, so the resize is not visible as a flicker.

Example · jsx
import { useLayoutEffect, useRef, useState } from "react";

export default function AutoGrowNote() {
  const ref = useRef(null);
  const [text, setText] = useState("Type and this box grows.");

  useLayoutEffect(() => {
    const node = ref.current;
    node.style.height = "auto";
    node.style.height = node.scrollHeight + "px";
  }, [text]);

  return (
    <textarea
      ref={ref}
      rows={1}
      value={text}
      onChange={(e) => setText(e.target.value)}
      style={{ width: "100%", overflow: "hidden", resize: "none" }}
    />
  );
}

Discussion

  • Be the first to comment on this lesson.