Skip to main content
WireKit
Copy for LLM

Input

A text input with label, error handling, hints, and prefix/suffix support.

Basic Usage

A labeled text input

With Error

Error state

This field 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 confirmation message — the mirror of the error state. The field stays valid, so it never sets aria-invalid. Pass a string to show a message, or :success="true" for just the green border. error always wins when both are present.

Success / valid state

Username is available

With Hint

With a hint below the field

We'll never share your email.

With Button

Combine an input with a button for inline actions like newsletter sign-ups or search forms. The wrapper uses display: flex; align-items: flex-start; so the button sits flush with the top edge of the input control (not the label), and a top spacer on the button nudges it down to align with the input row when the input has a label above it. The label is 1rem text on a 1.5 line-height (~24px) followed by a space-y-1.5 (6px) gap, so the input control's top edge sits 30px below the wrapper top — that's the offset to match.

Email subscribe with button

We'll send you a confirmation email.

Search with icon button

Prefix & Suffix

Prefix and suffix
.00
https://

Clearable & Copyable

Opt into trailing affordance buttons with the clearable and copyable flags. clearable shows an X button — visible only while the field has content — that empties the field, refocuses it, and fires input/change so wire:model stays in sync. copyable shows a copy-to-clipboard button with a brief "Copied" state. Both are off by default, so existing inputs render unchanged.

Clearable, copyable, and both

Input Types

Use the type prop or native HTML attributes to restrict input. WireKit styles the :user-invalid pseudo-class automatically — the moment the user types something that violates min, max, pattern, type="email", required, minlength or maxlength and then leaves the field, the border and focus ring turn red without any JavaScript or server round-trip. Try it: type a letter into the phone field below, then press Tab.

Number-only input

Type a value outside 1–99 and tab out — the field turns red.

Phone number with pattern

Type a letter and tab out — the field turns red. Allowed: digits, spaces, + and -.

Why doesn't the browser prevent you from typing invalid characters? That's intentional — filtering keystrokes breaks paste, autofill, IME input, and in-place corrections (you couldn't type 100 and then backspace to 99). WireKit follows the standard web pattern used by GitHub, Stripe, and Linear: accept any input, validate after blur or on submit, and show a clear visual error state.

Character Counter

Use Alpine.js to add a live character counter with min/max validation to any input:

Live character counter with min/max
Minimum 3 characters required. Character limit reached.
0/30

The counter is not built into the input component — it's a lightweight Alpine.js wrapper you add in your template. See the Textarea docs for a multi-line version of the same pattern.

Width

All form components default to full width (w-full) — they fill whatever container they're in. Control the width by constraining the parent element:

Fixed max width
Two columns in a grid
Mixed column widths (2/3 + 1/3)

This applies to all WireKit form components: Input, Textarea, Select, Combobox, Date Picker, Time Picker, Number Input, Password Input, and File Upload.

Compact Size

size="md-compact" is a middle tier (2.25rem) between sm and md — sized for dense list/filter toolbars where sm reads cramped and md runs slightly too tall. Available on input, select, and button.

md-compact in a dense toolbar

Icons in the frame and the mono variant

The leading / trailing slots put an icon or addon inside the field frame — a search glyph, a unit symbol — with the input padding adjusting to make room. They are distinct from the text-only prefix / suffix props. Set mono to render the value in the monospace font, for SKUs, measurements, and codes:

Leading icon and a monospace SKU field

Optimistic UI

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

<x-wirekit::input
    name="nickname"
    label="Nickname"
    :value="$nickname"
    optimistic="saveNickname"
/>

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 field — 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 value back. For a toggle or a select, putting the old value back costs you nothing — it is simply the other choice. Here the old value belongs to the server and the new one is what you just typed, so restoring it would delete your work because a save failed. The value stays, and you are told two things: that it did not save, and that it is still there.

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.

What a screen reader hears Leaving the field announces once, hedged — "Saving". Confirmation is silent: what was announced is what happened. A refusal announces once more, and says the entry is still there.

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. Focus stays exactly where you put it.

Fields in a Row

A field is a vertical stack: label, control, message. When validation fires, the stack grows by the height of the message — and in a horizontal row every sibling re-anchors to the new bottom edge, so the toolbar jumps and the field the reader was reaching for is somewhere else.

Row alignment cannot fix this. items-end follows the growth by definition, and items-start lines the row up with the labels rather than the controls, so the controls sit at different heights the moment one label wraps. There is no value of align-items that anchors siblings to an element two levels down.

reserve-message keeps the line's height whether or not there is anything to say:

A toolbar that holds still

Unknown reference

The same row without the reservation — the first two fields lift as soon as the third has something to say.

Unknown reference

The top edge has the same problem

reserve-message holds the bottom. The top is the mirror image, and the same align-items argument applies to it: a button or a plain block beside a labeled field starts at the container's top while the field's control starts one label-height lower, so two things meant for one line are not on one line.

<x-wirekit::field.spacer /> gives the non-field column the same starting box. Wrap that column in a <x-wirekit::field> of its own so it also keeps the distance a field puts between a label and its control — the top edges line up on the spacer, and everything below them lines up on that:

A button that lines up with the field beside it

Without the spacer the button sits a label-height above the field it belongs to.

It renders a real <x-wirekit::label> holding a no-break space, not a <span> copying the label's classes. That distinction is the whole point: whatever a label is — font, line-height, margin, and any token behind them — the spacer is exactly as tall, because it is one. A copy drifts the first time a token changes, and nothing renders wrong when it does: the two elements simply stop being the same height.

It is aria-hidden, so a screen reader is not told about a layout decision as if it were content.

It is off by default because in a stacked form the reserved line is wasted space — an empty row under every field. Turn it on where fields sit side by side.

Available on input, select and textarea.

Props

Prop Type Default Description
label string|null null Label text above the input
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 input
reserveMessage bool false Keep the message line's height even when there is no message, so a field in a horizontal row does not move its neighbors when validation fires. See Fields in a row
error string|null null Error message (overrides $errors bag)
announceError bool true Render the error as an ARIA live region (aria-live="polite") so a dynamically appearing validation error is announced to screen readers. Set false when the page runs its own live region
success string|bool|null null Valid-state confirmation — string shows a green message, true shows just the green border. error wins when both are set.
size string 'md' sm, md, lg
type string 'text' HTML input type
optimistic string|null null Livewire method to call when you leave the field, showing the new value as saving. A refusal keeps your value. See Optimistic UI.
optimisticArgs array [] Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to.
mono bool false Render the field value in the monospace font (--font-wk-mono) — for SKUs, measurements, codes, hashes
prefix string|null null Fixed text before the value (a $, a unit). For an icon inside the frame use the leading slot
suffix string|null null Fixed text after the value (.00, a unit). For an icon inside the frame use the trailing slot
clearable bool false Shows a trailing X button (visible only while the field has content) that clears the value, refocuses, and fires input/change for wire:model sync.
copyable bool false Shows a trailing copy-to-clipboard button with a brief "Copied" confirmation.
required bool false Marks the input as required. Emits the required HTML attribute AND propagates to the associated label so its required indicator (*) renders.
disabled bool false Disables the input. Emits the disabled HTML attribute; styling already accounts for :disabled.
readonly bool false Makes the input read-only. Emits the readonly HTML attribute.
autocomplete string|null null Sets the autocomplete hint (e.g. 'email', 'name', 'new-password', 'one-time-code', 'off').
placeholder string|null null Placeholder text inside the empty input.
scope string|null null Scoped personalization name

The HTML5 form-state props also flow through the attribute bag — passing them as plain HTML attributes (<x-wirekit::input required>) is equivalent to passing them as bound props (<x-wirekit::input :required="true">). Mixing both forms is safe and produces a single attribute on the underlying <input>.

Slots

Slot Description
leading An icon or addon inside the field frame, before the value (a search glyph, a unit). Distinct from the text-only prefix prop. The slot owns its own accessibility — a decorative <x-wirekit::icon> is already aria-hidden, and the field's label is its accessible name.
trailing The same, after the value — before any clearable / copyable buttons.

Accessibility

The input component automatically handles label pairing, error announcement, and validation feedback — no manual ARIA wiring needed.

  • Label pairing — When the label prop is set, a <x-wirekit::label> is rendered with for pointing to the input's id. The id is auto-generated from the name attribute if not provided explicitly.

  • Unique ids across duplicate names — When no id is given, the input derives one from name. If two controls share a name on the same page (a create + an edit form, a filter bar + a modal, a repeater row), the first keeps the clean derived id and each later one gets a -2 / -3 suffix, so label[for] and aria-describedby always resolve to the right control while the form key name stays repeated as intended. This is on by default; set wirekit.a11y.dedupe_ids to false (env WIREKIT_DEDUPE_IDS=false) to restore the verbatim behavior. Livewire caveat: the registry resets per request, so if you repeat the same name across independently updating islands, pass an explicit id — a partial island re-render can otherwise recompute a suffixed id back to the base.

  • Error states — When an error is present (via prop or Laravel's $errors bag), the input sets aria-invalid="true" and aria-describedby="{id}-error", linking the error message to the input for screen readers. The error message is also an ARIA live region (aria-live="polite") by default, so an error that appears dynamically (e.g. after a Livewire round-trip) is announced without the focus returning to the field — set :announce-error="false" to opt out when the page runs its own live region.

  • Hint text — When hint is set and no error is active, the hint paragraph is linked via aria-describedby="{id}-hint" so assistive technology announces it when the input receives focus.

  • :user-invalid styling — Native HTML5 constraint violations (required, pattern, min, max, minlength, maxlength, type) trigger a red border and focus ring automatically after the user interacts with the field. This uses the CSS :user-invalid pseudo-class (not :invalid), so empty required fields don't show errors on page load.

  • Disabled state — Uses the native disabled attribute. Visually muted with opacity-[var(--opacity-wk-disabled)] and cursor-not-allowed.

  • Focus ring — Uses focus-visible so keyboard users see the ring but mouse users don't.

  • autocomplete — Pass the native autocomplete attribute for login forms, addresses, and payment fields to support browser autofill:

    <x-wirekit::input label="Email" name="email" type="email" autocomplete="email" />
    

Keyboard Interaction

Key Action
Tab Move focus to the input
Any character Insert the character at caret position
Backspace / Delete Remove characters around caret
Home / End Move caret to start / end of value
Shift+ArrowLeft / Shift+ArrowRight Extend text selection

Pitfalls

  • Don't forget name="..." for non-Livewire usage. Without wire:model AND without name, the value never reaches the server on form submit.
  • Don't use class="bg-...". The component reads its background from --color-wk-bg-input. Override the token in CSS, not in the class string.
  • Don't apply dark: prefix. The token system auto-switches via the parent .dark class — dark: here is redundant and bypasses the design system.

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 Input text
--color-wk-text-muted / --color-wk-text-subtle Prefix / suffix accent text
--color-wk-text-placeholder Placeholder text
--color-wk-bg-input Input 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
--size-wk-sm / --size-wk-md / --size-wk-lg Control height per size prop
--padding-wk-x-sm / --padding-wk-x-md / --padding-wk-x-lg Horizontal 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

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store