HextaUI

Textarea

Texto de varias líneas que crece con suavidad según lo que escribes entre un mínimo y un máximo de filas, con contadores, validación y un atajo para enviar.

Grows with your text, then scrolls.

import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"

export function TextareaDemo() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Feedback</FieldLabel>
      <Textarea placeholder="What could we do better? Keep typing — the box grows with you." />
      <FieldDescription>Grows with your text, then scrolls.</FieldDescription>
    </Field>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/textarea.json

Añade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.

import { Textarea } from "@/components/ui/textarea"
<Textarea placeholder="Write something…" />

La caja crece con tu texto, suavizándose en cada nueva línea en lugar de saltar, desde minRows hasta maxRows, y luego hace scroll. La animación nunca retrasa lo que escribes.

Enviar con un atajo

submitOnShortcut envía el formulario con ⌘ + Enter o Ctrl + Enter. Enter por sí solo sigue iniciando una nueva línea.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { Textarea } from "@/components/ui/textarea"

export function TextareaSubmit() {
  const [comments, setComments] = React.useState<string[]>([])

  return (
    <form
      className="flex w-full max-w-sm flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        const form = event.currentTarget
        const text = String(new FormData(form).get("comment") ?? "").trim()
        if (text) {
          setComments((list) => [...list, text])
          form.reset()
        }
      }}
    >
      {comments.length > 0 ? (
        <ul className="flex flex-col gap-1 text-sm">
          {comments.map((comment, index) => (
            <li
              key={index}
              className="rounded-md bg-muted px-3 py-2 whitespace-pre-wrap"
            >
              {comment}
            </li>
          ))}
        </ul>
      ) : null}
      <Textarea
        name="comment"
        aria-label="Comment"
        placeholder="Write a comment…"
        minRows={2}
        submitOnShortcut
      />
      <div className="flex items-center justify-between gap-3">
        <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
          <KbdGroup>
            <Kbd keys="Mod+Enter" />
          </KbdGroup>
          to send
        </span>
        <Button type="submit" size="sm">
          Comment
        </Button>
      </div>
    </form>
  )
}

Contador de caracteres

Con maxLength dentro de un Field, FieldCounter cuenta mientras escribes y avisa cuando llegas al límite.

import { Field, FieldCounter, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"

export function TextareaCounter() {
  return (
    <Field className="w-full max-w-sm">
      <div className="flex items-baseline justify-between gap-3">
        <FieldLabel>Bio</FieldLabel>
        <FieldCounter />
      </div>
      <Textarea name="bio" maxLength={160} minRows={2} />
    </Field>
  )
}

Filas

minRows y maxRows definen dónde empieza y dónde termina el crecimiento.

import { Textarea } from "@/components/ui/textarea"

export function TextareaRows() {
  return (
    <div className="grid w-full max-w-md gap-4 sm:grid-cols-2">
      <Textarea
        aria-label="One to four rows"
        minRows={1}
        maxRows={4}
        placeholder="1–4 rows"
      />
      <Textarea
        aria-label="Five to fifteen rows"
        minRows={5}
        maxRows={15}
        placeholder="5–15 rows"
      />
    </div>
  )
}

Tamaño fijo

autoResize={false} mantiene una altura fija con un tirador de redimensionamiento vertical.

import { Textarea } from "@/components/ui/textarea"

export function TextareaFixed() {
  return (
    <Textarea
      aria-label="Notes"
      autoResize={false}
      minRows={4}
      placeholder="Fixed height. Drag the corner to resize."
      className="max-w-sm"
    />
  )
}

Estados

Deshabilitado, de solo lectura y no válido. Los textareas no válidos vibran cuando falla el envío de un formulario.

import { Textarea } from "@/components/ui/textarea"

export function TextareaStates() {
  return (
    <div className="grid w-full max-w-md gap-4 sm:grid-cols-3">
      <Textarea
        aria-label="Disabled"
        disabled
        placeholder="Disabled"
        minRows={2}
      />
      <Textarea
        aria-label="Read-only"
        readOnly
        defaultValue="Read-only text you can select but not change."
        minRows={2}
      />
      <Textarea
        aria-label="Invalid"
        aria-invalid
        placeholder="Invalid"
        minRows={2}
      />
    </div>
  )
}

De derecha a izquierda

El texto, el placeholder y el cursor siguen la dirección de lectura.

import { Textarea } from "@/components/ui/textarea"

export function TextareaRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <Textarea aria-label="رسالتك" placeholder="اكتب رسالتك هنا…" />
    </div>
  )
}
KeyAcción
EnterInicia una nueva línea.
⌘EnterEnvía el formulario, con submitOnShortcut. Ctrl + Enter en Windows y Linux.
  • Etiquétalo con FieldLabel o aria-label.
  • En pantallas táctiles el texto es de al menos 16px, para que iOS no haga zoom al enfocarlo.
  • El atajo espera mientras un método de entrada está componiendo, así que confirmar una palabra en japonés o chino nunca envía el mensaje.
  • Con movimiento reducido, la caja cambia de tamaño al instante.
PropTipoPredeterminado
autoResizeCrece con el texto en lugar de tener una altura fija.
booleantrue
minRows
number3
maxRows
number10
submitOnShortcutEnvía el formulario padre con ⌘/Ctrl + Enter.
booleanfalse
shakeVibra cuando el envío de un formulario lo encuentra no válido.
booleantrue
AtributoDescripción
data-slot="textarea"El textarea.
data-autoresizePresente cuando crece con su texto.
--textarea-min-rows / --textarea-max-rowsSe define a partir de minRows y maxRows.

Usado en bloques

Bloques que se construyen sobre Textarea.