Field
v0.0.10
mix mishka.ui.gen.headless field
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)
Base UI Field example (Phoenix headless)
Visible on your profilePlease 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.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | Unique id; anchors the control/description/error ids |
for
|
string
| — | — | No | Override the control id the label points at |
name
|
string
| — | — | No | Control name, handed back via the slot's :let map |
label
|
string
| — | — | No | Visible label text |
errors
|
list
|
[]
| — | No | List of error messages to display (a non-empty list forces invalid) |
disabled
|
boolean
|
false
| — | No | Mark the field disabled (data-disabled; forces pristine) |
valid
|
boolean
| — | — | No | Validity tri-state: nil = pristine (neither attr), true = data-valid, false = data-invalid |
class
|
any
| — | — | No | Extra classes for the root |
label_class
|
any
| — | — | No | Extra classes for the label part |
control_class
|
any
| — | — | No | Extra classes for the control wrapper part |
description_class
|
any
| — | — | No | Extra classes for the description part |
error_class
|
any
| — | — | No | Extra classes for each error part |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
inner_block
| Yes | The form control. Receives :let %{id, name, describedby, valid, invalid, disabled} |
description
| No | Optional help text describing the control |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
field
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless field