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 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.
Give the hint to the control, not to the wrapper
The hint is on the input above, not on the field. A control renders its own hint and points its own aria-describedby at it, so a screen reader reads the two together. The wrapper cannot do that for a control it did not render — Blade slots are opaque, so field has no way to add an attribute to whatever you put inside it.
field's own hint prop still exists, and it is the right one when the slot holds markup you wrote yourself: it renders the text with the stable id {name}-hint for you to reference. Reference it — a hint that no aria-describedby points at is never announced with the field, which is WCAG 1.3.1 and 3.3.2.
<x-wirekit::field label="Email address" name="email" hint="We will never share your email.">
<input id="email" name="email" type="email" aria-describedby="email-hint" />
</x-wirekit::field>
Do not put the same hint on both — that emits the id twice, and a duplicate id makes the reference ambiguous.
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. An explicit error prop covers one-off messages (useful in demos, previews, or programmatic validation). The preview below puts both on the control, which is the shape that also gets the red ring, aria-invalid and the aria-describedby link; the field keeps for so its label still points at the input:
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, itsaria-invalidand itsaria-describedbylink (thefieldthen carries the label andfor="{name}"instead ofname, so it still points at the control without doing a second bag lookup), or give it to the field when a plain message below the control is enough (leave the control'sname/erroroff) — in that second shape the message is text next to the control rather than text attached to it, so reach for it when the control is not a WireKit one and you will wirearia-describedby="{name}-error"yourself.
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.
A Rich Legend
When the caption needs more than a string — a badge, an icon, emphasis — pass it as the named legend slot instead of the prop. It renders into the same <legend> element, in the same place.
Put the legend in the slot, not in the default slot
A <legend> must be the <fieldset>'s first child to be the group's caption. <x-wirekit::field.set> puts its default slot inside a spacing wrapper, so a <x-wirekit::field.legend> written there is a grandchild — an ordinary inline box. It looks right and is wrong: the text still appears where you put it, but the <fieldset> has no accessible name and a screen reader announces nothing before the controls.
Use the legend prop, the legend slot, or <x-wirekit::field.legend> inside a <fieldset> you wrote yourself — one of the three. In debug mode, a <legend> found in the default slot is written to the log.
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) for you to reference witharia-describedby. The wrapper does not attach them to the slot: a WireKit form control links its OWN hint and its OWN error, and never sees the wrapper's. Which one renders the message is therefore the same choice as which one owns thename— see the note under Error State.
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, with the id {name}-hint. Not attached to the slot — reference it with aria-describedby, or give the hint to the control instead |
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 caption, rendered as the <fieldset>'s first-child <legend>. Also accepts the named <x-slot:legend> for rich content |
hint |
string|null | null |
Optional helper text below the legend |
scope |
string|null | null |
Scoped personalization name |
The base, legend and hint blocks all resolve through the personalization chain, so a project whose group captions are one size smaller sets that once instead of per call site — see Customization for the priority chain and the Tailwind source-scanning caveat.
// config/wirekit.php
'components' => [
'field.set' => [
'classes' => [
'legend' => 'mb-1 text-[length:var(--text-wk-sm)] font-[number:var(--font-wk-heading-weight)] text-[color:var(--color-wk-text)]',
],
],
],
<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.- Hint and error text rendered by a WireKit form control is linked via
aria-describedbyon that control — each of them does this independently for its ownhint/error. - Hint and error text rendered by the wrapper gets the stable id
{name}-hint/{name}-errorand nothing more: a slot is opaque, so the wrapper cannot putaria-describedbyon whatever is inside it. Put the message on the control, or point at the id yourself. - 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. <x-wirekit::field.set>emits its<legend>as the<fieldset>'s first child, which is what gives the group its accessible name. That holds for thelegendprop and for the<x-slot:legend>form alike; a<legend>placed in the default slot instead is a grandchild and names nothing.
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