AuroraUI.Components.Progress (Aurora UI v0.1.0)

View Source

Progress family — spinner, progress, skeleton, and an async_state wrapper for LiveView async assigns and streams.

Together these cover the "we are working / waiting on data" surface:

  • spinner/1 — an indeterminate busy indicator for short, unmeasurable waits (a submit in flight). It always carries a visually-hidden label and lives in a role="status" region so assistive tech announces it. Under prefers-reduced-motion it slows down rather than stopping, so it never reads as frozen.
  • progress/1 — a labelled progress bar. Determinate mode is a real role="progressbar" with aria-valuenow/min/max (and an optional visible percentage); the indeterminate variant drops aria-valuenow and shows a sweeping fill.
  • skeleton/1 — a layout-reserving placeholder. You pass explicit width/height/shape so the box occupies the same space the real content will, avoiding cumulative layout shift. It is aria-hidden and its shimmer is disabled under reduced motion.
  • async_state/1 — a single wrapper that renders one of its loading/empty/error/inner_block slots based on a state atom. It is designed to sit directly over a LiveView assign_async/stream result.

async_state/1 with LiveView async + streams

Map the AsyncResult (and stream emptiness) to a single state atom, then let the wrapper pick the branch:

# mount / handle_params
socket
|> assign(:page, AsyncResult.loading())
|> start_async(:load, fn -> Catalog.list() end)

def handle_async(:load, {:ok, rows}, socket) do
  {:noreply,
   socket
   |> assign(:page, AsyncResult.ok(socket.assigns.page, :loaded))
   |> stream(:rows, rows)}
end

# template — one branch renders at a time; the :ok branch streams
<.async_state state={async_state(@page, @streams.rows)}>
  <:loading><.skeleton :for={_ <- 1..5} height="2.5rem" /></:loading>
  <:empty><.empty_state title="No results" /></:empty>
  <:error>Could not load. <.button phx-click="retry">Retry</.button></:error>
  <div id="rows" phx-update="stream">
    <div :for={{id, row} <- @streams.rows} id={id}>{row.name}</div>
  </div>
</.async_state>

# a tiny mapper in your LiveView keeps the template declarative
defp async_state(%AsyncResult{loading: l}, _) when not is_nil(l), do: :loading
defp async_state(%AsyncResult{failed: f}, _) when not is_nil(f), do: :error
defp async_state(%AsyncResult{ok?: true}, []), do: :empty
defp async_state(%AsyncResult{ok?: true}, _), do: :ok

Summary

Functions

Renders exactly one of its slots based on state — the declarative counterpart to a case over a LiveView async/stream result. See the moduledoc for the assign_async + streams wiring.

A progress bar. Determinate by default (pass value); becomes indeterminate when value is nil or indeterminate is set.

A layout-reserving placeholder. Always give it the size the real content will occupy so it prevents cumulative layout shift. It is decorative (aria-hidden); announce the overall busy state at a higher level (a role="status" region, spinner, or async_state).

An accessible indeterminate busy indicator.

Functions

async_state(assigns)

Renders exactly one of its slots based on state — the declarative counterpart to a case over a LiveView async/stream result. See the moduledoc for the assign_async + streams wiring.

Examples

<.async_state state={@state}>
  <:loading><.spinner label="Loading orders" /></:loading>
  <:empty>No orders yet.</:empty>
  <:error>Something went wrong.</:error>
  <.orders_table rows={@orders} />
</.async_state>

Attributes

  • state (:atom) - which branch to render. Defaults to :ok. Must be one of :loading, :empty, :error, or :ok.
  • Global attributes are accepted.

Slots

  • loading - shown while state is :loading (defaults to a spinner).
  • empty - shown while state is :empty.
  • error - shown while state is :error (rendered in a role=alert region).
  • inner_block (required) - the loaded content, shown while state is :ok.

progress(assigns)

A progress bar. Determinate by default (pass value); becomes indeterminate when value is nil or indeterminate is set.

Determinate bars are a real role="progressbar" exposing aria-valuenow/aria-valuemin/aria-valuemax; the indeterminate variant omits aria-valuenow so AT announces "busy" rather than a false number.

Examples

<.progress value={64} label="Uploading" show_value />
<.progress indeterminate label="Processing" />

Attributes

  • value (:integer) - current progress; when nil the bar is indeterminate. Defaults to nil.
  • max (:integer) - Defaults to 100.
  • min (:integer) - Defaults to 0.
  • label (:string) - concise visible label describing the task. Defaults to nil.
  • show_value (:boolean) - renders the percentage next to the label. Defaults to false.
  • indeterminate (:boolean) - force the indeterminate variant even if a value is present. Defaults to false.
  • size (:string) - Defaults to "md". Must be one of "sm", "md", or "lg".
  • Global attributes are accepted.

skeleton(assigns)

A layout-reserving placeholder. Always give it the size the real content will occupy so it prevents cumulative layout shift. It is decorative (aria-hidden); announce the overall busy state at a higher level (a role="status" region, spinner, or async_state).

Examples

<.skeleton width="12rem" height="1.25rem" shape="text" />
<.skeleton width="3rem" height="3rem" shape="circle" />

Attributes

  • width (:string) - explicit width (any CSS length) to reserve layout. Defaults to "100%".
  • height (:string) - explicit height to reserve layout. Defaults to "1rem".
  • shape (:string) - text uses a line height + rounded ends; circle forces a 1:1 avatar. Defaults to "rect". Must be one of "rect", "text", "circle", or "pill".
  • Global attributes are accepted.

spinner(assigns)

An accessible indeterminate busy indicator.

When not to use

If the wait is measurable, prefer progress/1 with a value so the user sees how far along they are.

Examples

<.spinner label="Saving changes" />
<.spinner size="lg" label="Loading dashboard" />

Attributes

  • label (:string) - visually-hidden accessible name announced while busy. Defaults to "Loading…".
  • size (:string) - Defaults to "md". Must be one of "sm", "md", or "lg".
  • Global attributes are accepted.