Input
A text input with label, error handling, hints, and prefix/suffix support.
Basic Usage
With Error
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.
Username is available
With Hint
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.
We'll send you a confirmation email.
Prefix & Suffix
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.
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.
Type a value outside 1–99 and tab out — the field turns red.
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:
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:
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.
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:
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.
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:
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:
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
labelprop is set, a<x-wirekit::label>is rendered withforpointing to the input'sid. Theidis auto-generated from thenameattribute if not provided explicitly. -
Unique ids across duplicate names — When no
idis given, the input derives one fromname. If two controls share anameon 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/-3suffix, solabel[for]andaria-describedbyalways resolve to the right control while the form keynamestays repeated as intended. This is on by default; setwirekit.a11y.dedupe_idstofalse(envWIREKIT_DEDUPE_IDS=false) to restore the verbatim behavior. Livewire caveat: the registry resets per request, so if you repeat the samenameacross independently updating islands, pass an explicitid— 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
$errorsbag), the input setsaria-invalid="true"andaria-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
hintis set and no error is active, the hint paragraph is linked viaaria-describedby="{id}-hint"so assistive technology announces it when the input receives focus. -
:user-invalidstyling — 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-invalidpseudo-class (not:invalid), so empty required fields don't show errors on page load. -
Disabled state — Uses the native
disabledattribute. Visually muted withopacity-[var(--opacity-wk-disabled)]andcursor-not-allowed. -
Focus ring — Uses
focus-visibleso keyboard users see the ring but mouse users don't. -
autocomplete— Pass the nativeautocompleteattribute 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. Withoutwire:modelAND withoutname, 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.darkclass —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