Textarea
FreeA multi-line text input for longer form content like descriptions, comments, and messages. Supports auto-resize, character count, placeholder, and validation states. Always pair with a visible label.
8 variants
Variants
| Variant | Description | When to use |
|---|---|---|
| Default | Standard multi-line text area with a resizable handle. | Use for free-form text input that may span multiple lines — bios, descriptions, feedback. |
| With Counter | ||
| Auto-resize | Textarea that grows in height as the user types. | Use when you want to minimize initial vertical space but allow unlimited input. |
Usage Guidelines
Do
Set a minimum height of 3 rows so the multi-line nature is visually clear.
Don't
Use a textarea for single-line input — use Input instead.
Do
Show a character count when there is a maximum length.
Don't
Disable resize entirely — allow at least vertical resizing.
Do
Use placeholder text to suggest the expected format or tone.
Don't
Set maxlength without showing a character count — users need feedback.
Code
HTML
<div class="lex-field">
<label class="lex-label" for="bio-textarea">Bio</label>
<textarea
id="bio-textarea"
class="lex-textarea"
rows="4"
placeholder="Tell us about yourself..."
maxlength="280"
aria-describedby="bio-count"
></textarea>
<div class="lex-field__footer">
<span id="bio-count" class="lex-field__char-count">0 / 280</span>
</div>
</div>
<div class="lex-field lex-field--error">
<label class="lex-label" for="bio-error">Bio</label>
<textarea
id="bio-error"
class="lex-textarea lex-textarea--error"
rows="4"
aria-invalid="true"
aria-describedby="bio-error-msg"
>This is way too long...</textarea>
<span class="lex-field__error" id="bio-error-msg">
Bio must be 280 characters or fewer.
</span>
</div>CSS Custom Properties
.lex-textarea {
width: 100%;
min-height: var(--lex-textarea-min-height, 96px);
padding: var(--lex-input-padding, 12px);
background: var(--lex-input-bg);
border: 1px solid var(--lex-input-border);
border-radius: var(--lex-input-radius, var(--lex-radius-md));
color: var(--lex-input-text);
font-size: var(--lex-font-size-sm);
font-family: inherit;
line-height: var(--lex-line-height-normal, 1.5);
resize: vertical;
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.lex-textarea::placeholder {
color: var(--lex-input-placeholder);
}
.lex-textarea:focus {
outline: none;
border-color: var(--lex-input-border-focus);
box-shadow: 0 0 0 2px var(--lex-input-ring);
}
.lex-textarea--error {
border-color: var(--lex-input-border-error);
}
.lex-textarea--error:focus {
box-shadow: 0 0 0 2px var(--lex-input-ring-error);
}
.lex-field__char-count {
font-size: var(--lex-font-size-xs);
color: var(--lex-text-muted);
text-align: right;
}React
// Using Lexicon CSS classes with React
export function BioField() {
return (
<Textarea
label="Bio"
placeholder="Tell us about yourself..."
maxLength={280}
showCharCount
rows={4}
/>
);
}
export function BioFieldWithError() {
return (
<Textarea
label="Bio"
rows={4}
maxLength={280}
showCharCount
error="Bio must be 280 characters or fewer."
/>
);
}Design Tokens
| Token | Value (dark) | Value (light) | CSS property |
|---|---|---|---|
| --lex-input-bg | — | — | bg |
| --lex-input-border | — | — | border |
| --lex-input-border-focus | — | — | focus |
| --lex-input-radius | — | — | radius |
| --lex-textarea-min-height | — | — | height |
Accessibility
- Associate with a visible <label> via htmlFor/id.
- Character count should be linked via aria-describedby so screen readers announce remaining characters.
- Error state uses aria-invalid="true" and aria-describedby for the error message.
- Do not use aria-live on the character count — it creates excessive announcements. Update on blur or at thresholds.