HextaUI

useAutosize

Lässt eine Textarea mit dem Geschriebenen zwischen minimaler und maximaler Höhe wachsen und animiert jede Änderung, ohne je den Text anzufassen.

"use client"

import * as React from "react"

import { useAutosize } from "@/hooks/use-autosize"

export function UseAutosizeDemo() {
  const ref = React.useRef<HTMLTextAreaElement>(null)
  useAutosize(ref, true, undefined)

  return (
    <textarea
      ref={ref}
      rows={1}
      aria-label="Note"
      placeholder="Write a note. It grows up to six lines."
      className="max-h-[calc(6lh+1rem)] min-h-[calc(1lh+1rem)] w-full max-w-sm resize-none rounded-lg bg-muted px-3 py-2 text-sm/6 outline-none placeholder:text-muted-foreground focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden pointer-coarse:text-touch"
    />
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/use-autosize.json

Fügt den Hook und alles, wovon er abhängt, zu deinem Projekt hinzu.

import { useAutosize } from "@/hooks/use-autosize"
const ref = React.useRef<HTMLTextAreaElement>(null)
useAutosize(ref, true, value)

<textarea
  ref={ref}
  rows={1}
  value={value}
  onChange={(event) => setValue(event.target.value)}
  className="min-h-[calc(1lh+1rem)] max-h-[calc(8lh+1rem)] resize-none"
/>

<Textarea autoResize> nutzt diesen Hook bereits. Rufe ihn selbst auf, wenn du dein eigenes <textarea> renderst, etwa einen Chat-Composer oder einen Inline-Editor.

Bei jeder Änderung setzt der Hook die Höhe auf auto, liest die natürliche scrollHeight und begrenzt sie auf min-height und max-height aus dem CSS des Elements. All das geschieht vor dem Paint, sodass du den eingeklappten Frame nie siehst. Die Grenzen bleiben in deinem CSS, können also responsiv sein oder lh nutzen, um Zeilen zu zählen.

min-h-[calc(2lh+1rem)]   at least two lines plus the vertical padding
max-h-[calc(8lh+1rem)]   then scroll after eight
max-h-64                 or any fixed length
  • Höhenänderungen werden über 180ms animiert, und eine Änderung mitten in der Animation startet von der aktuellen Höhe, sodass schnelles Tippen nie springt. Bei reduzierter Bewegung springt die Höhe.
  • Über max-height hinaus scrollt die Textarea. Ansonsten bleibt ihre Scrollleiste verborgen, damit sie nicht bei jeder neuen Zeile aufblitzt.
  • Er passt sich neu an, wenn sich die Breite ändert, da anders umbrechender Text die Höhe ändert.
  • Er lauscht auf input an window und misst nur. Er liest oder schreibt nie den Wert, sodass Tastenanschläge nie verzögert oder verworfen werden.

Kontrollierte Werte

Tippen löst input aus, das Setzen von value aus dem Code jedoch nicht. Übergib den Wert als drittes Argument, und die Textarea passt sich neu an, wenn du eine Vorlage einfügst oder sie leerst.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { useAutosize } from "@/hooks/use-autosize"

const template = `Hi team,

The release is out. Highlights:
- Faster search
- Offline drafts
- New keyboard shortcuts

Thanks!`

export function UseAutosizeControlled() {
  const ref = React.useRef<HTMLTextAreaElement>(null)
  const [value, setValue] = React.useState("")
  useAutosize(ref, true, value)

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <textarea
        ref={ref}
        rows={1}
        aria-label="Message"
        value={value}
        onChange={(event) => setValue(event.target.value)}
        placeholder="Message"
        className="max-h-[calc(8lh+1rem)] min-h-[calc(2lh+1rem)] w-full resize-none rounded-lg bg-muted px-3 py-2 text-sm/6 outline-none placeholder:text-muted-foreground focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:outline-hidden pointer-coarse:text-touch"
      />
      <div className="flex gap-2">
        <Button variant="outline" size="sm" onClick={() => setValue(template)}>
          Insert template
        </Button>
        <Button variant="ghost" size="sm" onClick={() => setValue("")}>
          Clear
        </Button>
      </div>
    </div>
  )
}
  • Setze rows={1} und resize-none. Ein manueller Resize-Griff kämpft gegen die automatische Höhe.
  • Das Abschalten von enabled oder das Unmounten entfernt die Inline-Höhe und das Overflow und gibt die Größe an dein CSS zurück.
  • Verwende auf Touchscreens mindestens 16px Text in der Textarea, sonst zoomt iOS beim Fokussieren hinein.
PropTypStandard
refDie Textarea, deren Größe angepasst wird.
RefObject<HTMLTextAreaElement | null>–
enabledOb automatisch dimensioniert wird.
boolean–
valueDer kontrollierte Wert. Übergib undefined für unkontrollierte Textareas.
unknown–

Textarea und InputGroupTextarea.