HextaUI

useAutosize

テキストエリアを、書いた内容に合わせて最小の高さと最大の高さの間で伸ばします。変化ごとにアニメーションしますが、テキストには一切触れません。

"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

フックと、その依存関係をプロジェクトに追加します。

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> はすでにこのフックを使っています。チャットのコンポーザーやインラインエディターのように、独自の <textarea> を描画する場合は、自分で呼び出してください。

変更のたびに、フックは高さを auto にして本来の scrollHeight を読み取り、要素自身の CSS の min-height と max-height の範囲に収めます。これらはすべて描画前に行われるため、縮んだフレームが見えることはありません。範囲は CSS 側に残るので、レスポンシブにしたり、lh で行数を指定したりできます。

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
  • 高さの変化は180msでアニメーションし、アニメーションの途中で来た変更は現在の高さから始まるため、素早く入力してもジャンプしません。視差効果の軽減が有効な場合、高さは即座に切り替わります。
  • max-height を超えると textarea はスクロールします。それ以外ではスクロールバーは隠れたままなので、新しい行のたびに点滅しません。
  • テキストの折り返し方が変わると高さが変わるため、幅が変わったときに再調整します。
  • window の input を監視し、計測するだけです。値の読み取りも書き込みも行わないため、キー入力が遅れたり失われたりすることはありません。

制御される値

入力では input が発火しますが、コードから value を設定しても発火しません。値を第3引数に渡すと、テンプレートの挿入やクリアの際に textarea が再調整します。

"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>
  )
}
  • rows={1} と resize-none を設定してください。手動のリサイズハンドルは自動の高さと競合します。
  • enabled をオフにするかアンマウントすると、インラインの高さと overflow が取り除かれ、サイズが CSS に戻されます。
  • タッチスクリーンでは、textarea に16px以上のテキストを使ってください。そうしないと、フォーカス時に iOS がズームします。
プロパティ型デフォルト
refサイズ調整する textarea。
RefObject<HTMLTextAreaElement | null>–
enabled自動でサイズ調整するかどうか。
boolean–
value制御される値。非制御の textarea には undefined を渡します。
unknown–

Textarea と InputGroupTextarea。