Text Input
v0.0.10
mix mishka.ui.gen.headless text_input
Overview of the Phoenix and LiveView Headless Text Input Component
The Mishka Chelekom Headless Text Input is an unstyled Phoenix function component for a single-line control with optional leading and trailing sections. The root is the control box and the input sits inside it. Your classes paint the box and leave the input transparent, which is what lets an icon, a prefix or a unit sit inside the border. The parts are start-section, input and end-section, and they take class, start_section_class, input_class and end_section_class.
You can wire it two ways, and they combine. Pass field={@form[:email]} and it takes its id, name, value and errors from the Phoenix form. Or pass name, value and errors yourself. In a LiveView, errors from a form field only appear once the user has touched the input. errors and valid are reflected as aria-invalid, data-invalid and data-valid, so you can paint either state from CSS.
Base UI Text Input example - field with a leading section (Phoenix headless)
Base UI Text Input example - field with a leading section (Phoenix headless)
<.text_input
id="baseui-text-input-hero"
name="site"
placeholder="example.com"
class="flex w-full max-w-80 items-center rounded-md border border-neutral-200 bg-white text-sm text-neutral-950 dark:border-neutral-800 dark:bg-neutral-950 dark:text-white focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-neutral-950 dark:focus-within:outline-white [&_[data-part=input]]:w-full [&_[data-part=input]]:bg-transparent [&_[data-part=input]]:px-3 [&_[data-part=input]]:py-1.5 [&_[data-part=input]]:outline-none [&_[data-part=start-section]]:pl-3 [&_[data-part=start-section]]:text-neutral-500"
>
<:start_section>https://</:start_section>
</.text_input>Base UI Text Input example - invalid field (Phoenix headless)
This example has no preview. errors is a list, and a page marker can only pass text. A non-empty list sets aria-invalid="true" on the input and data-invalid on the root and on the input, which the data-[invalid]: classes below turn into a red border.
<.text_input
id="baseui-text-input-invalid"
name="email"
type="email"
value="nope"
errors={["must have the @ sign"]}
class="flex w-full max-w-80 items-center rounded-md border border-neutral-200 bg-white text-sm text-neutral-950 dark:border-neutral-800 dark:bg-neutral-950 dark:text-white focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-neutral-950 dark:focus-within:outline-white [&_[data-part=input]]:w-full [&_[data-part=input]]:bg-transparent [&_[data-part=input]]:px-3 [&_[data-part=input]]:py-1.5 [&_[data-part=input]]:outline-none [&_[data-part=start-section]]:pl-3 [&_[data-part=start-section]]:text-neutral-500 data-[invalid]:border-red-600 dark:data-[invalid]:border-red-500"
/>Text Input example - valid, disabled and read-only states (Phoenix headless)
Text Input example - valid, disabled and read-only states (Phoenix headless)
<div class="flex flex-col gap-3">
<.text_input
id="text-input-valid"
name="valid"
type="email"
value="[email protected]"
valid
class="flex w-full max-w-80 items-center rounded-md border border-neutral-200 bg-white text-sm text-neutral-950 dark:border-neutral-800 dark:bg-neutral-950 dark:text-white focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-neutral-950 dark:focus-within:outline-white [&_[data-part=input]]:w-full [&_[data-part=input]]:bg-transparent [&_[data-part=input]]:px-3 [&_[data-part=input]]:py-1.5 [&_[data-part=input]]:outline-none [&_[data-part=start-section]]:pl-3 [&_[data-part=start-section]]:text-neutral-500 data-[valid]:border-green-600 dark:data-[valid]:border-green-500 data-[disabled]:opacity-50 data-[readonly]:bg-neutral-50 dark:data-[readonly]:bg-neutral-900"
/>
<.text_input
id="text-input-disabled"
name="disabled"
value="Disabled"
disabled
class="flex w-full max-w-80 items-center rounded-md border border-neutral-200 bg-white text-sm text-neutral-950 dark:border-neutral-800 dark:bg-neutral-950 dark:text-white focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-neutral-950 dark:focus-within:outline-white [&_[data-part=input]]:w-full [&_[data-part=input]]:bg-transparent [&_[data-part=input]]:px-3 [&_[data-part=input]]:py-1.5 [&_[data-part=input]]:outline-none [&_[data-part=start-section]]:pl-3 [&_[data-part=start-section]]:text-neutral-500 data-[valid]:border-green-600 dark:data-[valid]:border-green-500 data-[disabled]:opacity-50 data-[readonly]:bg-neutral-50 dark:data-[readonly]:bg-neutral-900"
/>
<.text_input
id="text-input-readonly"
name="readonly"
value="Read only"
readonly
class="flex w-full max-w-80 items-center rounded-md border border-neutral-200 bg-white text-sm text-neutral-950 dark:border-neutral-800 dark:bg-neutral-950 dark:text-white focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-neutral-950 dark:focus-within:outline-white [&_[data-part=input]]:w-full [&_[data-part=input]]:bg-transparent [&_[data-part=input]]:px-3 [&_[data-part=input]]:py-1.5 [&_[data-part=input]]:outline-none [&_[data-part=start-section]]:pl-3 [&_[data-part=start-section]]:text-neutral-500 data-[valid]:border-green-600 dark:data-[valid]:border-green-500 data-[disabled]:opacity-50 data-[readonly]:bg-neutral-50 dark:data-[readonly]:bg-neutral-900"
/>
</div>Forms and fields
With field, the component reads id, name, value and errors from the form field. Errors are used only after Phoenix.Component.used_input?/1 says the user has touched the input, so a fresh form is not red. A value, id or name you pass yourself wins over the one from the field.
<.text_input
field={@form[:email]}
type="email"
placeholder="[email protected]"
class="flex w-full max-w-80 items-center rounded-md border data-[invalid]:border-red-600"
/>Inside a Headless Field, pass the ids it hands back through :let. The field renders the label, the description and the error messages, and describedby links them to the input.
<.field :let={f} id="email-field" name="email" label="Email" errors={@errors}>
<.text_input
id={f.id}
name={f.name}
type="email"
errors={@errors}
describedby={f.describedby}
/>
<:description>We will never share it.</:description>
</.field>Attributes
type is one of text, email, password, search, tel, url, number, date, time, datetime-local, month or week, and defaults to text. placeholder, disabled, readonly and required map to the native attributes. They also set data-disabled, data-readonly and data-required on the root. describedby becomes aria-describedby.
Everything else goes to the input element through the global attributes: autocomplete, autofocus, form, inputmode, list, max, maxlength, min, minlength, pattern, size, step, and also phx-*, aria-* and data-* attributes. That makes phx-change and phx-debounce work on the input itself.
Parts & data attributes
The root is data-part="root" and carries data-invalid, data-valid, data-disabled, data-readonly and data-required when they apply. data-valid is dropped when the input is invalid. The start-section and end-section parts are spans rendered only when their slots have content. The input part also carries data-invalid and aria-invalid. Each element also has a structural class such as chelekom-text-input__input.
Accessibility
The control is a native input, which follows the WAI-ARIA Textbox pattern and keeps normal keyboard and form behavior. Give every input a visible label, for example through the Headless Field. errors set aria-invalid, and describedby connects descriptions and error messages to the input.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | No | Unique id; taken from the form field when one is given |
type
|
string
|
text
|
textemailpasswordsearchtelurlnumberdatetimedatetime-localmonthweek
| No | Native input type |
field
|
Phoenix.HTML.FormField
| — | — | No | A form field struct — supplies id, name, value and errors |
name
|
string
| — | — | No | Input name (ignored when `field` is given) |
value
|
any
| — | — | No | Input value (ignored when `field` is given) |
errors
|
list
|
[]
| — | No | Error messages; a non-empty list marks the input invalid |
valid
|
boolean
|
false
| — | No | Mark the input valid (data-valid) |
disabled
|
boolean
|
false
| — | No | Disable the input (data-disabled) |
readonly
|
boolean
|
false
| — | No | Block editing but keep it focusable |
required
|
boolean
|
false
| — | No | Require a value for form submit |
placeholder
|
string
| — | — | No | Placeholder text |
describedby
|
string
| — | — | No | Id(s) of the description/error elements |
class
|
any
| — | — | No | Extra classes for the control root |
input_class
|
any
| — | — | No | — |
end_section_class
|
any
| — | — | No | — |
start_section_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | Any input/global attrs, e.g. phx-change |
Slots
| Name | Required | Description |
|---|---|---|
start_section
| No | Leading content inside the control (icon, prefix, …) |
end_section
| No | Trailing content inside the control (unit, clear button, …) |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
text_input
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless text_input