Overview of the Phoenix and LiveView Headless Field Component
The Mishka Chelekom Headless Field
ships zero opinionated styling
- it wraps an arbitrary control together with an optional label, an optional
description
and a list of error
messages, and does the accessibility wiring for you: it derives stable ids, points
label for
at the control, and assembles a single
aria-describedby
from the description and every error. The control is caller-supplied, so those derived
values are handed back through the default slot as a
:let
map you spread onto your input. The root also reflects
data-disabled
plus the mutually-exclusive validity tri-state
data-valid
/
data-invalid
, and the Field JS hook tracks
the control to toggle
data-focused
,
data-touched
,
data-dirty
and
data-filled
. Every part is addressable
through a
*_class
attribute, so you style it entirely with your own Tailwind
classes while
the accessibility and behavior keep working underneath.
Base UI Field example (Phoenix headless)
Visible on your profile
Please enter your name
<.field :let={f} id="baseui-field-hero" name="name" label="Name" errors={["Please enter your name"]} class="flex w-full max-w-64 flex-col items-start gap-1" label_class="text-sm font-bold text-neutral-950 dark:text-white" error_class="text-sm text-red-700 dark:text-red-400" description_class="text-sm text-neutral-600 dark:text-neutral-400" > <input type="text" required placeholder="Required" id={f.id} name={f.name} aria-describedby={f.describedby} aria-invalid={f.invalid && "true"} disabled={f.disabled} class="h-8 self-stretch border border-neutral-950 bg-white dark:bg-neutral-950 px-2 text-sm any-pointer-coarse:text-base font-normal text-neutral-950 placeholder:text-neutral-500 focus:outline-2 focus:-outline-offset-1 focus:outline-neutral-950 dark:focus:outline-white dark:border-white dark:text-white dark:placeholder:text-neutral-400" /> <:description>Visible on your profile</:description> </.field>
Parts & data attributes
Each part carries a stable
data-part
and a structural
chelekom-field__*
class you can target, plus the root's validity and interaction state.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | - | data-disabled, data-valid, data-invalid, data-touched, data-dirty, data-filled, data-focused | Carries the Field hook; reflects server-derived validity plus client-derived interaction state. |
| Label | label | for | Points at the derived control id. |
| Control | control | - | Wraps the caller's control; the Field hook tracks the input inside it. The control receives the derived id / name / aria-describedby / aria-invalid / disabled via the slot's let map. |
| Description | description | id folded into aria-describedby | Optional help text describing the control. |
| Error | error | id folded into aria-describedby | One per error message, each with a stable id-error-N id. |
Accessibility
There is no formal
WAI-ARIA APG
pattern for a form field wrapper. The label's
for
is pointed at the control, and the description plus every error message are folded into
a single
aria-describedby
on the control, so assistive technology announces the help text and any errors together.
Validation is server-driven: pass changeset errors via
errors
, and drive
valid
from your form (for example
used_input?(f) and f.errors == []
) to light up a success state.