# `Skullet`
[🔗](https://github.com/gilbertwong96/skullet/blob/main/lib/skullet.ex#L1)

Draws a skeleton of what is about to load.

While data is missing, your own component is rendered from a *sample* with its text
transparent, the browser lays it out for real, and rounded bars are drawn over it — one
per wrapped line, one block per image or control. Nothing is guessed, so nothing moves
when the content arrives.

The most common shape, one `assign_async/3` and a sample:

    <Skullet.skeleton :let={user} id="profile" async={@user} sample={@sample_user}>
      <Profile.card user={user} />
    </Skullet.skeleton>

Wiring, in the app's `assets/js/app.js` and `assets/css/app.css`:

    import { SkulletHook } from "../../deps/skullet/priv/static/skullet.js"
    new LiveSocket("/live", Socket, { hooks: { Skullet: SkulletHook } })

    @import "../../deps/skullet/priv/static/skullet.css";

The CSS is not optional: it keeps the sample invisible before the engine runs.

Under a Content Security Policy the boot script and the theme's `<style>` block need the
page's nonce: `nonce={@csp_nonce}` on `skeleton/1`, `provider/1` and `box/1`; see the
[CSP section](usage.html#content-security-policy).

The detail lives in the guides:

  * [Usage](usage.html) — async assigns and plain data, attributes and slots, empty and
    error states, memory, images, plain pages without LiveView.
  * [Theming](theming.html) — every custom property, the `theme` map, the provider.
  * [Reveals](reveals.html) — how `smart` decides, the built-in modes, specs of your own.
  * [Internals](internals.html) — how measuring works, first paint, the engine's build.

`Skullet.Boot.boot/1` covers the gap before the bundle loads, and `Skullet.Memory`
backs `remember`.

# `box`

```elixir
@spec box(map()) :: Phoenix.LiveView.Rendered.t()
```

A placeholder block for what cannot be measured — a logo, a chart, a canvas.

Put it inside the skeleton's sample in place of the thing, sized like the thing:

    <Skullet.skeleton :let={report} id="report" async={@report} sample={@sample}>
      <Skullet.box width={240} height={120} />
      <p>{report.title}</p>
    </Skullet.skeleton>

## Attributes

* `width` (`:any`) - any CSS length, or a number of pixels. Defaults to `"60%"`.
* `height` (`:any`) - any CSS length, or a number of pixels. Defaults to `"1em"`.
* `radius` (`:any`) - defaults to `--skullet-radius`. Defaults to `nil`.
* `nonce` (`:string`) - the CSP nonce for the `<style>` block this emits. Defaults to `nil`.

# `provider`

Sets the skeleton's look for everything inside — one place instead of every skeleton.

    <Skullet.provider mode="dark" animation="group-shimmer" speed="fast">
      …
    </Skullet.provider>

`mode` sets `color-scheme` on the wrapper, which is what the bars' colours follow, so
`"dark"` gives dark-page bars even where the page itself is light. `theme`, `animation`
and `speed` become the custom properties the skeletons inside inherit.

## Attributes

* `id` (`:string`) - DOM id for the wrapper. Defaults to `nil`.
* `nonce` (`:string`) - the CSP nonce for the `<style>` block this emits. Defaults to `nil`.
* `mode` (`:string`) - Defaults to `nil`.Must be one of `nil`, `"light"`, `"dark"`, or `"system"`.
* `animation` (`:string`) - Defaults to `nil`.Must be one of `nil`, `"shimmer"`, `"group-shimmer"`, `"pulse"`, or `"none"`.
* `speed` (`:string`) - Defaults to `nil`.Must be one of `nil`, `"slow"`, or `"fast"`.
* `theme` (`:map`) - custom properties for everything inside. Defaults to `%{}`.
* Global attributes are accepted.
## Slots

* `inner_block` (required)

# `skeleton`

```elixir
@spec skeleton(map()) :: Phoenix.LiveView.Rendered.t()
```

## Attributes

* `id` (`:string`) (required) - DOM id for the hook; also the future shape key.
* `async` (`:any`) - a `%Phoenix.LiveView.AsyncResult{}`. Defaults to `nil`.
* `data` (`:any`) - plain data; `nil` means loading, anything else is loaded. Defaults to `nil`.
* `sample` (`:any`) (required) - stands in for the data to draw the skeleton.
* `loading` (`:boolean`) - forces the skeleton, even over data. Defaults to `false`.
* `empty` (`:boolean`) - shows the `empty_message` slot in place of the content. Defaults to `false`.
* `reveal` (`:string`) - `smart`, `together`, `sequence`, `sweep`, `instant`, or a name registered with `registerReveal/2` in JS. Defaults to `"smart"`.
* `theme` (`:map`) - custom properties to override on this skeleton. Defaults to `%{}`.
* `nonce` (`:string`) - the CSP nonce for the `<style>` block this emits when `theme` is set. Defaults to `nil`.
* `label` (`:string`) - read to screen readers while loading. Defaults to `"Loading"`.
* `remember` (`:any`) - opts in to remembering the data this skeleton last showed, under this scope. Defaults to `nil`.
## Slots

* `error` - rendered in place of the content when the async assign failed. It is your markup —
  the library styles none of it — so an alert, an empty state, or your own card frame
  all work here.

* `skeleton` - your own markup to measure instead of the sample — for a logo, a chart, a canvas.
  It is rendered while loading in place of the inner block.

* `empty_message` - rendered in place of the content while `empty` is true — the content itself when left
  out. It is your markup: the library styles none of it, so a dashed box, an illustration
  or a card frame all work here.

* `inner_block` (required) - receives the sample, then the real value.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
