Skip to main content
Pricing

Textarea

Free

A 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

State
Message
Write your message here...
24/500

Variants

VariantDescriptionWhen to use
DefaultStandard 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-resizeTextarea 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

TokenValue (dark)Value (light)CSS property
--lex-input-bgbg
--lex-input-borderborder
--lex-input-border-focusfocus
--lex-input-radiusradius
--lex-textarea-min-heightheight

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.

Keyboard Interactions

TabMoves focus into or out of the textarea.
EnterInserts a new line within the textarea.
Any characterTypes the character into the textarea.