Textarea
Textareas are multi-line text inputs for longer-form content like comments, descriptions, or messages.
Basic Textarea
A standard multi-line text input.
<div style="max-width: 400px;">
<label class="Form-label" for="basic-textarea">Description</label>
<textarea class="Textarea" id="basic-textarea" rows="4" placeholder="Enter a description..."></textarea>
</div>
<label class="Form-label" for="basic-textarea">Description</label>
<textarea class="Textarea" id="basic-textarea" rows="4" placeholder="Enter a description..."></textarea>
With Helper Text
Add context or instructions below the textarea.
Maximum 500 characters
<div class="Form-group" style="max-width: 400px;">
<label class="Form-label" for="helper-textarea">Bio</label>
<textarea class="Textarea" id="helper-textarea" rows="3" placeholder="Tell us about yourself..."></textarea>
<span class="Form-hint">Maximum 500 characters</span>
</div>
<div class="Form-group">
<label class="Form-label" for="helper-textarea">Bio</label>
<textarea class="Textarea" id="helper-textarea" rows="3" placeholder="Tell us about yourself..."></textarea>
<span class="Form-hint">Maximum 500 characters</span>
</div>
Character Counter
Show remaining or used character count.
<div class="Form-group" style="max-width: 400px;">
<label class="Form-label" for="counter-textarea">Message</label>
<textarea class="Textarea" id="counter-textarea" rows="4" maxlength="280" placeholder="What's on your mind?"></textarea>
<div class="Textarea-footer">
<span class="Form-hint">Be concise and clear</span>
<span class="Textarea-counter">0 / 280</span>
</div>
</div>
<div class="Form-group">
<label class="Form-label" for="counter-textarea">Message</label>
<textarea class="Textarea" id="counter-textarea" rows="4" maxlength="280" placeholder="..."></textarea>
<div class="Textarea-footer">
<span class="Form-hint">Be concise and clear</span>
<span class="Textarea-counter">0 / 280</span>
</div>
</div>
Sizes
Multiple size variants for different contexts.
<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 400px;">
<div class="Form-group">
<label class="Form-label">Small</label>
<textarea class="Textarea Textarea--sm" rows="2" placeholder="Small textarea..."></textarea>
</div>
<div class="Form-group">
<label class="Form-label">Medium (Default)</label>
<textarea class="Textarea" rows="3" placeholder="Medium textarea..."></textarea>
</div>
<div class="Form-group">
<label class="Form-label">Large</label>
<textarea class="Textarea Textarea--lg" rows="4" placeholder="Large textarea..."></textarea>
</div>
</div>
<textarea class="Textarea Textarea--sm" rows="2">Small</textarea>
<textarea class="Textarea" rows="3">Medium (default)</textarea>
<textarea class="Textarea Textarea--lg" rows="4">Large</textarea>
States
Different visual states for validation and interaction.
This field is required
<div style="display: flex; flex-direction: column; gap: var(--space-4); max-width: 400px;">
<div class="Form-group">
<label class="Form-label">Default</label>
<textarea class="Textarea" rows="2" placeholder="Default state..."></textarea>
</div>
<div class="Form-group">
<label class="Form-label">Focused</label>
<textarea class="Textarea Textarea--focus" rows="2" placeholder="Focused state..."></textarea>
</div>
<div class="Form-group">
<label class="Form-label">Error</label>
<textarea class="Textarea Textarea--error" rows="2" placeholder="Error state..."></textarea>
<span class="Form-error">This field is required</span>
</div>
<div class="Form-group">
<label class="Form-label">Success</label>
<textarea class="Textarea Textarea--success" rows="2" placeholder="Success state...">Valid content here</textarea>
</div>
<div class="Form-group">
<label class="Form-label">Disabled</label>
<textarea class="Textarea" rows="2" disabled placeholder="Disabled state...">Cannot edit this</textarea>
</div>
<div class="Form-group">
<label class="Form-label">Read-only</label>
<textarea class="Textarea" rows="2" readonly>This content is read-only</textarea>
</div>
</div>
<textarea class="Textarea">Default</textarea>
<textarea class="Textarea Textarea--error">Error state</textarea>
<textarea class="Textarea Textarea--success">Success state</textarea>
<textarea class="Textarea" disabled>Disabled</textarea>
<textarea class="Textarea" readonly>Read-only</textarea>
Auto-resize
Textareas that grow with content.
Textarea expands as you type
<div class="Form-group" style="max-width: 400px;">
<label class="Form-label" for="autoresize-textarea">Notes</label>
<textarea class="Textarea Textarea--autoresize" id="autoresize-textarea" rows="2" placeholder="Start typing... the textarea will grow"></textarea>
<span class="Form-hint">Textarea expands as you type</span>
</div>
<textarea class="Textarea Textarea--autoresize" rows="2" placeholder="..."></textarea>
<script>
// Auto-resize implementation
textarea.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
</script>
With Toolbar
Add formatting controls above the textarea.
<div class="Textarea-wrapper" style="max-width: 500px;">
<div class="Textarea-toolbar">
<button type="button" class="Textarea-toolbar-btn" title="Bold">
<i class="ph ph-text-bolder"></i>
</button>
<button type="button" class="Textarea-toolbar-btn" title="Italic">
<i class="ph ph-text-italic"></i>
</button>
<button type="button" class="Textarea-toolbar-btn" title="Underline">
<i class="ph ph-text-underline"></i>
</button>
<div class="Textarea-toolbar-divider"></div>
<button type="button" class="Textarea-toolbar-btn" title="Link">
<i class="ph ph-link"></i>
</button>
<button type="button" class="Textarea-toolbar-btn" title="Image">
<i class="ph ph-image"></i>
</button>
<button type="button" class="Textarea-toolbar-btn" title="Code">
<i class="ph ph-code"></i>
</button>
<div class="Textarea-toolbar-divider"></div>
<button type="button" class="Textarea-toolbar-btn" title="List">
<i class="ph ph-list-bullets"></i>
</button>
<button type="button" class="Textarea-toolbar-btn" title="Quote">
<i class="ph ph-quotes"></i>
</button>
</div>
<textarea class="Textarea Textarea--no-top-radius" rows="6" placeholder="Write your content here..."></textarea>
</div>
<div class="Textarea-wrapper">
<div class="Textarea-toolbar">
<button type="button" class="Textarea-toolbar-btn" title="Bold">
<i class="ph ph-text-bolder"></i>
</button>
<button type="button" class="Textarea-toolbar-btn" title="Italic">
<i class="ph ph-text-italic"></i>
</button>
...
</div>
<textarea class="Textarea Textarea--no-top-radius" rows="6"></textarea>
</div>
With Action Bar
Add actions below the textarea for submit or attachments.
<div class="Textarea-wrapper" style="max-width: 500px;">
<textarea class="Textarea Textarea--no-bottom-radius" rows="4" placeholder="Write a comment..."></textarea>
<div class="Textarea-actions">
<div class="Textarea-actions-left">
<button type="button" class="Textarea-action-btn" title="Attach file">
<i class="ph ph-paperclip"></i>
</button>
<button type="button" class="Textarea-action-btn" title="Add emoji">
<i class="ph ph-smiley"></i>
</button>
<button type="button" class="Textarea-action-btn" title="Mention">
<i class="ph ph-at"></i>
</button>
</div>
<button type="submit" class="Button Button--primary Button--sm">
Post Comment
</button>
</div>
</div>
<div class="Textarea-wrapper">
<textarea class="Textarea Textarea--no-bottom-radius" rows="4"></textarea>
<div class="Textarea-actions">
<div class="Textarea-actions-left">
<button type="button" class="Textarea-action-btn">
<i class="ph ph-paperclip"></i>
</button>
...
</div>
<button type="submit" class="Button Button--primary Button--sm">
Post Comment
</button>
</div>
</div>
No Resize
Disable user resizing.
<div class="Form-group" style="max-width: 400px;">
<label class="Form-label" for="noresize-textarea">Fixed Size</label>
<textarea class="Textarea Textarea--no-resize" id="noresize-textarea" rows="4" placeholder="This textarea cannot be resized..."></textarea>
</div>
<textarea class="Textarea Textarea--no-resize" rows="4"></textarea>
Horizontal Resize Only
Allow only horizontal resizing.
<div class="Form-group" style="max-width: 600px;">
<label class="Form-label">Adjustable Width</label>
<textarea class="Textarea Textarea--resize-horizontal" rows="3" placeholder="Drag the corner to resize horizontally..."></textarea>
</div>
<textarea class="Textarea Textarea--resize-horizontal" rows="3"></textarea>
Required Field
Mark textarea as required with visual indicator.
Required field
<div class="Form-group" style="max-width: 400px;">
<label class="Form-label">
Feedback
<span class="Form-required">*</span>
</label>
<textarea class="Textarea" rows="4" required placeholder="Please share your feedback..."></textarea>
<span class="Form-hint">Required field</span>
</div>
<div class="Form-group">
<label class="Form-label">
Feedback
<span class="Form-required">*</span>
</label>
<textarea class="Textarea" rows="4" required></textarea>
</div>
Common Patterns
Comment Box
<div class="Textarea-wrapper" style="max-width: 500px;">
<textarea class="Textarea Textarea--no-bottom-radius" rows="3" placeholder="Write a comment..."></textarea>
<div class="Textarea-actions">
<div class="Textarea-actions-left">
<button type="button" class="Textarea-action-btn" title="Attach file"><i class="ph ph-paperclip"></i></button>
<button type="button" class="Textarea-action-btn" title="Emoji"><i class="ph ph-smiley"></i></button>
</div>
<button type="submit" class="Button Button--primary Button--sm">Comment</button>
</div>
</div>
Feedback Form
<div style="max-width: 450px; display: flex; flex-direction: column; gap: var(--space-4);">
<div class="Form-group">
<label class="Form-label">How can we improve?</label>
<textarea class="Textarea" rows="4" placeholder="Tell us what you think..."></textarea>
<div class="Textarea-footer">
<span class="Form-hint">Your feedback is anonymous</span>
<span class="Textarea-counter">0 / 500</span>
</div>
</div>
<div style="display: flex; justify-content: flex-end; gap: var(--space-2);">
<button class="Button Button--secondary">Cancel</button>
<button class="Button Button--primary">Submit</button>
</div>
</div>
Rich Text Editor
<div class="Textarea-wrapper" style="max-width: 550px;">
<div class="Textarea-toolbar">
<button type="button" class="Textarea-toolbar-btn" title="Bold"><i class="ph ph-text-bolder"></i></button>
<button type="button" class="Textarea-toolbar-btn" title="Italic"><i class="ph ph-text-italic"></i></button>
<button type="button" class="Textarea-toolbar-btn" title="Underline"><i class="ph ph-text-underline"></i></button>
<div class="Textarea-toolbar-divider"></div>
<button type="button" class="Textarea-toolbar-btn" title="Link"><i class="ph ph-link"></i></button>
<button type="button" class="Textarea-toolbar-btn" title="Code"><i class="ph ph-code"></i></button>
<div class="Textarea-toolbar-divider"></div>
<button type="button" class="Textarea-toolbar-btn" title="List"><i class="ph ph-list-bullets"></i></button>
<button type="button" class="Textarea-toolbar-btn" title="Quote"><i class="ph ph-quotes"></i></button>
</div>
<textarea class="Textarea Textarea--no-top-radius" rows="8" placeholder="Write your content here..."></textarea>
</div>
Bio / Profile Description
<div class="Form-group" style="max-width: 400px;">
<label class="Form-label">Bio</label>
<textarea class="Textarea" rows="3" maxlength="160" placeholder="Write a short bio...">Product designer crafting thoughtful interfaces.</textarea>
<div class="Textarea-footer">
<span class="Form-hint">Displayed on your public profile</span>
<span class="Textarea-counter">49 / 160</span>
</div>
</div>
Customization
Override textarea styles using CSS custom properties:
/* Custom border color on focus */
.Textarea:focus {
border-color: oklch(55% 0.2 150);
box-shadow: 0 0 0 3px oklch(55% 0.2 150 / 0.15);
}
/* Monospace textarea for code */
.Textarea--code {
font-family: var(--ff-m);
font-size: 0.875rem;
line-height: 1.6;
tab-size: 2;
}
/* Dark toolbar */
.Textarea-toolbar--dark {
background: var(--fg);
color: var(--bg);
}
.Textarea-toolbar--dark .Textarea-toolbar-btn {
color: var(--bg);
}
/* Minimal textarea (no border until focus) */
.Textarea--minimal {
border-color: transparent;
background: transparent;
}
.Textarea--minimal:focus {
border-color: var(--bd);
background: var(--bg);
}
API Reference
Base Classes
| Class | Description |
|---|---|
.Textarea |
Base textarea styling (required) |
.Textarea-wrapper |
Container for textarea + toolbar/actions |
.Textarea-footer |
Footer with counter/hint |
.Textarea-counter |
Character counter display |
Size Classes
| Class | Description |
|---|---|
.Textarea--sm |
Small size variant |
.Textarea--lg |
Large size variant |
State Classes
| Class | Description |
|---|---|
.Textarea--focus |
Forced focus state (for demos) |
.Textarea--error |
Error state styling |
.Textarea--success |
Success/valid state styling |
Modifier Classes
| Class | Description |
|---|---|
.Textarea--autoresize |
Auto-growing textarea |
.Textarea--no-resize |
Disables user resizing |
.Textarea--resize-horizontal |
Horizontal resize only |
.Textarea--no-top-radius |
Removes top border radius (for toolbar) |
.Textarea--no-bottom-radius |
Removes bottom border radius (for action bar) |
Toolbar Classes
| Class | Description |
|---|---|
.Textarea-toolbar |
Formatting toolbar above textarea |
.Textarea-toolbar-btn |
Toolbar button |
.Textarea-toolbar-divider |
Divider between toolbar groups |
.Textarea-actions |
Action bar below textarea |
.Textarea-actions-left |
Left-aligned actions |
.Textarea-action-btn |
Action bar button |
CSS Reference
/* Base textarea */
.Textarea {
display: block;
width: 100%;
padding: var(--space-2) var(--space-3);
font-family: var(--ff-b);
font-size: var(--text-sm);
line-height: 1.5;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--bd);
border-radius: var(--radius-md);
resize: vertical;
transition: border-color 0.15s, box-shadow 0.15s;
}
.Textarea::placeholder {
color: var(--fg-3);
}
.Textarea:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px oklch(60% 0.15 250 / 0.15);
}
/* Sizes */
.Textarea--sm {
padding: var(--space-1) var(--space-2);
font-size: var(--text-xs);
}
.Textarea--lg {
padding: var(--space-3) var(--space-4);
font-size: var(--text-base);
}
/* States */
.Textarea--error {
border-color: oklch(55% 0.2 25);
}
.Textarea--error:focus {
box-shadow: 0 0 0 3px oklch(55% 0.2 25 / 0.15);
}
.Textarea--success {
border-color: oklch(55% 0.15 150);
}
.Textarea--success:focus {
box-shadow: 0 0 0 3px oklch(55% 0.15 150 / 0.15);
}
.Textarea:disabled {
opacity: 0.5;
cursor: not-allowed;
background: var(--bg-s);
}
.Textarea[readonly] {
background: var(--bg-s);
}
/* Resize modifiers */
.Textarea--no-resize { resize: none; }
.Textarea--resize-horizontal { resize: horizontal; }
.Textarea--autoresize { resize: none; overflow: hidden; }
/* Border radius modifiers */
.Textarea--no-top-radius { border-top-left-radius: 0; border-top-right-radius: 0; }
.Textarea--no-bottom-radius { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
/* Wrapper */
.Textarea-wrapper {
display: flex;
flex-direction: column;
}
/* Toolbar */
.Textarea-toolbar {
display: flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
background: var(--bg-s);
border: 1px solid var(--bd);
border-bottom: none;
border-radius: var(--radius-md) var(--radius-md) 0 0;
}
.Textarea-toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: none;
border: none;
color: var(--fg-3);
cursor: pointer;
border-radius: var(--radius-sm);
}
.Textarea-toolbar-btn:hover {
background: var(--bg);
color: var(--fg);
}
.Textarea-toolbar-divider {
width: 1px;
height: 1rem;
background: var(--bd);
margin: 0 var(--space-1);
}
/* Actions bar */
.Textarea-actions {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-2) var(--space-3);
background: var(--bg-s);
border: 1px solid var(--bd);
border-top: none;
border-radius: 0 0 var(--radius-md) var(--radius-md);
}
.Textarea-actions-left {
display: flex;
gap: var(--space-1);
}
.Textarea-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: none;
border: none;
color: var(--fg-3);
cursor: pointer;
}
.Textarea-action-btn:hover {
color: var(--fg);
}
/* Footer */
.Textarea-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: var(--space-1);
}
.Textarea-counter {
font-size: var(--text-xs);
color: var(--fg-3);
}
Accessibility
- Always associate textareas with labels using
forandid - Use
aria-describedbyto link helper text and error messages - Provide clear placeholder text that disappears on focus
- Ensure error states are announced to screen readers
- Support keyboard navigation (Tab to focus, standard text editing)
- Use
aria-invalid="true"for error states - Include
maxlengthattribute when character limits apply - Announce character count updates to screen readers with
aria-live="polite" - Ensure toolbar buttons have descriptive
titleoraria-labelattributes
Keyboard Support
| Key | Action |
|---|---|
| Tab | Move focus to/from the textarea |
| Shift + Tab | Move focus to previous element |
| Escape | Remove focus from textarea (when appropriate) |
Best Practices
Do
- ✓ Always include a label — Every textarea needs an associated label
- ✓ Set appropriate rows — Match initial height to expected input length
- ✓ Show character limits — Display counter when maxlength is set
- ✓ Provide helper text — Explain what input is expected
- ✓ Use auto-resize for chat — Reduces unnecessary whitespace
- ✓ Include error messages — Specific, actionable validation feedback
Don’t
- ✗ Disable resize without reason — Users may need more space
- ✗ Use for single-line input — Use
<input>instead - ✗ Set tiny max heights — Cramped textareas frustrate users
- ✗ Rely on placeholder as label — Placeholders disappear on input
- ✗ Skip validation feedback — Show errors inline, not just on submit
- ✗ Use for read-only content — Use
<div>or<pre>instead