Component
StableForm

Textarea

Multi-line text input for longer content like notes, descriptions, and messages. Uses the same .input class as all other form fields — consistent styling, focus ring, and validation states.

Overview

Textarea shares the exact same base style as <input> and <select> — the .input class. The only additions are resize:vertical (allow height resize, never width) and min-height:80px. Always wrap in .form-group with a .form-label.

Variants

All States
Optional. Max 500 characters.
Required field.
0 / 300
Description must be at least 20 characters.

Token Reference

PropertyValueNotes
Base class.inputIdentical to input/select — same padding, radius, border, focus ring
Min height80pxNever let textarea collapse smaller than this
Resizevertical onlyNever allow horizontal resize — breaks layout
line-height1.6More readable for multi-line content
rows attribute3–5Use rows="3" for notes, rows="5" for descriptions

AI Implementation Notes

For AI coding assistants: Textarea uses the exact same .input class as text inputs — no separate class needed. Add resize:vertical and min-height:80px via CSS on the textarea.input selector. Always set a rows attribute. For character counters, use a JS input event listener that updates a .char-count div. Never use resize:none — always allow vertical resize.

When to Use

Use Textarea when
  • Multi-line text content (notes, descriptions, messages)
  • Content expected to exceed 100 characters
  • Free-form text that benefits from vertical space
Don't use Textarea when
  • Single-line data — use Input instead
  • Rich text with formatting needed — use a rich text editor
  • Code input — use a code editor component

Properties

PropertyTypeRequiredDefaultDescription
rowsnumberoptional3Initial visible row count. Use 3 for notes, 5 for descriptions. min-height:80px enforced.
maxLengthnumberoptionalundefinedCharacter limit. When set, show .input-char-count below. Turns red (.over class) when exceeded.
resizestringoptional'vertical'CSS resize property. Always vertical — never none or both.

Design Decisions

Why allow vertical resize but not horizontal?
Vertical resize lets users adjust height to see more of their content — a common and useful interaction. Horizontal resize would break the layout by allowing the textarea to overflow its container or misalign with other form fields. Horizontal resize is disabled in all cases.

Changelog

v1.0.0July 2026Added
  • Shares .input class with input and select for visual consistency
  • Character counter support, vertical-only resize
Default
Same as input, resize:vertical
Focus
Same focus ring as input
Min 20 chars
Error
border:#F05252 + error msg

Accessibility

RequirementImplementation
labelAlways include .form-label linked via for/id. Textarea must have an associated label.
aria-describedbyLink hint text: aria-describedby="hintId". Link error: aria-describedby="errorId" with aria-invalid="true".
Resizeresize:vertical is user-controlled and acceptable for accessibility. Never use resize:none — it restricts users who need larger text area.

Composition

Allowed
Inside .form-group with .form-label
With character counter (.input-char-count)
Modal body for notes/description fields
Forbidden
resize:none — always allow vertical resize
Without min-height:80px — always enforce minimum
For short single-line data — use Input instead

Best Practices

Do
Set appropriate rows attribute — rows="3" for brief notes, rows="5" for detailed descriptions.
Initial height sets user expectation about expected content length.
Don't
Use rows="1" with resize:none to make textarea look like an input.
If you want an input, use an input — misusing textarea creates inconsistent behavior.

Anatomy

1
Label
label.form-label. UPPERCASE 12px fw-700. Identical to input label.
2
Textarea Element
textarea.input. Same border, radius, focus ring as input. resize:vertical, min-height:80px, line-height:1.6.
3
Character Counter (optional)
div.input-char-count. 10px color:#94A3B8, text-align:right. Adds class .over when maxLength exceeded.

Content Rules

Add notes about this customer...
Contextual. Hints at expected content type.
Notes
Duplicates the label. Placeholder should hint at content format, not repeat the field name.

States

Default
border:#E2E8F0, bg:#fff
Focus
border:#3B5BDB + 3px ring
Min 20 chars
Error
border:#F05252 + error msg
Disabled
bg:#F8FAFC, cursor:not-allowed

Behavior

TriggerBehavior
Focusborder-color:#3B5BDB + box-shadow: 0 0 0 3px rgba(59,91,219,.1). Same focus ring as input.
TypeUpdates value. Character counter updates if maxLength set. No validation on keystroke — validate on blur.
BlurTriggers validation if field was touched. Error state applied if invalid. Focus ring removed.
Vertical resizeUser can drag bottom-right handle to increase height. Minimum height: 80px. Horizontal resize is disabled (resize:vertical only).
maxLength exceeded.input-char-count div turns red (.over class). Input continues to accept characters — the character count is a warning, not a hard stop (unless maxLength HTML attribute is also set).
Disabledbg:#F8FAFC, cursor:not-allowed, no editing, no resize. opacity:.75.