Corex. SignaturePad
(Corex v0.1.1)
View Source
Phoenix implementation of Zag.js Signature Pad.
Anatomy
Basic Usage
<.signature_pad class="signature-pad">
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
</.signature_pad>With Callback
<.signature_pad
on_draw_end="signature_drawn"
class="signature-pad">
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
</.signature_pad>def handle_event("signature_drawn", %{"paths" => paths}, socket) do
{:noreply, put_flash(socket, :info, "Signature drawn with #{length(paths)} paths")}
endCustom Drawing Options
<.signature_pad
drawing_fill="blue"
drawing_size={3}
drawing_simulate_pressure
class="signature-pad">
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
</.signature_pad>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 phx-change and used_input?/1, set phx-change on <.form> so the whole form is sent (not on a single input only).
For cross-cutting invalid styling and error presentation, see the Forms guide. Pass invalid={Corex.FormField.invalid?(@form[:signature])} when you want alert borders after validation.
With field={@form[:signature]}, paths submit as name="…[]" hidden array inputs. On draw or clear, the hook updates those inputs and dispatches input so LiveView tracks the field. Errors render only when Phoenix.Component.used_input?/1 is true for the field.
Controller
Build the form from an Ecto changeset:
def form_page(conn, _params) do
form =
%MyApp.Form.SignatureForm{}
|> MyApp.Form.SignatureForm.changeset(%{})
|> Phoenix.Component.to_form(as: :signature_form, id: "signature-form")
render(conn, :form_page, form: form)
end<.form :let={f} for={@form} action={@action} method="post">
<.signature_pad field={f[:signature]} class="signature-pad">
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.signature_pad>
<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 :name, :string
field :signature, :text
timestamps(type: :utc_datetime)
end
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :signature])
|> validate_required([:name, :signature])
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">
<.signature_pad
field={@form[:signature]}
id="my-signature-pad"
class="signature-pad"
>
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
<:error :let={msg}>
<.heroicon name="hero-exclamation-circle" class="icon" />
{msg}
</:error>
</.signature_pad>
</.form>
"""
end
endAPI
Requires a stable id on <.signature_pad>.
| Function | Action | Returns |
|---|---|---|
clear/1 | Clear canvas (client) | %Phoenix.LiveView.JS{} |
clear/2 | Clear canvas (server) | socket |
Events
Pick an event name and pass it to on_* on <.signature_pad>.
Server events
| Event | When | Payload |
|---|---|---|
on_draw_end="signature_drawn" | Stroke ends | %{"id" => id, "paths" => paths} |
on_draw_end
<.signature_pad
class="signature-pad"
on_draw_end="signature_drawn"
>
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
</.signature_pad>def handle_event("signature_drawn", %{"id" => _id, "paths" => paths}, socket) do
{:noreply, assign(socket, :path_count, length(paths))}
endPatterns
LiveView sync
Pass paths from a LiveView assign and handle on_draw_end so the server owns stroke data.
<.signature_pad
class="signature-pad"
paths={@signature_paths}
on_draw_end="signature_drawn"
>
<:label>Sign here</:label>
<:clear_trigger>
<.heroicon name="hero-x-mark" />
</:clear_trigger>
</.signature_pad>def handle_event("signature_drawn", %{"paths" => paths}, socket) do
{:noreply, assign(socket, :signature_paths, paths)}
endStyle
Use data attributes to target elements:
[data-scope="signature-pad"][data-part="root"] {}
[data-scope="signature-pad"][data-part="label"] {}
[data-scope="signature-pad"][data-part="control"] {}
[data-scope="signature-pad"][data-part="segment"] {}
[data-scope="signature-pad"][data-part="guide"] {}
[data-scope="signature-pad"][data-part="clear-trigger"] {}
[data-scope="signature-pad"][data-part="hidden-input"] {}If you wish to use the default Corex styling, you can use the class signature-pad on the component.
This requires to install Mix.Tasks.Corex.Design first and import the component css file.
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/signature-pad.css";Drawing stroke color is set with the drawing_fill attribute (a CSS color value such as
var(--color-ink) or var(--color-accent)). It is not controlled by root modifier classes.
Trigger color, size, and corner radius use modifier classes on the root:
<.signature_pad
class="signature-pad signature-pad--accent signature-pad--lg signature-pad--rounded-xl"
drawing_fill="var(--color-ink)"
>| Modifier | Applies to |
|---|---|
signature-pad--{accent,brand,alert,success,info} | Clear trigger only |
signature-pad--{sm,md,lg,xl} | Label, control height, clear trigger |
signature-pad--rounded-{none,sm,md,lg,xl,full} | Control, pad surface, clear trigger |
The guide line (data-part="guide") is not themed by color modifiers.
Summary
Components
Renders a signature pad component.
Components
Renders a signature pad component.
Attributes
id(:string) - The id of the signature pad, useful for API to identify the signature pad.drawing_fill(:string) - CSS color for drawing strokes (e.g.var(--color-ink)orvar(--color-accent)). Defaults to"black".drawing_size(:integer) - The size/thickness of drawing strokes. Defaults to2.drawing_simulate_pressure(:boolean) - Whether to simulate pressure for drawing strokes. Defaults tofalse.drawing_smoothing(:float) - Smoothing factor for drawing strokes (0–1, perfect-freehand option). Defaults to0.9.drawing_easing(:string) - Easing function for drawing strokes (perfect-freehand option). Defaults tonil.drawing_thinning(:float) - Thinning factor for drawing strokes (perfect-freehand option). Defaults tonil.drawing_streamline(:float) - Streamline factor for drawing strokes (perfect-freehand option). Defaults to0.1.dir(:string) - The direction of the signature pad. When nil, derived from document (html lang + config :rtl_locales). Defaults tonil. Must be one ofnil,"ltr", or"rtl".on_draw_end(:string) - The server event name when drawing ends. Defaults tonil.on_draw_end_client(:string) - The client event name when drawing ends. Defaults tonil.paths(:any) - Initial stroke paths: a list of SVG d strings, or one string with lines separated by newline, sent asdata-default-pathsto the hook. Defaults tonil.name(:string) - The name of the signature pad input for form submission.errors(:list) - List of error messages to display. Defaults to[].field(Phoenix.HTML.FormField) - A form field from the form, e.g. @form[:signature]. Sets id, name, paths, and errors. Errors are filtered withused_input?/1(see the Phoenix Form Integration section).- Global attributes are accepted.
Slots
label- Accepts attributes:class(:string)
clear_trigger- Accepts attributes:class(:string)aria_label(:string) - Accessibility label for the clear button. Defaults to 'Clear signature' if not provided.
error- Accepts attributes:class(:string)
API
Clear all strokes from a control (phx-click).
<.action phx-click={Corex.SignaturePad.clear("my-signature-pad")}>Clear</.action>
<.signature_pad id="my-signature-pad" class="signature-pad">
<:label>Sign</:label>
<:clear_trigger><.heroicon name="hero-x-mark" /></:clear_trigger>
</.signature_pad>document.getElementById("my-signature-pad")?.dispatchEvent(
new CustomEvent("corex:signature-pad:clear", {
bubbles: false,
detail: { id: "my-signature-pad" },
})
);
Clear strokes from handle_event.
<.action phx-click="clear_sig">Clear</.action>
<.signature_pad id="my-signature-pad" class="signature-pad">
<:label>Sign</:label>
<:clear_trigger><.heroicon name="hero-x-mark" /></:clear_trigger>
</.signature_pad>def handle_event("clear_sig", _, socket) do
{:noreply, Corex.SignaturePad.clear(socket, "my-signature-pad")}
end