Field
A wrapper that composes label, hint text, error message, and required indicator around any form input. Eliminates the boilerplate of wiring up for/id matching and aria-describedby by hand.
Before & After
Without the wrapper, every form field needs 5+ lines of manual composition:
<div class="space-y-1.5">
<x-wirekit::label for="email" required>Email</x-wirekit::label>
<x-wirekit::input name="email" type="email" id="email" />
@error('email')<p class="...">{{ $message }}</p>@enderror
</div>
With the wrapper, it collapses to:
<x-wirekit::field label="Email" name="email" required>
<x-wirekit::input name="email" type="email" />
</x-wirekit::field>
Basic Usage
We will never share your email.
Required Fields
The red asterisk is marked aria-hidden="true" — screen readers rely on the required attribute on the input itself (pass it through as usual: <x-wirekit::input ... required />).
Error State
The wrapper automatically reads from Laravel's $errors bag when you pass a name, so after a failed submission the matching message renders below the input — no manual @error directive needed. You can also pass an explicit error prop for one-off messages (useful in demos, previews, or programmatic validation):
Own the error on ONE level. The form controls (
input,select,textarea,checkbox,toggle) ALSO read the$errorsbag by their ownnameand render their own message + red ring +aria-invalid. If you give the samename(or an expliciterror) to BOTH thefieldand the control it wraps, the message renders twice. Pick one: givename/errorto the control when you want its red ring andaria-invalidtreatment (use thefieldfor the label + hint only, without aname), or give it to the field when a plain message below the control is enough (leave the control'sname/erroroff).
That email is already taken.
Works With Any Input
500 characters max.
Horizontal Orientation
Set orientation="horizontal" to place the label in a column beside the control instead of above it — the classic settings-form layout. The error/hint messages stay aligned under the control.
Shown on your public profile.
Grouped Fields (Fieldset + Legend)
Wrap a set of related controls — most often a radio or checkbox group — in <x-wirekit::field.set>. It renders a native <fieldset> with a <legend>, the WCAG-recommended grouping pattern: the legend is announced before each control in the set. Pass legend (and optional hint) as props, or use <x-wirekit::field.legend> inside the slot for rich legend content.
How It Works
The wrapper coordinates three pieces:
- Label — Rendered via
<x-wirekit::label :for="$name" :required="$required">. - Slot — Your input (any WireKit form component or plain HTML).
- Hint / Error — Rendered with stable IDs (
{name}-hint,{name}-error) that individual inputs already reference viaaria-describedby.
Design Decisions
- Why does the child input still need its own
name? The wrapper doesn't inject attributes into its slot — Blade slots are opaque. You passnameto both the field and the input. The wrapper uses it for the label'sforattribute and for the$errorslookup; the input uses it for submission. - Why isn't there a
sizeprop? Size is visual and belongs on the actual input (<x-wirekit::input size="lg">), not the wrapper.
Lining a Control Up With a Field
A toolbar is usually a field plus something beside it — a button, a second control. Put them in a row and the neighbor sits a label-height too high: the field's control starts below its label, and the neighbor starts at the top of the row.
items-end does not fix it, because it follows the field as the field grows. items-start
does not either, because the first thing in the field's stack is the label, not the
control.
<x-wirekit::field.spacer> puts a label-shaped blank where the label would go, so both columns
begin with the same box and items-start lines the controls up.
Put it in a <x-wirekit::field> of its own. A field is what sets the distance between a label
and the control under it, so a column that borrows the label's height has to borrow that
distance too — otherwise the top edges match and everything below them is off by that gap. Use
the field itself rather than a stack with a chosen gap: it is the same spacing, from the
component that defines it, and it stays right when that spacing changes.
More than one control goes in a row inside that same field, so the group still starts on the input's line:
It renders the real <x-wirekit::label>, not a hand-written stand-in with copied classes —
so it stays exactly as tall as a label when the typography changes. A copy would drift, and
the drift would look like a small misalignment rather than like a stale duplicate.
It is aria-hidden: a screen reader announcing an empty label would be describing a layout
decision as if it were content.
This is the other half of reserve-message. That one holds the space below a control so a
field does not shove its neighbors down when validation fires; this one holds the space
above so two columns start on the same line.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string|null | null |
Label text (omit to skip label rendering) |
name |
string|null | null |
Used for for="{name}" AND $errors->has(name) lookup |
hint |
string|null | null |
Helper text below the input |
error |
string|null | null |
Explicit 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 |
required |
bool | false |
Renders red asterisk next to label |
for |
string|null | null |
Custom for target (defaults to name) |
orientation |
string | 'vertical' |
vertical (label above) or horizontal (label beside the control) |
scope |
string|null | null |
Scoped personalization name |
<x-wirekit::field.set> props
| Prop | Type | Default | Description |
|---|---|---|---|
legend |
string|null | null |
Group label rendered as <legend> (or use <x-wirekit::field.legend> in the slot) |
hint |
string|null | null |
Optional helper text below the legend |
scope |
string|null | null |
Scoped personalization name |
<x-wirekit::field.spacer> props
| Prop | Type | Default | Description |
|---|---|---|---|
scope |
string|null | null |
Scoped personalization name |
Accessibility
for/idmatching is automatic — clicking the label always focuses the input.- Error messages are linked via
aria-describedbyon the inner input (each WireKit form component does this independently). - The error message is an ARIA live region (
aria-live="polite") by default, so a validation error that appears after a Livewire round-trip is announced without the focus returning to the field. Disable it with:announce-error="false"when the page runs its own live region. - Required indicator is
aria-hidden— the semanticrequiredattribute on the input itself is what screen readers announce.
Keyboard Interaction
This component is a layout wrapper. Keyboard interaction is delegated to its children.
Design Tokens
The Field wrapper is layout-only — it composes Label above the slot and renders hint / error text using the same tokens as Input. Visual styling on the input itself comes from whichever WireKit form component you slot in.
| Token | Used for |
|---|---|
--font-wk-sans |
Hint + error font family |
--text-wk-md |
Hint + error font size |
--color-wk-text |
Body text |
--color-wk-danger-text |
Error message |
See Also
- Inline Edit — edit this value in place, with an explicit confirm step