Corex. PasswordInput
(Corex v0.2.0)
View Source
Password input for Phoenix LiveView forms. Behavior follows Zag.js Password Input.
Anatomy
Minimal
<.password_input class="password-input">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>Custom Error
<.password_input field={@form[:password]} class="password-input">
<:label>Password</:label>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>Form
When using with Phoenix forms, set the form id in to_form/2 (for example to_form(changeset, as: :name, id: "my-form")) and use <.form for={@form}>.
For cross-cutting invalid styling and error presentation, see the Forms guide. With field={@form[:…]}, pass auto_invalid for alert borders from visible errors, or invalid={true} to force the alert state.
Controller
Build the form from an Ecto changeset:
def form_page(conn, _params) do
form =
%MyApp.Form.PasswordForm{}
|> MyApp.Form.PasswordForm.changeset(%{})
|> Phoenix.Component.to_form(as: :password_form, id: "password-form")
render(conn, :form_page, form: form)
end<.form :let={f} for={@form} action={@action} method="post">
<.password_input field={f[:password]} class="password-input">
<:label>Password</:label>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>
<button type="submit">Submit</button>
</.form>Live View
Prefer building the form from an Ecto changeset (see "With Ecto changeset" below).
With Ecto changeset
First create your schema and changeset:
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :password, :string
timestamps(type: :utc_datetime)
end
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :password])
|> validate_required([:email, :password])
|> validate_length(:password, min: 8)
end
enddefmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view
alias MyApp.Accounts.User
def mount(_params, _session, socket) do
{:ok, assign(socket, :form, to_form(User.changeset(%User{}, %{})))}
end
def handle_event("validate", %{"user" => user_params}, socket) do
changeset = User.changeset(%User{}, user_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def render(assigns) do
~H"""
<.form for={@form} phx-change="validate">
<.password_input field={@form[:password]} class="password-input">
<:label>Password</:label>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>
</.form>
"""
end
endAPI
Requires a stable id on <.password_input>.
| Function | Action | Returns |
|---|---|---|
set_visible/2 | Set visibility (client) | %Phoenix.LiveView.JS{} |
set_visible/3 | Set visibility (server) | socket |
toggle_visible/1 | Toggle visibility (client) | %Phoenix.LiveView.JS{} |
toggle_visible/2 | Toggle visibility (server) | socket |
focus/1 | Focus input (client) | %Phoenix.LiveView.JS{} |
focus/2 | Focus input (server) | socket |
Events
Pick an event name and pass it to on_* on <.password_input>.
Server events
| Event | When | Payload |
|---|---|---|
on_visibility_change="password_visibility_changed" | Visibility toggles | %{"id" => id, "visible" => boolean} |
on_visibility_change
<.password_input
class="password-input"
on_visibility_change="password_visibility_changed"
>
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>def handle_event("password_visibility_changed", %{"id" => _id, "visible" => visible}, socket) do
{:noreply, assign(socket, :password_visible, visible)}
endClient events
| Event | When | event.detail |
|---|---|---|
on_visibility_change_client="password-visibility-changed" | Visibility toggles | id, visible |
Style
Use data attributes to target elements:
[data-scope="password-input"][data-part="root"] {}
[data-scope="password-input"][data-part="label"] {}
[data-scope="password-input"][data-part="control"] {}
[data-scope="password-input"][data-part="input"] {}
[data-scope="password-input"][data-part="visibility-trigger"] {}
[data-scope="password-input"][data-part="indicator"] {}@import "../corex/corex.css";Stack modifiers on the host (class on <.password_input>). Combine axes, for example password-input ui-accent ui-size-lg.
Axes: Semantic (ui-accent, ui-brand, ui-alert, ui-info, ui-success), Size (ui-size-sm … ui-size-xl), Radius (ui-rounded-*). No variant axis. See the modifier guide.
Semantic modifiers set palette variables for focus and accent ink on the field and visibility trigger. The input surface stays the shared ui-input treatment. Password input has no variant axis.
Semantic
Palette for focus and accent ink on the field and visibility trigger.
| Modifier | Classes |
|---|---|
| Default | password-input |
| Accent | password-input ui-accent |
| Brand | password-input ui-brand |
| Alert | password-input ui-alert |
| Info | password-input ui-info |
| Success | password-input ui-success |
Size
| Modifier | Classes |
|---|---|
| SM | password-input ui-size-sm |
| MD | password-input ui-size-md |
| LG | password-input ui-size-lg |
| XL | password-input ui-size-xl |
Summary
API
Focus the input from a control (phx-click).
Focus the input from handle_event.
Set whether the password is visible from a control (phx-click).
Set visibility from handle_event.
Toggle visibility from a control (phx-click).
Toggle visibility from handle_event.
Components
Attributes
id(:string) - Stable id for the component. Defaults tonil.field(Phoenix.HTML.FormField) - A form field struct retrieved from the form, for example: @form[:password]. Automatically sets id, name, form, and errors from the form field. Passinvalidexplicitly for alert styling. Defaults tonil.name(:string) - Name attribute for form submission. Defaults tonil.form(:string) - Form id to associate the control with. Defaults tonil.invalid(:boolean) - Whether the control has validation errors. Defaults tonil.auto_invalid(:boolean) - When true withfield, set invalid from visible changeset errors (default false). Defaults tofalse.disabled(:boolean) - Whether the control is disabled. Defaults tofalse.read_only(:boolean) - Whether the control is read-only. Defaults tofalse.required(:boolean) - Whether the control is required. Defaults tofalse.value(:string) - Defaults tonil.visible(:boolean) - Defaults tofalse.ignore_password_managers(:boolean) - Defaults totrue.dir(:string) - Defaults tonil.Must be one ofnil,"ltr", or"rtl".orientation(:string) - Defaults to"vertical". Must be one of"horizontal", or"vertical".auto_complete(:string) - Defaults to"current-password". Must be one of"current-password", or"new-password".on_visibility_change(:string) - Defaults tonil.on_visibility_change_client(:string) - Defaults tonil.errors(:list) - List of error messages to display. Defaults to[].translation(Corex.PasswordInput.Translation) - Override translatable strings. Defaults tonil.- Global attributes are accepted.
Slots
label- Accepts attributes:class(:string)
error- Accepts attributes:class(:string)
visible_indicator- Icon shown when password is visible. Accepts attributes:class(:string)
hidden_indicator- Icon shown when password is hidden. Accepts attributes:class(:string)
API
@spec focus(String.t()) :: Phoenix.LiveView.JS.t()
Focus the input from a control (phx-click).
<.action phx-click={Corex.PasswordInput.focus("my-password-input")}>Focus</.action>
<.password_input id="my-password-input" class="password-input" name="pw">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>document.getElementById("my-password-input")?.dispatchEvent(
new CustomEvent("corex:password-input:focus", { bubbles: false })
);
@spec focus(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Focus the input from handle_event.
<.action phx-click="focus_pw">Focus</.action>
<.password_input id="my-password-input" class="password-input" name="pw">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>def handle_event("focus_pw", _, socket) do
{:noreply, Corex.PasswordInput.focus(socket, "my-password-input")}
end
@spec set_visible(String.t(), boolean()) :: Phoenix.LiveView.JS.t()
Set whether the password is visible from a control (phx-click).
<.action phx-click={Corex.PasswordInput.set_visible("my-password-input", true)}>Reveal</.action>
<.password_input id="my-password-input" class="password-input" name="pw">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>document.getElementById("my-password-input")?.dispatchEvent(
new CustomEvent("corex:password-input:set-visible", {
bubbles: false,
detail: { visible: true },
})
);
@spec set_visible(Phoenix.LiveView.Socket.t(), String.t(), boolean()) :: Phoenix.LiveView.Socket.t()
Set visibility from handle_event.
<.action phx-click="reveal_pw">Reveal</.action>
<.password_input id="my-password-input" class="password-input" name="pw">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>def handle_event("reveal_pw", _, socket) do
{:noreply, Corex.PasswordInput.set_visible(socket, "my-password-input", true)}
end
@spec toggle_visible(String.t()) :: Phoenix.LiveView.JS.t()
Toggle visibility from a control (phx-click).
<.action phx-click={Corex.PasswordInput.toggle_visible("my-password-input")}>Toggle</.action>
<.password_input id="my-password-input" class="password-input" name="pw">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>document.getElementById("my-password-input")?.dispatchEvent(
new CustomEvent("corex:password-input:toggle-visible", { bubbles: false })
);
@spec toggle_visible(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Toggle visibility from handle_event.
<.action phx-click="toggle_pw">Toggle</.action>
<.password_input id="my-password-input" class="password-input" name="pw">
<:label>Password</:label>
<:visible_indicator><.heroicon name="hero-eye" /></:visible_indicator>
<:hidden_indicator><.heroicon name="hero-eye-slash" /></:hidden_indicator>
</.password_input>def handle_event("toggle_pw", _, socket) do
{:noreply, Corex.PasswordInput.toggle_visible(socket, "my-password-input")}
end