J3W1 / DESIGN SYSTEM
Textarea
Multi-line free-text entry in the same box as the text field; resizes vertically only and can show a character count.
stable · LIGHT DOM · 1.1.0
Visible native controls retain FormData, labels, constraints, disabled fieldsets and reset. No duplicate form-value mechanism is installed.
Live variants
default
Plain text; one change per line.
<j3w1-textarea id="example-textarea-default"><div class="textarea">
<label class="textarea-label" for="example-textarea-default-ta-notes">Release notes <span class="textarea-required" aria-hidden="true">*</span><span class="textarea-required sr-only"> required</span></label>
<div class="textarea-root">
<textarea class="textarea-input" id="example-textarea-default-ta-notes" rows="3" placeholder="What changed and why" aria-describedby="example-textarea-default-ta-notes-help example-textarea-default-ta-notes-message" name="example-textarea-default-ta-notes">Focus rings now recolour on filled surfaces.
Disabled controls no longer use opacity.</textarea>
</div>
<p class="textarea-help" id="example-textarea-default-ta-notes-help">Plain text; one change per line.</p>
<p class="textarea-message" id="example-textarea-default-ta-notes-message">✕ Say why, not only what.</p>
</div></j3w1-textarea>with-count
46 / 200
Counts characters, not bytes.
<j3w1-textarea id="example-textarea-with-count"><div class="textarea">
<label class="textarea-label" for="example-textarea-with-count-ta-summary">Commit summary <span class="textarea-required" aria-hidden="true">*</span><span class="textarea-required sr-only"> required</span></label>
<div class="textarea-root">
<textarea class="textarea-input" id="example-textarea-with-count-ta-summary" rows="3" maxlength="200" placeholder="Under 200 characters" aria-describedby="example-textarea-with-count-ta-summary-help example-textarea-with-count-ta-summary-count example-textarea-with-count-ta-summary-message" name="example-textarea-with-count-ta-summary">spec: button, link and the basic form controls</textarea>
</div>
<p class="textarea-count" id="example-textarea-with-count-ta-summary-count" aria-live="polite">46 / 200</p>
<p class="textarea-help" id="example-textarea-with-count-ta-summary-help">Counts characters, not bytes.</p>
<p class="textarea-message" id="example-textarea-with-count-ta-summary-message">✕ Over the limit.</p>
</div></j3w1-textarea>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@1.1.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/textarea.css';
import '@j3w1/ui/register/textarea';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@1.1.0 j3w1-ui copy textarea --out ./vendor/j3w1/textareaThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
value | CustomEvent | Forwards value to the primary native control. |
required | CustomEvent | Forwards required to the primary native control. |
readonly | CustomEvent | Forwards readonly to the primary native control. |
checked | CustomEvent | Forwards checked to the primary native control. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
value | string | Primary native value; assignment does not fabricate user events. |
checked | boolean | Native checkbox state. |
required | boolean | Native required constraint. |
readOnly | boolean | Native readonly property. |
form | HTMLFormElement | null | Owning native form. Read only. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
checkValidity | (): boolean | Check owned native constraints. |
reportValidity | (): boolean | Report the first invalid owned control. |
clear | (): void | Clear an editable primary input and emit native input/change. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | Native change from an owned control, alongside the original native event. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Purpose
Free text longer than a line: descriptions, messages, notes. It shares the text field’s box, states and message so a form reads as one system. Code goes in the code-editor component, not a textarea.
Anatomy
Label above; the box with the native <textarea> inside, at least three
rows tall, resizable downwards only; the optional count; help; the
validation message that takes the help slot visually while help stays in the
DOM.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | 1px color.border.control on color.surface.input; text color.text.default | — |
| hover | background → color.interaction.hover.bg; border unchanged | cursor: text |
| focus-visible | border → color.border.active; ring 1px dashed color.interaction.focus.ring at −2px | the ring |
| placeholder-shown | placeholder in color.text.placeholder, italic | italic |
| filled | as default with a value | — |
| required | * after the label in color.status.danger.text plus visually hidden “required” | required; the mark |
| invalid | border 2px color.status.danger.border; message with the ✕ glyph; a count past its limit turns color.status.danger.text with the glyph | border width 1 → 2px; glyph; aria-invalid |
| invalid+focus-visible | the 2px danger border and the ring in color.interaction.focus.ring-container at −4px | double boundary |
| disabled | text color.text.disabled; border color.border.disabled; background color.interaction.disabled.bg; no resize handle | disabled; cursor: not-allowed |
| read-only | no box: 1px dotted color.border.divider bottom edge on color.surface.canvas; no resize handle; selectable | readonly; dotted edge |
Precedence: disabled > invalid > hover; focus-visible is always drawn.
Keyboard
Native editing. Enter inserts a line break and never submits. Tab leaves the control; a host that needs literal tabs provides a separate command and documents it. The count is not focusable.
Accessibility
A programmatic label is required. Help, count and message are linked
through aria-describedby in that order. The count is aria-live="polite"
and throttled so it does not announce every keystroke; the limit is stated
in the help text so it is known before typing. aria-invalid="true" only
after interaction or submit. Contrast as the text field: text 8.65:1,
placeholder 5.81:1, control border 4.45:1, invalid border 4.69:1, message
5.40:1.
Portability
Border, background, outline and resize: vertical. Hosts without a resize
handle grow the box with content up to a maximum and record it. Hosts
without dashed outlines draw a solid 1px ring.
Non-examples
Horizontal or free resizing. A box narrower than its label column. A count that only changes colour past the limit. A glow instead of the dashed ring. Rounded corners. An auto-growing box with no maximum. Tab inserting a character. Placeholder text as the label. Disabling by opacity.