PhiaUi.Components.ImageUpload (phia_ui v0.1.17)

Copy Markdown View Source

Image upload component for PhiaUI.

Provides image_upload/1 — a styled wrapper over Phoenix LiveView's native file upload system (live_file_input). Renders a click-to-upload drop zone, live image previews via live_img_preview/1, per-entry progress bars, per-entry error messages, and entry removal buttons.

This component uses zero custom JavaScript — all upload behaviour is handled by Phoenix LiveView's built-in upload machinery. No JS hook is needed.

When to use

Use image_upload/1 for any image attachment field in a form:

  • Profile / avatar upload
  • Product image gallery
  • Article cover image
  • Document attachment (with accept: ~w(.pdf .docx .xlsx))
  • Receipt / invoice photo capture

Requirements

The parent LiveView must configure the upload with allow_upload/3 in mount/3:

def mount(_params, _session, socket) do
  {:ok,
   socket
   |> allow_upload(:avatar,
     accept: ~w(.jpg .jpeg .png .webp .gif),
     max_entries: 1,
     max_file_size: 5_242_880  # 5 MB
   )}
end

Then pass @uploads.avatar as the :upload attribute.

Basic example

<.form for={@form} phx-submit="save_profile" phx-change="validate">
  <.image_upload
    upload={@uploads.avatar}
    label="Drag & drop or click to upload a profile photo"
  />
  <.button type="submit">Save</.button>
</.form>

Multiple files example

def mount(_params, _session, socket) do
  {:ok,
   socket
   |> allow_upload(:product_images,
     accept: ~w(.jpg .jpeg .png .webp),
     max_entries: 6,
     max_file_size: 10_000_000
   )}
end

<.image_upload
  upload={@uploads.product_images}
  label="Upload up to 6 product images"
/>

Handling entry cancellation

Add this event handler to support the entry removal (×) button:

def handle_event("cancel_upload", %{"ref" => ref}, socket) do
  {:noreply, cancel_upload(socket, :avatar, ref)}
end

The upload name (:avatar) must match the atom used in allow_upload/3.

Consuming uploads after form submission

After a successful phx-submit, consume entries with consume_uploaded_entries/3:

def handle_event("save_profile", _params, socket) do
  urls =
    consume_uploaded_entries(socket, :avatar, fn %{path: tmp_path}, _entry ->
      dest = Path.join([:code.priv_dir(:my_app), "static", "uploads",
                        Path.basename(tmp_path) <> ".jpg"])
      File.cp!(tmp_path, dest)
      {:ok, "/uploads/" <> Path.basename(dest)}
    end)

  {:noreply, assign(socket, profile_image_url: List.first(urls))}
end

Error messages

The component maps Phoenix LiveView upload error atoms to English strings:

AtomMessage
:too_large"File too large"
:not_accepted"Unsupported file format"
:too_many_files"File limit reached"
other"Upload error"

Summary

Functions

Renders an image upload zone integrated with Phoenix LiveView's upload system.

Functions

image_upload(assigns)

Renders an image upload zone integrated with Phoenix LiveView's upload system.

Renders:

  1. A click-to-upload drop zone (a <label> wrapping live_file_input/1)
  2. A list of accepted entries — each with a thumbnail preview, filename, progress bar, and a remove button
  3. Per-entry error messages for validation failures

The drop zone is hidden from the visible file input (sr-only) so the entire styled <label> area acts as the upload trigger. This approach requires no custom JS — it is standard HTML form behaviour.

Attributes

  • upload (:any) (required) - A Phoenix.LiveView.UploadConfig struct returned by @uploads.field_name. Provides entry list, progress, errors, and the file input reference.

  • label (:string) - Instructional text displayed inside the drop zone. Defaults to "Click or drag & drop to upload".

  • class (:string) - Additional CSS classes for the outer wrapper div. Defaults to nil.