HextaUI

useAutosize

Grows a textarea with what you write between its minimum and maximum height, animating each change without ever touching the text.

"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

Adds the hook and anything it depends on to your project.

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> already uses this hook. Call it yourself when you render your own <textarea>, such as a chat composer or an inline editor.

On each change the hook sets the height to auto, reads the natural scrollHeight, and clamps it to the element's own CSS min-height and max-height. All of that happens before paint, so you never see the collapsed frame. The bounds stay in your CSS, so they can be responsive or use lh to count lines.

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
  • Height changes animate over 180ms, and a change that comes in mid-animation starts from the current height, so fast typing never jumps. Under reduced motion the height snaps.
  • Past max-height the textarea scrolls. Otherwise its scrollbar stays hidden, so it doesn't flash on each new line.
  • It refits when the width changes, because text that wraps differently changes the height.
  • It listens for input on window and only measures. It never reads or writes the value, so keystrokes are never delayed or dropped.

Controlled values

Typing fires input, but setting value from code doesn't. Pass the value as the third argument and the textarea refits when you insert a template or clear it.

"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>
  )
}
  • Set rows={1} and resize-none. A manual resize handle fights the automatic height.
  • Turning enabled off or unmounting removes the inline height and overflow, handing the size back to your CSS.
  • On touch screens, use at least 16px text in the textarea, or iOS zooms in when it's focused.
PropTypeDefault
refThe textarea to size.
RefObject<HTMLTextAreaElement | null>–
enabledWhether to size automatically.
boolean–
valueThe controlled value. Pass undefined for uncontrolled textareas.
unknown–

Textarea and InputGroupTextarea.