Skip to main content
WireKit
Copy for LLM

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

Field with input

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

Required indicator

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 $errors bag by their own name and render their own message + red ring + aria-invalid. If you give the same name (or an explicit error) to BOTH the field and the control it wraps, the message renders twice. Pick one: give name/error to the control when you want its red ring, its aria-invalid and its aria-describedby link (the field then carries the label and for="{name}" instead of name, 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's name/error off) — 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 wire aria-describedby="{name}-error" yourself.

Field with an explicit error message

That email is already taken.

Works With Any Input

Select inside field
Textarea inside field

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.

Horizontal field

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.

Radio group inside a fieldset
Notification frequency

Choose how often we email you.

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.

Fieldset with a rich legend
Permissions Beta

Applies to every project in the workspace.

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:

  1. Label — Rendered via <x-wirekit::label :for="$name" :required="$required">.
  2. Slot — Your input (any WireKit form component or plain HTML).
  3. Hint / Error — Rendered with stable IDs ({name}-hint, {name}-error) for you to reference with aria-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 the name — 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 pass name to both the field and the input. The wrapper uses it for the label's for attribute and for the $errors lookup; the input uses it for submission.
  • Why isn't there a size prop? 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.

A button that starts where the input starts

More than one control goes in a row inside that same field, so the group still starts on the input's line:

Two actions beside a field

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 / id matching is automatic — clicking the label always focuses the input.
  • Hint and error text rendered by a WireKit form control is linked via aria-describedby on that control — each of them does this independently for its own hint / error.
  • Hint and error text rendered by the wrapper gets the stable id {name}-hint / {name}-error and nothing more: a slot is opaque, so the wrapper cannot put aria-describedby on 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 semantic required attribute 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 the legend prop 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

Further Reading

Was this page helpful?

Voting requires cookies or local storage. What we store