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
Optional. Max 500 characters.
Required field.
0 / 300
Description must be at least 20 characters.
Token Reference
| Property | Value | Notes |
|---|---|---|
| Base class | .input | Identical to input/select — same padding, radius, border, focus ring |
| Min height | 80px | Never let textarea collapse smaller than this |
| Resize | vertical only | Never allow horizontal resize — breaks layout |
| line-height | 1.6 | More readable for multi-line content |
| rows attribute | 3–5 | Use 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
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| rows | number | optional | 3 | Initial visible row count. Use 3 for notes, 5 for descriptions. min-height:80px enforced. |
| maxLength | number | optional | undefined | Character limit. When set, show .input-char-count below. Turns red (.over class) when exceeded. |
| resize | string | optional | '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
| Requirement | Implementation |
|---|---|
| label | Always include .form-label linked via for/id. Textarea must have an associated label. |
| aria-describedby | Link hint text: aria-describedby="hintId". Link error: aria-describedby="errorId" with aria-invalid="true". |
| Resize | resize: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.
Related Components
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
Good placeholder
Add notes about this customer...
Contextual. Hints at expected content type.
Bad placeholder
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
| Trigger | Behavior |
|---|---|
| Focus | border-color:#3B5BDB + box-shadow: 0 0 0 3px rgba(59,91,219,.1). Same focus ring as input. |
| Type | Updates value. Character counter updates if maxLength set. No validation on keystroke — validate on blur. |
| Blur | Triggers validation if field was touched. Error state applied if invalid. Focus ring removed. |
| Vertical resize | User 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). |
| Disabled | bg:#F8FAFC, cursor:not-allowed, no editing, no resize. opacity:.75. |