useTextareaAutoSize()
Live demo: useTextareaAutoSize
Autosize a textarea to content height; optionally sync the measured height to a wrapper via styleTarget.
Loading demo...
Demo sourceJSX
Overview
useTextareaAutoSize measures a <textarea>’s scrollHeight and applies the computed height either directly on the textarea or on an optional styleTarget wrapper, so the field grows/shrinks as the user types without manual height bookkeeping. It also exposes input/setInput state (kept in sync with options.input when provided) and triggerResize for cases where layout changes without a value change (fonts loaded, window resized, programmatic DOM updates).
What it accepts
options?: UseTextareaAutoSizeOptions- Optional configuration (elementRef,input,onResize,styleTarget).
What it returns
textareaRef: Ref to attach to the<textarea>(or your providedelementRef). TypeReact.RefObject<HTMLTextAreaElement | null>.input: Current textarea text managed by the hook. Typestring | undefined.setInput: Updatesinputand triggers a resize pass. Type(next: string | undefined) => void.triggerResize: Recomputes height from the current DOM state. Type() => void.
Usage
Real-world example: auto-grow a comment box, optionally sizing a wrapper (styleTarget) and observing resize events via onResize.
import { useEffect, useRef, useState } from 'react'
import useTextareaAutoSize from '@dedalik/use-react/useTextareaAutoSize'
function Example() {
const shellRef = useRef<HTMLDivElement | null>(null)
const [styleTarget, setStyleTarget] = useState<HTMLElement | undefined>(undefined)
const [resizeCount, setResizeCount] = useState(0)
useEffect(() => {
setStyleTarget(shellRef.current ?? undefined)
}, [])
const { textareaRef, input, setInput } = useTextareaAutoSize({
styleTarget,
onResize: () => setResizeCount((c) => c + 1),
})
return (
<div>
<h3>Comment</h3>
<div
ref={shellRef}
style={{
border: '1px solid #ddd',
borderRadius: 8,
padding: 12,
minHeight: 120,
}}
>
<textarea
ref={textareaRef}
value={input ?? ''}
onChange={(event) => setInput(event.target.value)}
placeholder='Write a comment…'
style={{ width: '100%', resize: 'none', lineHeight: 1.4 }}
/>
</div>
<p>Characters: {(input ?? '').length}</p>
<p>Resize events: {resizeCount}</p>
<button type='button' onClick={() => setInput(`${input ?? ''}\nThanks!`)}>
Append line
</button>
</div>
)
}
export default function Demo() {
return <Example />
}API Reference
useTextareaAutoSize
Signature: useTextareaAutoSize(options?: UseTextareaAutoSizeOptions)
Parameters
options?(optionalUseTextareaAutoSizeOptions) -elementRefuses an external textarea ref;inputcontrols the text from outside;onResizeruns after height updates;styleTargetapplies height to a wrapper element instead of the textarea.
Returns
Object with:
textareaRef- Ref to attach to the<textarea>(or your providedelementRef). (React.RefObject<HTMLTextAreaElement | null>).input- Current textarea text managed by the hook. (string | undefined).setInput- Updatesinputand triggers a resize pass. ((next: string | undefined) => void).triggerResize- Recomputes height from the current DOM state. (() => void).
Copy-paste hook
TypeScript
import { useEffect, useRef, useState, useCallback, RefObject } from 'react'
interface UseTextareaAutoSizeOptions {
/** Textarea element ref. */
elementRef?: RefObject<HTMLTextAreaElement>
/** Textarea content. */
input?: string | undefined
/** Function called when the textarea size changes. */
onResize?: () => void
/** Specify style target to apply the height based on textarea content. If not provided it will use textarea itself. */
styleTarget?: HTMLElement
}
export default function useTextareaAutoSize(options?: UseTextareaAutoSizeOptions) {
const [input, setInput] = useState<string | undefined>(options?.input)
const internalTextareaRef = useRef<HTMLTextAreaElement | null>(null)
const textareaRef = options?.elementRef || internalTextareaRef
const triggerResize = useCallback(() => {
const textarea = textareaRef.current
if (!textarea) return
let height = ''
textarea.style.height = '1px'
const textareaScrollHeight = textarea.scrollHeight
// If style target is provided update its height
if (options?.styleTarget) {
options.styleTarget.style.height = `${textareaScrollHeight}px`
}
// else update textarea's height by updating height variable
else {
height = `${textareaScrollHeight}px`
}
textarea.style.height = height
options?.onResize?.()
}, [options, textareaRef])
useEffect(() => {
triggerResize()
}, [input, triggerResize])
// Updating the local state when the input prop changes
useEffect(() => {
setInput(options?.input)
}, [options?.input])
return {
textareaRef,
input,
setInput, // To allow updating the input externally
triggerResize,
}
}
export type UseTextareaAutoSizeType = ReturnType<typeof useTextareaAutoSize>JavaScript
import { useEffect, useRef, useState, useCallback } from 'react'
export default function useTextareaAutoSize(options) {
const [input, setInput] = useState(options?.input)
const internalTextareaRef = useRef(null)
const textareaRef = options?.elementRef || internalTextareaRef
const triggerResize = useCallback(() => {
const textarea = textareaRef.current
if (!textarea) return
let height = ''
textarea.style.height = '1px'
const textareaScrollHeight = textarea.scrollHeight
if (options?.styleTarget) {
options.styleTarget.style.height = `${textareaScrollHeight}px`
} else {
height = `${textareaScrollHeight}px`
}
textarea.style.height = height
options?.onResize?.()
}, [options, textareaRef])
useEffect(() => {
triggerResize()
}, [input, triggerResize])
useEffect(() => {
setInput(options?.input)
}, [options?.input])
return {
textareaRef,
input,
setInput,
triggerResize,
}
}