Textarea#

A multi-line text field on a native <textarea>. It is Input's shape minus the control box: nothing sits inside a textarea for a wrapper to hold, so the border, ring and invalid tint draw on the element itself. The model is a plain string written through on every keystroke, and the visible element is the form control.

Import#

TSX
import { Textarea } from '@sigx/zero/textarea';

Textarea is a compound: Textarea.Root, Textarea.Label, Textarea.Textarea. It is also re-exported from the @sigx/zero root, together with textareaAnatomy and useTextareaContext.

Usage#

TSX
import { component } from 'sigx';
import { Textarea } from '@sigx/zero/textarea';

const Profile = component(({ signal }) => {
    const state = signal({ bio: '' });

    return () => (
        <Textarea.Root model={() => state.bio} name="bio" rows={4} maxlength={280}>
            <Textarea.Label>Bio</Textarea.Label>
            <Textarea.Textarea placeholder="Tell us about yourself" />
        </Textarea.Root>
    );
});

model={() => state.bio} binds the text both ways — every input event writes state.bio, and writing state.bio updates the field. Leave the model off and pass defaultValue to keep the state inside the component; valueChange fires either way. See Models.

Inside a Field#

TSX
<Field.Root required invalid={!!state.error}>
    <Field.Label>Message</Field.Label>
    <Textarea.Root model={() => state.message} name="message" rows={6}>
        <Textarea.Textarea />
    </Textarea.Root>
    <Field.Description>Markdown is supported.</Field.Description>
    <Field.Error>{state.error}</Field.Error>
</Field.Root>

Inside a Field.Root the <textarea> adopts the field's control id, its disabled / invalid / required flags and its aria-describedby, so Textarea.Label becomes optional — the Field.Label names it. Standalone, the component mints its own ids and Textarea.Label is the label.

Rows and sizing#

TSX
<Textarea.Root model={() => state.notes} rows={10}>

rows passes straight through to the element. Zero does not auto-size the box — growing it means measuring scrollHeight against a collapsed height on every keystroke, which is a layout behavior rather than an anatomy one — and whether the box is user-resizable is the design system's call (resize: vertical is the usual choice).

Anatomy#

PartElementStatesFlagsNotes
rootdivdisabled, invalid, required, readonlyCarries the variant axes.
labellabeldisabled, invalid, requiredfor the textarea's id. Inside root.
textareatextareadisabled, invalid, required, readonly, focus-visibleThe native element; carries name, rows, aria-invalid and aria-describedby. The chrome draws here. Inside root.

Every part carries data-scope="textarea" and data-part="<part>". There are no machine states, only flags. Where Input has a control box as the seam for something to sit beside the text, a textarea's scrollbar and resize handle belong to the element itself, so a wrapper would be chrome with nothing to wrap: the focus-visible flag, the radius-field and size token hints all live on the textarea part. See The anatomy contract.

There is no hidden-input part either: a <textarea> is a form control and carries its own name, so it posts pre-hydration without a mirror.

Props#

Textarea.Root#

PropTypeDefaultDescription
modelstringTwo-way binding of the text.
defaultValuestring''Initial value when uncontrolled.
valueChangeevent (value: string)Fires on every change of the value.
namestringForm field name, rendered on the <textarea>.
autocompletestringNative autofill hint — street-address, off, …
maxlengthnumberNative maxlength.
rowsnumberNative rows; the element's default applies when unset.
requiredbooleanfalseRenders required and data-required; a wrapping Field's required also applies.
invalidbooleanfalseRenders aria-invalid and data-invalid; a wrapping Field's invalid also applies.
readonlybooleanfalseRenders readonly and data-readonly.
disabledbooleanfalseRenders disabled and data-disabled; a wrapping Field's disabled also applies.
color / size / variant / axes / modsdesign-system vocabularyThe variant axes, rendered as data-* on root.
classstringExtra classes on the root element.

Textarea.Label#

Only class. Renders the label part around its children.

Textarea.Textarea#

PropTypeDefaultDescription
placeholderstringNative placeholder text.
classstringExtra classes on the <textarea>.

name, autocomplete, maxlength, rows, the flags and the ids all come from Textarea.Root through context; the element part takes only what is specific to it.

Keyboard#

The platform's: multi-line text editing, Tab in and out. Enter inserts a newline rather than submitting a form, as a native <textarea> does. Zero adds nothing on top.

In the shipped design systems#

Both @sigx/zero-basic and @sigx/zero-daisyui wire color (the eight recommended roles) and size (xsxl) on textarea, so <Textarea.Root color="accent" size="sm"> is styled in both. Neither wires a variant or any mods on the scope; under a design system's /register import those props are therefore absent. See Typed vocabulary.

Input · Field