Light

Textarea

Displays a form textarea or a component that looks like a textarea.

Spec · from metadata

When to use

  • Multi-line text entry such as comments, descriptions, or messages
  • Free-form content that may exceed a single line
  • Form fields where the user needs to see all their input at once

When not to use

  • Single-line text input — use Input instead
  • Rich text editing — use a dedicated rich text editor
  • Code editing — use a code editor component

Anti-patterns

Avoid<Textarea rows={3} />
Prefer<Textarea placeholder="Describe the issue..." />

Textarea uses field-sizing-content for automatic height. Setting rows overrides this behavior.

Avoid<Textarea />
Prefer<Field>
  <FieldLabel>Description</FieldLabel>
  <Textarea placeholder="Describe the issue..." />
</Field>

Always provide an accessible label via Field + FieldLabel or aria-label.

Accessibility

  • Required ARIAaria-label (when no visible label)
  • Screen readerRenders as a native <textarea>. Pair with a <label> for screen reader support.
  • ContrastUses bg-input-bg background and text-foreground for WCAG AA compliance.

Token bindings

TokenCategoryUsage
input-bgcolorBackground color
inputcolorBorder color
ringcolorFocus ring color
destructivecolorError state border and ring
rounded-mdradiusBorder radius
shadow-xsshadowBox shadow

Import

import { Textarea } from "@timelycare/helix-ui"
import { Label } from "@timelycare/helix-ui"

Props

interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  className?: string
}

Structure

PartTailwind
Textareamin-h-[60px] w-full px-3 py-2 rounded-md bg-input-bg border border-input shadow-xs text-sm
Labeltext-sm font-medium leading-none text-foreground
Descriptiontext-sm font-normal leading-normal text-muted-foreground
Placeholdertext-muted-foreground
Containerflex flex-col gap-2

States

StateBorderRingText
Defaultborder-inputtext-foreground
Filledborder-inputtext-foreground
Focusborder-ringring-[3px] ring-ring/50text-foreground
Disabledborder-inputopacity-50 cursor-not-allowed
Errorborder-destructivetext-foreground
Error (Focus)border-destructivering-[3px] ring-destructive/50text-foreground

Common Patterns

Basic Textarea

<Textarea placeholder="Type your message here" />

With Label

<div className="flex flex-col gap-2">
  <Label htmlFor="message">Message</Label>
  <Textarea id="message" placeholder="Type your message here" />
</div>

With Label and Description

<div className="flex flex-col gap-2">
  <Label htmlFor="bio">Bio</Label>
  <Textarea id="bio" placeholder="Tell us about yourself" />
  <p className="text-sm text-muted-foreground">
    Your bio will be visible on your public profile.
  </p>
</div>

Disabled

<Textarea 
  placeholder="Type your message here" 
  disabled 
/>

Error State

<div className="flex flex-col gap-2">
  <Label htmlFor="message">Message</Label>
  <Textarea 
    id="message" 
    placeholder="Type your message here"
    className="border-destructive focus-visible:ring-destructive/50"
  />
  <p className="text-sm text-destructive">
    Message is required.
  </p>
</div>

With Character Count

const [value, setValue] = useState("")
const maxLength = 280

<div className="flex flex-col gap-2">
  <Label htmlFor="tweet">Tweet</Label>
  <Textarea 
    id="tweet"
    value={value}
    onChange={(e) => setValue(e.target.value)}
    maxLength={maxLength}
    placeholder="What's happening?"
  />
  <p className="text-sm text-muted-foreground text-right">
    {value.length}/{maxLength}
  </p>
</div>

Fixed Height

<Textarea 
  placeholder="Type your message here"
  className="h-32 resize-none"
/>

Accessibility

  • Standard HTML textarea enhanced with design tokens
  • Keyboard: standard text input behavior
  • Screen reader: label announced via htmlFor/id association
  • Requirements: aria-describedby for hints, aria-invalid for errors
  • Focus visible ring applied automatically

Gotchas

ProblemSolution
Textarea not resizingDefault allows resize; use resize-none to disable
Label not clickableUse htmlFor matching Textarea id
Error ring colorOverride with focus-visible:ring-destructive/50
Min height too smallDefault is min-h-[60px], adjust as needed

See Also


Last updated: February 24, 2026