Skip to content
ShineUI
Install

Textarea

Notes that can breathe.

<sui-textarea>Text inputsStable

About

Multi-line text box with the shared label, help and error lines. Form-associated: it posts name=value with a plain form and binds to React, Vue (v-model) and Angular forms. Use it for notes and memos; turn on auto-resize to grow with the text, and show-count with max-length for a character count.

Usage

<sui-textarea label="Memo" name="memo" rows="3" placeholder="Notes for the accounts payable team" help="Printed on the remittance advice."></sui-textarea> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-textarea"); el.addEventListener("suiChange", (e) => console.log(e.detail)); </script>
import { SuiTextarea } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiTextarea label="Memo" name="memo" rows="3" placeholder="Notes for the accounts payable team" help="Printed on the remittance advice." onSuiChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiTextarea } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiTextarea label="Memo" name="memo" rows="3" placeholder="Notes for the accounts payable team" help="Printed on the remittance advice." @sui-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiTextarea } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiTextarea], template: `<sui-textarea label="Memo" name="memo" rows="3" placeholder="Notes for the accounts payable team" help="Printed on the remittance advice." (suiChange)="onChange($event)"></sui-textarea>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The label is bound to the inner textarea, so clicking it focuses the box. Help text is wired through aria-describedby and read after the label.

<div style="max-width:420px"> <sui-textarea label="Memo" name="memo" rows="3" placeholder="Notes for the accounts payable team" help="Printed on the remittance advice."></sui-textarea> </div>

Auto-resize

auto-resize hides the resize handle and measures after every render, so the box tracks the text instead of trailing one edit behind.

<div style="max-width:420px"> <sui-textarea label="Delivery notes" auto-resize rows="2" value="Leave the pallet at the dock door. Call the warehouse line on arrival."></sui-textarea> </div>

Character count

show-count needs maxlength. The count is described, not announced, so it does not read out on every keystroke.

<div style="max-width:420px"> <sui-textarea label="Memo" value="Fall supplies order. Deliver to the New Albany dock." maxlength="200" show-count rows="3"></sui-textarea> </div>

Sizes

sm and md set the type and padding so a one-row box lines up with the 28 and 34 controls beside it; md is the default, one height with the rest of the form. lg is the roomier box for a sign-in or marketing page. rows still sets the height.

<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;align-items:end"> <sui-textarea label="Large" size="lg" rows="2" placeholder="Sign-in page"></sui-textarea> <sui-textarea label="Medium" size="md" rows="2" placeholder="Form page"></sui-textarea> <sui-textarea label="Small" size="sm" rows="2" placeholder="Table row"></sui-textarea> </div>

States

The error line replaces help and carries role="alert", so it announces when it appears. A read-only box still posts its text; a disabled one does not.

<div style="display:grid;gap:14px;max-width:420px"> <sui-textarea label="Memo" required rows="2" error="Memo is required."></sui-textarea> <sui-textarea label="Posted memo" rows="2" value="Fall supplies order, period 09." readonly help="Locked once the period closes."></sui-textarea> <sui-textarea label="Memo" rows="2" value="Fall supplies order, period 09." disabled></sui-textarea> </div>

In a form

Each box posts one entry under its host name. Empty posts an empty string, a disabled box posts nothing, and Reset restores the text it mounted with.

Submit to see what posts.
<form id="ta-form" style="display:grid;gap:14px;max-width:420px"> <sui-textarea label="Memo" name="memo" required rows="2" value="Fall supplies order, period 09."></sui-textarea> <sui-textarea label="Delivery notes" name="notes" rows="2" placeholder="Dock instructions"></sui-textarea> <sui-textarea label="Approver comment" name="comment" rows="2" value="Approved by A. Reyes." disabled></sui-textarea> <div style="display:flex;gap:8px"> <sui-button type="submit" variant="brand" label="Post memo"></sui-button> <sui-button type="reset" variant="outline" label="Reset"></sui-button> </div> <output id="ta-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Submit to see what posts.</output> </form>
// setup(root): properties and events setup(root) { const form = root.querySelector('#ta-form'); const out = root.querySelector('#ta-out'); form.addEventListener('submit', (e) => { e.preventDefault(); out.textContent = [...new FormData(form)].map(([k, v]) => `${k}=${v || '(empty)'}`).join(' '); }); form.addEventListener('reset', () => { out.textContent = 'Reset. Submit to see what posts.'; }); }

Events

suiInput fires on every keystroke, suiChange on commit at blur. Both carry the current text as the detail. suiFocus and suiBlur follow the box.

Idle.
<div style="display:grid;gap:10px;max-width:420px"> <sui-textarea id="ta-events" label="Delivery notes" name="notes" rows="2" placeholder="Dock instructions"></sui-textarea> <span id="ta-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#ta-log'); const el = root.querySelector('#ta-events'); el.addEventListener('suiFocus', () => { log.textContent = 'suiFocus'; }); el.addEventListener('suiBlur', () => { log.textContent = 'suiBlur'; }); el.addEventListener('suiInput', (e) => { log.textContent = `suiInput ${e.detail.length} characters`; }); el.addEventListener('suiChange', (e) => { log.textContent = `suiChange ${e.detail || '(empty)'}`; }); }

Keyboard

KeyAction
TabMoves to the box and on to the next control; the host itself takes no tab stop.
Printable characters EnterType into the native textarea; Enter starts a new line and suiInput fires on every keystroke.

API

<sui-textarea>

Properties

PropertyAttributeTypeDefaultDescription
autoResizeauto-resizebooleanfalseGrows the box with its content and hides the resize handle.
autocompleteautocompletestringstring | undefinedNative autocomplete token forwarded to the textarea, e.g. "street-address" or "off". inputmode and enterkeyhint set on the host reach the input the same way, and spellcheck and autocapitalize on the host apply to it as they do to any descendant.
disableddisabledbooleanfalseDisables the field.
errorerrorstringstring | undefinedError message; paints the field in the danger tone and sets aria-invalid.
helphelpstringstring | undefinedHelp text under the field. Replaced by error when set.
invalidinvalidbooleanfalseDanger border without a message (validation handled elsewhere).
labellabelstringstring | undefinedField label above the box. Without one the box takes the host's aria-label or aria-labelledby as its name; a visible label wins over both.
maxLengthmaxlengthnumbernumber | undefinedMaximum number of characters, as the native maxlength.
minLengthminlengthnumbernumber | undefinedNative minlength: a value shorter than this, once the user has typed, fails as tooShort.
namenamestringstring | undefinedForm control name. Reflected: entry construction reads the content attribute, and the three framework bindings set props, so an unreflected name would post nothing.
placeholderplaceholderstringstring | undefinedPlaceholder text.
readOnlyreadonlybooleanfalseDisplay only: the text can be selected and copied but not edited; the field still posts its value.
requiredrequiredbooleanfalseMarks the field required (asterisk + valueMissing validity when empty).
rowsrowsnumber4Visible rows (initial height when autoResize is on).
showCountshow-countbooleanfalsePrints "n / maxLength" under the field in tabular numerals. Needs maxLength.
sizesize"lg" | "md" | "sm"'md'Type and padding: sm and md match a one-row box to the 28 and 34 controls beside it in a table row or a form (md is the default, shared with every other control); lg is the roomier box for a sign-in or marketing page. rows still sets the height.
valuevaluestring''Current value. Mutable; listen to suiInput / suiChange to observe edits.

Events

EventDetailReactVueDescription
suiBlurvoidonSuiBlur@sui-blurFires when the box loses focus; no detail. Does not bubble.
suiChangestringonSuiChange@sui-changeFires on commit (blur, native change); detail is the final value.
suiFocusvoidonSuiFocus@sui-focusFires when the box takes focus; no detail. The same pair is on every text field. Does not bubble.
suiInputstringonSuiInput@sui-inputFires on every keystroke; detail is the current value.

Methods

MethodSignatureDescription
select()select() => Promise<void>Selects the field text.
setFocus()setFocus() => Promise<void>Moves keyboard focus into the field.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also