Skip to main content
WireKit
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 feature, supported across the WireKit browser baseline). The numeric rows still acts as the minimum height. 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.

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

Combine the textarea with Alpine.js to show a live character count with min/max validation:

Live character counter with min/max
Minimum 10 characters required. Character limit reached.
0/500

The counter is not part of the textarea component itself — it's a lightweight Alpine.js wrapper you add in your template. This gives you full control over the counter position, validation messages, and threshold logic.

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') }}"></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
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
scope string|null null Scoped personalization name

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 no error is active, the hint is linked via aria-describedby="{id}-hint".
  • :user-invalid styling — Native HTML5 constraints (required, minlength, maxlength) trigger a red border after user interaction, using :user-invalid (not :invalid).
  • Character counter — When adding a character counter (see example above), ensure the counter text is perceivable. The Alpine.js pattern uses x-text which updates the DOM live — screen readers pick up changes naturally. For more explicit announcements, wrap the counter in an aria-live="polite" region.
  • 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

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store