Loading Overlay
v0.0.10
mix mishka.ui.gen.headless loading_overlay
Overview of the Phoenix and LiveView Headless Loading Overlay Component
The Mishka Chelekom Headless Loading Overlay covers a container with a loader while something loads. It is an unstyled Phoenix function component that renders one element with role="status", aria-live="polite" and an aria-label. You put a spinner or text in the default slot. visible sets data-visible on the element, and your Tailwind classes decide how it fades. No JavaScript is involved.
While visible is false, the element gets aria-hidden="true" and an inline pointer-events: none, so a faded-out overlay never blocks clicks on the content under it. In a LiveView you flip visible from an assign. Your classes only own the looks, for example opacity-0 whenever data-visible is missing.
Base UI Loading Overlay example - loader over a card (Phoenix headless)
Base UI Loading Overlay example - loader over a card (Phoenix headless)
Content behind the overlay…
<div class="relative h-32 w-64 overflow-hidden rounded-lg border border-neutral-300 p-4 text-sm text-neutral-700 dark:border-neutral-700 dark:text-neutral-300">
<p>Content behind the overlay…</p>
<.loading_overlay
visible
label="Loading"
class="absolute inset-0 flex items-center justify-center bg-white/70 backdrop-blur-sm dark:bg-neutral-900/70 [&:not([data-visible])]:pointer-events-none [&:not([data-visible])]:opacity-0"
>
<span class="size-6 animate-spin rounded-full border-2 border-neutral-300 border-t-neutral-900 dark:border-neutral-600 dark:border-t-white"></span>
</.loading_overlay>
</div>Showing and hiding from the server
Bind visible to an assign and change the assign in your LiveView. The overlay stays in the DOM, so the fade works in both directions.
<div class="relative h-40 w-72 overflow-hidden rounded-lg border p-4">
<h4>Quarterly report</h4>
<p>The numbers keep loading behind this overlay.</p>
<.loading_overlay
visible={@loading}
label="Loading report"
class="absolute inset-0 flex items-center justify-center bg-white/70 backdrop-blur-sm transition-opacity duration-200 [&:not([data-visible])]:pointer-events-none [&:not([data-visible])]:opacity-0"
>
<span class="size-6 animate-spin rounded-full border-2 border-neutral-300 border-t-neutral-900"></span>
</.loading_overlay>
</div>
<button type="button" phx-click="toggle">Toggle loading</button>def handle_event("toggle", _params, socket) do
{:noreply, update(socket, :loading, &(!&1))}
endAttributes
visible is a boolean and defaults to false. label is written to aria-label and defaults to Loading. id is optional. class goes on the root, and any other attribute, such as data-* or aria-*, is passed to it too. The default slot holds the loader.
Parts & data attributes
The component has one element, marked data-part="root". It carries data-visible when the overlay is shown and aria-hidden="true" when it is not. The structural class is chelekom-loading-overlay, which you can target from your own CSS.
Accessibility
The overlay is a status live region named by label. Give it a label that says what is loading, such as Loading report. While it is hidden it is removed from the accessibility tree with aria-hidden and from pointer input with pointer-events: none. The overlay does not trap focus or make the content beneath it inert, so keyboard users can still tab into that content.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | No | Optional unique id |
visible
|
boolean
|
false
| — | No | Whether the overlay is shown (sets data-visible) |
label
|
string
|
Loading
| — | No | Accessible label (aria-label) |
class
|
any
| — | — | No | Extra classes |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
inner_block
| No | The loader content (spinner, text, …) |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
loading_overlay
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless loading_overlay