Expanding a text area as you type is simple, right?... RIGHT?

⏱︎ 2 min read

I wasn’t even gonna write about this…

thumbs up

K… So the idea is to attach an onChange event handler to <textarea />, look at the scrollheight and adjust the style accordingly… right? Something like:

Almost there UX

export const ExpandingTextareaAlmostThereButNotQuite = () => {
  const ref = useRef<HTMLTextAreaElement>(null)
  const handleChange = () => {
    const textarea = ref.current
    if (textarea) {
      textarea.style.height = `${textarea.scrollHeight}px`
    }
  }

  return (
    <textarea
      ref={ref}
      onChange={handleChange}
      placeholder="Type something..."
    />
  )
}

👉 Things seem to work fine until you start hitting backspace

A Better UX

export const ExpandingTextareaGoodStuff = ({
  minHeight = 40,
  maxHeight = 300
}: {
  minHeight?: number
  maxHeight?: number
}) => {
  const [text, setText] = useState("")
  const textareaRef = useRef<HTMLTextAreaElement>(null)

  const adjustHeight = () => {
    const textarea = textareaRef.current
    if (textarea) {
      // temporarily reduce height to trigger scrollHeight recalculation
      textarea.style.height = `${minHeight}px`
      // adjust to new scrollHeight
      textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`
    }
  }

  const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
    setText(event.target.value)
    adjustHeight()
  }

  return (
    <textarea
      ref={textareaRef}
      value={text}
      onChange={handleChange}
      style={{
        width: "100%",
        minHeight,
        overflowY: "hidden"
      }}
      placeholder="Type something..."
    />
  )
}

##🍋 Why does this work

To make the <textarea> expand and contract smoothly with its content, we need to adjust its height based on the scrollHeight. The scrollHeight property represents the total height of the content, including any part not visible due to overflow.

However, when you delete content, setting the height to scrollHeight directly doesn’t always reflect the reduction correctly because the browser may not have recalculated the scrollHeight yet.

The Solution: We temporarily reduce the height of the <textarea> to its minimum height. This triggers the browser to recalculate the scrollHeight based on the current content.