Textarea
A multi-line text field that grows with its content.
Basic
Add .textarea to any <textarea>. The fixed-height contract from .input drops, so rows and content drive the height. The resize handle is vertical only, so the width tracks the parent.
<div class="w-100" style="max-width: 24rem;">
<label for="basicTextarea" class="form-label">Notes</label>
<textarea class="textarea" id="basicTextarea" rows="3" placeholder="Anything else we should know?"></textarea>
</div>Sizes
Three sizes match the input scale so a textarea sits beside an input in the same row. Add .textarea--sm or .textarea--lg.
<div class="demo-stack w-100" style="max-width: 24rem;">
<textarea class="textarea textarea--sm" rows="2" placeholder="Small"></textarea>
<textarea class="textarea" rows="2" placeholder="Default"></textarea>
<textarea class="textarea textarea--lg" rows="2" placeholder="Large"></textarea>
</div>Helper text
Use .form-text below the field for short hints. Wire it to the textarea with aria-describedby so screen readers announce it.
<div class="w-100" style="max-width: 24rem;">
<label for="bioTextarea" class="form-label">Bio</label>
<textarea class="textarea" id="bioTextarea" rows="3" aria-describedby="bioHelp"></textarea>
<div id="bioHelp" class="form-text">A sentence or two. Visible on your public profile.</div>
</div>Browser validation
Pair native constraint attributes (required, minlength, maxlength) with the :user-invalid pseudo. The browser fires it after the user interacts with the field, and clears it the moment the value satisfies the constraints.
<form class="demo-stack w-100" style="max-width: 24rem;" onsubmit="event.preventDefault()">
<div>
<label for="reqMessage" class="form-label">Message</label>
<textarea class="textarea" id="reqMessage" rows="3" required minlength="10" placeholder="At least 10 characters"></textarea>
<div class="form-text">Hit Submit with fewer than 10 characters to trigger <code>:user-invalid</code>. Type past 10 and the red clears on its own.</div>
</div>
<button type="submit" class="btn btn--primary">Submit</button>
</form>Server validation
Set aria-invalid="true" from your form library. The attribute is sticky, and Stisla just paints while it's present. Pair with a .form-text--error message tied via aria-describedby.
<div class="demo-stack w-100" style="max-width: 24rem;">
<div>
<label for="srvFeedback" class="form-label">Feedback</label>
<textarea
class="textarea"
id="srvFeedback"
rows="3"
aria-invalid="true"
aria-describedby="srvFeedbackError">Way too short.</textarea>
<div id="srvFeedbackError" class="form-text form-text--error">Please write at least 50 characters.</div>
</div>
</div>Disabled & readonly
disabled blocks interaction and dims the field. readonly keeps the value selectable for copy but rejects edits. The bg shifts a tier so you can see at a glance that the field is selectable but not editable.
<div class="demo-stack w-100" style="max-width: 24rem;">
<textarea class="textarea" rows="2" disabled>Disabled</textarea>
<textarea class="textarea" rows="2" readonly>Readonly</textarea>
</div>Customization
Eight variables retune .textarea independently of .input. Override on the field itself, on a parent scope, or on :root. --textarea-height acts as a min-height floor (the field grows past it as content fills in).
| Variable | Default | Use |
|---|---|---|
--textarea-radius |
var(--st-input-radius, var(--st-radius)) | Corner radius; sizes pull --st-radius-sm / -lg |
--textarea-height |
2.25rem | Min-height floor; the field grows past this with content. sm and lg reassign this |
--textarea-padding-x |
calc(0.625rem * var(--st-density)) | Horizontal padding; sm and lg reassign this |
--textarea-font-size |
0.875rem | Text size; sm and lg reassign this |
--textarea-bg |
var(--st-surface) | Background; readonly shifts a tier |
--textarea-color |
var(--st-foreground) | Text color |
--textarea-border |
var(--st-border) | Border color; validation hooks flip this to --st-danger |
--textarea-placeholder |
var(--st-muted-foreground) | Placeholder text color |