Skip to main content
Copy for LLM

Textarea

A textarea component with label, error handling, hint support, and configurable rows.

Basic Usage

A labeled textarea

Custom Rows

A taller field via rows

Disable Resize

Resize disabled

Auto-size

Set rows="auto" to grow the textarea with its content as the user types — no JavaScript needed. It uses the CSS field-sizing: content property, which is newer than the supported-browser floor, so WireKit applies it behind an @supports test: this is progressive enhancement, not something to assume. Where the property is unsupported the field keeps a two-row minimum and scrolls, and it stays resizable — nothing depends on the growing, but the growing is what the prop is named after, so reach for rows="auto" where a taller field is a nicety rather than a requirement. Auto-size grows the height only — the width follows the container, as in any real layout (here a fixed-width form column).

Grows with content

With Error

Error state

Message is required.

Errors from Laravel's validation bag are shown automatically when name matches a field, also for a nested name such as items[0][qty], whose error Laravel stores as items.0.qty, and for a list such as tags[].

With Success

Confirm a valid field with a green border and an optional message — the mirror of the error state, but valid (no aria-invalid). error wins when both are set.

Success / valid state

Saved

With Hint

With a hint below the field

Maximum 500 characters.

Character Counter

counter counts the characters in the field as the reader types, in the browser, with no request to the server. With a maxlength the count reads "120 / 500" and turns to the danger color past the limit; without one it is the number on its own.

Character counter with a limit

A string instead of true is the sentence the counter shows. Its placeholders are :count, :max, :remaining, and it is also what a screen reader hears, so write it as a sentence:

Character counter with your own sentence

The visible count is hidden from screen readers, which would otherwise read it out on every key. Once typing pauses, a polite live region says it in words: what is left of the limit, how far past it the text is, or the count itself.

It counts what maxlength counts in Chromium and Firefox, UTF-16 code units, so an emoji can count as two. With wire:model the field still sends nothing while the reader types, and a value the server sets shows its count once the server has answered. The counter runs under Alpine's CSP build. Input takes the same prop.

Width

Like all WireKit form components, the textarea fills its container (w-full). Control the width via the parent element:

Constrained width via parent container

See Input — Width for more layout examples (grid columns, mixed widths).

Optimistic UI

Pass the name of the Livewire method the field should call and the text is sent when you leave the field, shown as saving while it goes:

<x-wirekit::textarea
    name="bio"
    label="Bio"
    optimistic="saveBio"
>{{ $bio }}</x-wirekit::textarea>

Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:

@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}" defer></script>

Try it

The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.

Optimistic textarea — accepted, refused, and a slow answer

The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo methods so the page can show a real round trip. The block under it is what you write.

A refusal does not take your text back, and that is the important part. Everywhere else in WireKit an optimistic update that fails puts the old value back — for a toggle or a select that costs you nothing, because the old value is simply the other choice. Here the old value belongs to the server and the new one is what you just wrote, so putting it back would delete your work because a save failed. No editor behaves that way, and neither does this.

Instead the text stays exactly where it is, and you are told two things: that it did not save, and that your text is still there. The second one is the question you actually have.

The field is not marked invalid. aria-invalid means this value is wrong, and a save that failed on the network says nothing about the value — it may be perfectly good and simply unsaved.

What a screen reader hears

Leaving the field announces once, hedged — "Saving" — so the state is audible as provisional. Confirmation is silent: what was announced is what happened.

A refusal announces once more, and the message carries the reassurance: could not save, your text is still here.

Where the field already shows a validation message, the layer stays silent and leaves that message to speak: it tells you what to fix, "could not save" does not.

An aborted request announces nothing at all — nothing was refused.

Focus stays exactly where you put it. An undo arrives on the server's schedule, and moving focus then would take you out of your place for a reason you could not predict.

Props

Prop Type Default Description
label string|null null Label text above the textarea
hideLabel bool false Render the label visually hidden (sr-only) but keep it for assistive tech — for a compact field in a toolbar / header
hint string|null null Help text below the textarea
help string|null null An explanation of the field, shown in a tooltip from a question mark beside the label and read as the field's description. Needs a label to stand beside. See Help beside the label
reserveMessage bool false Keep the message line's height even when there is no message — see Fields in a row
error string|null null Error message (overrides $errors bag)
success string|bool|null null Valid-state confirmation — string shows a green message, true shows just the green border. error wins.
size string 'md' sm, md, lg
rows int|string 3 Number of visible rows, or 'auto' to grow with content (min height stays at the numeric fallback)
optimistic string|null null Livewire method to call when you leave the field, showing the new text as saving. A refusal keeps your text. See Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.
resize bool true Allow vertical resizing
counter bool|string false Counts the characters under the field as they are typed, in the browser with no request: true shows the number ("12 / 60" with a maxlength), a string is the sentence that shows it, with the placeholders :count, :max, :remaining. See Character Counter.
scope string|null null Scoped personalization name
announceError bool|null null Render the error message in an aria-live="polite" region so a validation error that appears after render reaches a screen reader. Precedence: this prop, then the parent <x-wirekit::form :announce-errors>, then config('wirekit.a11y.announce_error'). Set false when the page runs its own live region.

A readonly attribute reaches the textarea and paints the muted surface, the same treatment as input: the text stays legible and selectable and is submitted with the form, and the field does not look like one that takes typing.

Slots

Slot Description
labelAfter Markup after the label and its help question mark, in the same row and outside both, such as a badge that says a setting is not decided yet. See A Mark After the Label.

Accessibility

The textarea component follows the same accessibility patterns as Input.

  • Label pairing — When the label prop is set, a <x-wirekit::label> is rendered with for pointing to the textarea's id. The id is auto-generated from the name attribute.
  • Error states — When an error is present, the textarea sets aria-invalid="true" and aria-describedby="{id}-error" to link the error message for screen readers.
  • Hint text — When hint is set and neither an error nor a success message is active, the hint is linked via aria-describedby="{id}-hint". A success message takes the link instead ({id}-success), and an error takes it over both — only one description is linked at a time.
  • :user-invalid styling — Native HTML5 constraints (required, minlength, maxlength) trigger a red border after user interaction, using :user-invalid (not :invalid).
  • Character counter: the visible count of counter (see Character Counter) is hidden from screen readers, and a polite live region says the count in words once typing pauses: what is left of the limit, how far past it the text is, or the count itself, or the sentence you gave.
  • Disabled state — Uses the native disabled attribute with visual muting.
  • Focus ring — Uses focus-visible for keyboard-only visibility.

Keyboard Interaction

Key Action
Tab Move focus into the textarea (within the textarea, Tab is treated as focus-out, not as an indent character)
Enter Insert a line break
Home / End Move caret to start / end of the current line
Ctrl+Home / Ctrl+End Move caret to top / bottom of the textarea
Shift+Arrow Extend selection

Pitfalls

  • Don't use textarea for code input. Reach for <x-wirekit::code-block> (read-only) or a real editor (Monaco / CodeMirror) — textarea has no syntax highlighting, no tab-indent, and no line-numbering.
  • Don't bind wire:model.live for prose. Each keystroke round-trips. Use wire:model.blur.

Design Tokens

Token Used for
--font-wk-sans Body font family
--font-wk-letter-spacing Letter spacing
--text-wk-sm / --text-wk-md / --text-wk-lg Font size per size prop
--color-wk-text Textarea text
--color-wk-text-muted Counter / hint text
--color-wk-text-placeholder Placeholder text
--color-wk-bg-input Textarea background
--color-wk-border-strong Default border
--color-wk-border-strong-hover Hover border
--color-wk-border-error Error-state border
--color-wk-danger / --color-wk-danger-text Error message
--color-wk-ring / --color-wk-ring-offset Focus ring color + offset
--ring-wk-width / --ring-wk-offset Focus ring geometry
--border-wk-width Border width
--radius-wk-sm / --radius-wk-md Border radius
--shadow-wk-sm Subtle shadow
--padding-wk-x-sm / --padding-wk-x-md / --padding-wk-x-lg Horizontal padding
--padding-wk-y-sm / --padding-wk-y-md / --padding-wk-y-lg Vertical padding
--opacity-wk-disabled Disabled visual weight
--transition-wk-duration / --transition-wk-easing Hover / focus transition

See Also

  • Inline Edit — edit this value in place, with an explicit confirm step

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store