El componente <textarea> que viene integrado en el navegador te permite renderizar un input de texto multilínea.

<textarea />

Referencia

<textarea>

Para mostrar un text area, renderiza el componente <textarea> que viene integrado en el navegador.

<textarea name="contenidoPost" />

Mira más ejemplos abajo.

Props

<textarea> soporta todas las props comunes de los elementos.

Puedes hacer un text area controlado pasando la prop value:

  • value: Un string. Controla el texto dentro del text area.

Cuando pasas value, también debes pasar un manejador onChange que actualice el valor proporcionado.

En cambio, si tu <textarea> no es controlado, puedes pasar la prop defaultValue:

Estas props de <textarea> son relevantes tanto para text areas controlados como no controlados:

  • autoComplete: 'on' u 'off'. Especifica el comportamiento del autocompletado.
  • autoFocus: Un booleano. Si es true, React enfocará el elemento al montarlo.
  • children: <textarea> no acepta hijos. Para establecer el valor inicial, usa defaultValue.
  • cols: Un número. Especifica la anchura por defecto en promedio de anchura de carácter. El valor por defecto es 20.
  • disabled: Un booleano. Si es true, el input no será interactivo y aparecerá atenuado.
  • form: Un string. Especifica el id del <form> al que este input pertenece. Si es omitido, es el formulario padre mas cercano.
  • maxLength: Un número. Especifica la longitud máxima del texto.
  • minLength: Un número. Especifica la longitud mínima del texto.
  • name: Un string. Especifica el nombre para este input que es enviado con el formulario.
  • onChange: Una función manejadora de eventos. Requerida para text areas controlados. Es ejecutada inmediatamente cuando el valor del input es modificado por el usuario (por ejemplo, es ejecutada con cada pulsación de tecla). Se comporta como el evento input del navegador.
  • onChangeCapture: Una versión de onChange que es ejecutada en la fase de captura.
  • onInput: Una función manejadora de eventos. Es ejecutada inmediatamente cuando el valor es cambiado por el usuario. Por razones históricas, en React es idiomático usar onChange en su lugar, el cual funciona de manera similar.
  • onInputCapture: Una versión de onInput que es ejecutada en la fase de captura.
  • onInvalid: Una función manejadora de eventos. Es ejecutada si la validación de un input fracasa al enviar el formulario. A diferencia del evento invalid que viene integrado, el evento onInvalid de React se propaga.
  • onInvalidCapture: Una versión de onInvalid que es ejecutado en la fase de captura.
  • onSelect: Una función manejadora de eventos. Es ejecutada después de que la selección dentro de <textarea> cambia. React extiende el evento onSelect para que también sea ejecutado para selecciones vacías y en ediciones (las cuales puede afectar la selección).
  • onSelectCapture: Una versión de onSelect que es ejecutada en la fase de captura.
  • placeholder: Un string. Mostrado en un color atenuado cuando el valor del text area está vacío.
  • readOnly: Un booleano. Si es true, el text area no puede ser editado por el usuario.
  • required: Un booleano. Si es true, el valor debe ser proporcionado para que el formulario sea enviado.
  • rows: Un número. Especifica la altura por defecto en promedio de altura de carácter. El valor por defecto es 2.
  • wrap: 'hard', 'soft', u 'off'. Especifica la manera en que el texto debe ser envuelto al enviar un form.

Advertencias

  • No es permitido pasar un hijo como <textarea>algo</textarea>. Usa defaultValue para el contenido inicial.
  • Si un text area recibe una prop value string, este será tratado como controlado.
  • Un text area no puede ser controlado y no controlado a la vez.
  • Un text area no puede alternar entre ser controlado o no controlado a lo largo de su vida.
  • Todo text area controlado necesita un manejador de evento onChange que actualice su valor de manera síncrona.

Uso

Mostrar un text area

Renderiza <textarea> para mostrar un text area. Puedes especificar su tamaño por defecto con los atributos rows y cols, pero por defecto el usuario será capaz de modificar su tamaño. Para deshabilitar la modificación de tamaño, puedes especificar resize: none en el CSS.

export default function NuevoPost() {
  return (
    <label>
      Escribe tu post:
      <textarea name="contenidoPost" rows={4} cols={40} />
    </label>
  );
}


Proporcionar un label para un text area

Típicamente, colocarás todos los <textarea> dentro de una etiqueta <label>. Esto le indica al navegador que este label está asociado con ese text area. Cuando el usuario haca click en el label, el navegador enfocará el text area. Esto también es esencial para accesibilidad: un lector de pantallas anunciará el texto del label cuando el usuario enfoque el text area.

Si no puedes anidar el <textarea> dentro de un <label>, asociales pasando el mismo identificador a <textarea id> y <label htmlFor>. Para evitar conflictos entre instancias de un componente, genera un identificador con useId.

import { useId } from 'react';

export default function Formulario() {
  const postTextAreaId = useId();
  return (
    <>
      <label htmlFor={postTextAreaId}>
        Escribe tu post:
      </label>
      <textarea
        id={postTextAreaId}
        name="contenidoPost"
        rows={4}
        cols={40}
      />
    </>
  );
}


Proporcionar un valor inicial para un text area

Opcionalmente puedes especificar el valor inicial de un text area. Pásalo a través de la prop defaultValue.

export default function EditarPost() {
  return (
    <label>
      Edita tu post:
      <textarea
        name="contenidoPost"
        defaultValue="¡Disfruté el paseo en bicicleta de ayer!"
        rows={4}
        cols={40}
      />
    </label>
  );
}

Atención

A diferencia de HTML, no es posible pasar el texto inicial como <textarea>Algún contenido</textarea>.


Leer el valor de text area al enviar un formulario

Agrega un <form> alrededor de tu text area con un <button type="submit"> dentro. Este llamará a tu manejador de evento <form onSubmit>. Por defecto, el navegador enviara los datos del formulario a el URL actual y actualizará la página. Puedes sobrescribir ese comportamiento llamando e.preventDefault(). Para leer los datos del formulario, usa new FormData(e.target).

export default function EditarPost() {
  function handleSubmit(e) {
    // Evita que el navegador actualice la página
    e.preventDefault();

    // Lee los datos del formulario
    const form = e.target;
    const formData = new FormData(form);

    // Puedes pasar formData directamente como body
    fetch('/some-api', { method: form.method, body: formData });

    // O puedes trabajarlo como un objeto plano:
    const formJson = Object.fromEntries(formData.entries());
    console.log(formJson);
  }

  return (
    <form method="post" onSubmit={handleSubmit}>
      <label>
        Título del post: <input name="tituloPost" defaultValue="Paseo en bicicleta" />
      </label>
      <label>
        Edita tu post:
        <textarea
          name="contenidoPost"
          defaultValue="¡Disfruté el paseo en bicicleta de ayer!"
          rows={4}
          cols={40}
        />
      </label>
      <hr />
      <button type="reset">Reiniciar cambios</button>
      <button type="submit">Guardar post</button>
    </form>
  );
}

Nota

Dale un name a tu <textarea>, por ejemplo <textarea name="contenidoPost" />. El name especificado será usado como key en los datos del form, por ejemplo { contenidoPost: "Tu contenido" }.

Atención

Por defecto, cualquier button dentro de un <form> lo enviará. ¡Esto podría tomarte por sorpresa! Si tienes tu propio componente de React Button personalizado, considera regresar <button type="button"> en lugar de <button>. Después, para ser explícito, usa <button type="submit"> en los botones que deberían enviar el formulario.


Controlar un text area con una variable de estado

Un text area como <textarea /> es no controlado. Incluso si pasas un valor inicial como <textarea defaultValue="Texto inicial" />, tu JSX solo especifica el valor inicial, no el valor actual.

Para renderizar un text area controlado, pásale la prop value. React forzará al text area a siempre tener value que pasaste. Normalmente, controlarás un text area declarando una variable de estado:

function NuevoPost() {
const [contenidoPost, asignaContenidoPost] = useState(''); // Declara una variable de estado...
// ...
return (
<textarea
value={contenidoPost} // ...fuerza al valor del input a que coincida con la variable de estado...
onChange={e => asignaContenidoPost(e.target.value)} // ... ¡y actualiza la variable de estado con cada cambio!
/>
);
}

Esto es útil si quieres re-renderizar alguna parte de la IU cada vez que una tecla sea pulsada.

import { useState } from 'react';
import MarkdownPreview from './MarkdownPreview.js';

export default function EditorMarkdown() {
  const [contenidoPost, asignaContenidoPost] = useState('_Hola,_ **Markdown**!');
  return (
    <>
      <label>
        Inserta markdown:
        <textarea
          value={contenidoPost}
          onChange={e => asignaContenidoPost(e.target.value)}
        />
      </label>
      <hr />
      <MarkdownPreview markdown={contenidoPost} />
    </>
  );
}

Atención

Si pasas value sin onChange, será imposible escribir en el text area. Cuando controlas un text area pasándole un value, lo estás forzando a siempre tener el valor proporcionado. Así que si pasas una variable de estado como un value pero olvidas actualizar esa variable de estado de manera síncrona durante el manejador de eventos onChange, React revertirá el text area al value especificado después de cada pulsación de tecla.


Solución de problemas

Mi text area no se actualiza cuando escribo en él

Si renderizas un text area con value pero sin onChange, verás un error en la consola:

// 🔴 Error: text area controlado sin manejador onChange
<textarea value={algo} />
Console
You provided a value prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultValue. Otherwise, set either onChange or readOnly.

Como sugiere el mensaje de error, si solo quisiste especificar el valor inicial, solo debes pasar defaultValue:

// ✅ Bien: text area no controlado con un valor inicial
<textarea defaultValue={algo} />

Si quieres controlar este text area con una variable de estado, especifica un manejador onChange:

// ✅ Bien: text area controlado con onChange
<textarea value={algo} onChange={e => asignaAlgo(e.target.value)} />

Si el valor es de sólo lectura intencionalmente, agrega la prop readOnly para evitar el error:

// ✅ Bien: text area controlado de solo lectura sin onChange
<textarea value={algo} readOnly={true} />

El caret de mi text area salta al inicio con cada pulsación de tecla

Si controlas un text area, debes actualizar su variable de estado al valor del text area del DOM durante onChange.

No puedes actualizarlo a algo más que no sea e.target.value:

function handleChange(e) {
// 🔴 Error: actualizar un input a algo que no sea e.target.value
asignaPrimerNombre(e.target.value.toUpperCase());
}

Tampoco puedes actualizarlo de manera asíncrona:

function handleChange(e) {
// 🔴 Error: actualizar un input de manera asíncrona
setTimeout(() => {
asignaPrimerNombre(e.target.value);
}, 100);
}

Para arreglar tu código, actualízalo de manera síncrona a e.target.value:

function handleChange(e) {
// ✅ Actualizar un input controlado a e.target.value de manera síncrona
asignaPrimerNombre(e.target.value);
}

Si esto no arregla el problema, es posible que el text area esté siendo removido y agregado nuevamente al DOM con cada pulsación de tecla. Esto puede suceder si estás reiniciando el estado accidentalmente en cada re-renderización. Por ejemplo, esto puede suceder si el text area o uno de sus padres siempre recibe un atributo key diferente, o si anidas definiciones de componentes (lo cual no está permitido en React y causa que el componente de “adentro” sea re-montado en cada renderización).


Estoy obteniendo un error: “A component is changing an uncontrolled input to be controlled”

Si proporcionas un value al componente, este valor debe mantenerse como string durante todo su tiempo de vida.

No puedes pasar value={undefined} primero y después pasar value="un string" porque React no sabrá si quieres que el componente sea controlado o no controlado. Un componente controlado siempre debe recibir un value string, no null o undefined.

Si tu value viene de una API o una variable de estado, esta podría ser inicializada como null o undefined. En ese caso, asígnala a un string vacío ('') inicialmente, o pasa value={someValue ?? ''} para asegurar que value es un string.