Skip to content

InputsTextarea

Textarea

The multi-line text input. Used for long-form feedback, descriptions, and the open-response fields in the HCI study.

Auto-resize optionCharacter counterInvalid state

Demo

The default is 4 rows tall, with a 500 character cap and a live counter.

Default

0 / 500

States

States

Anatomy

  • Same border, padding, and radius as the Input
  • Resize: vertical only. No horizontal resize.
  • Character counter is label style, 0.6875 rem, 0.1 em tracking, uppercase.
  • Line height 1.5, font 0.9375 rem.

Implementation

textarea.tsx
import { forwardRef, useEffect, useRef, type TextareaHTMLAttributes } from "react"
import { cn } from "@/lib/utils"

interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
  autoResize?: boolean
  invalid?: boolean
  maxLength?: number
}

export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
  function Textarea({ autoResize, invalid, maxLength, className, value, onInput, ...props }, ref) {
    const innerRef = useRef<HTMLTextAreaElement | null>(null)
    useEffect(() => {
      if (!autoResize || !innerRef.current) return
      const el = innerRef.current
      el.style.height = "auto"
      el.style.height = el.scrollHeight + "px"
    }, [value, autoResize])
    return (
      <textarea
        ref={(node) => {
          if (typeof ref === "function") ref(node)
          innerRef.current = node
        }}
        className={cn("ds-textarea", invalid && "ds-textarea--invalid", className)}
        maxLength={maxLength}
        aria-invalid={invalid || undefined}
        value={value}
        onInput={onInput}
        {...props}
      />
    )
  },
)

Props

PropTypeDefaultDescription
rowsnumber4Visible row count
autoResizebooleanfalseGrow with content
maxLengthnumberNoneMaximum characters
invalidbooleanfalseError state