Skip to contents

Shows a grey placeholder in the shape of the content while the output loads for the first time. When the output calculates again, the old content stays on the screen and is dimmed. The user is possibly reading that content, and grey boxes in its place would remove information and add none.

Usage

withBones(
  ui,
  type = NULL,
  rows = 6L,
  cols = 4L,
  lines = 3L,
  n = 3L,
  height = NULL,
  animation = NULL,
  stale = NULL,
  delay = NULL,
  min_time = NULL,
  remember = NULL,
  skeleton = NULL
)

Arguments

ui

A Shiny output, such as plotOutput("chart").

type

The shape of the skeleton: "text", "table", "plot", "cards", "value", a chart shape ("bar", "line", "scatter", "area", "histogram", "pie", "heatmap", "map", "network", "timeline", "wordcloud"), or a table package shape ("dt", "reactable", "gt", "rhandsontable"). NULL (the default) gets the shape from ui.

rows, cols

The number of body rows and columns, when type is "table" or a table package shape.

lines

The number of lines, when type is "text".

n

The number of cards or values, when type is "cards" or "value".

height

A CSS height for the placeholder, or a number of pixels, as in Shiny. NULL uses the height of the output. If the output has no height, NULL uses a default for the type.

animation

"wave", "pulse", "cascade", "sweep" or "none". See bones_defaults() for what each one does. NULL uses the value from bones_defaults().

stale

TRUE keeps the old content on the screen, dimmed, while the output calculates again. FALSE shows the skeleton again. NULL uses the value from bones_defaults(), which is TRUE if you did not set it.

delay

The time in milliseconds before the skeleton, or the dimming, appears. A load that ends sooner shows nothing, so a fast output does not flicker. The space of the content is kept from the start. NULL uses the value from bones_defaults(), which is 300 if you did not set it.

min_time

The shortest time in milliseconds that the skeleton, or the dimming, stays on the screen once it has appeared. A load that ends a moment after delay thus does not flash a skeleton for one frame. NULL uses the value from bones_defaults(), which is 500 if you did not set it.

remember

TRUE stores the real height of the content in the browser, and keeps that height on the next visit in place of the estimate, so the page does not move when the content arrives. Only a height from an estimate is stored: a height from height, or from the output itself, is exact already. The height is kept per page and per output id, and it is used only when the output has almost the same width as when it was measured. For a plotly, echarts4r or highcharter output, remember also stores the kind of the chart (see "Shape"). NULL uses the value from bones_defaults(), which is TRUE if you did not set it.

skeleton

Your own placeholder, in place of a built-in shape: a tag or a tag list, made of bones_block() blocks and any layout around them. See "Your own placeholder". Give type or skeleton, not both.

Value

ui, in a placeholder container.

Shape

The shape comes from the output that you give: plotOutput() gets the shape of a chart, tableOutput() gets rows and columns, and textOutput() gets lines of text. Use type to set a different shape. You must do this for uiOutput(), because its content is not known before it arrives.

A plotOutput() gets the shape of a bar chart. If the chart is of another kind, give that kind: "line", "scatter", "area", "histogram", "pie" or "heatmap". The output cannot tell, because renderPlot() decides the kind later, on the server.

A table from a table package gets the shape of that package, found from its output: DT::DTOutput() gets "dt", reactable::reactableOutput() gets "reactable", gt::gt_output() gets "gt", and rhandsontable::rHandsontableOutput() gets "rhandsontable". Each shape copies the parts of its package, such as the search box of DT. Set rows to the number of rows on a page: DT and reactable show 10 by default. Before gt 1.0.0, gt_output() did not mark its output as a gt table, so with an older gt, give type = "gt".

A visualisation widget gets the shape of its kind, found from its output in the same way. A map (leaflet, tmap, mapview, mapdeck and others) gets "map". A network or a diagram (visNetwork, DiagrammeR, networkD3, collapsibleTree) gets "network". timevis gets "timeline", wordcloud2 gets "wordcloud", and dygraphs gets "line". ggmap and a ggplot2 map draw into a plotOutput(), so give them type = "map".

A plotly, echarts4r or highcharter output can draw any kind of chart. With no type, it finds its kind by itself, but only when its first value arrives in the browser: the spec of the chart names the kind of each series. The first skeleton thus has the bar shape. The next loads, with stale = FALSE, show the shape of the kind: "bar", "line", "area", "scatter", "histogram", "pie", "heatmap", "map", "network" or "wordcloud". With remember, the kind is also stored in the browser, so on the next visit the first skeleton has that shape too. The first series that names a kind decides. A chart of a kind with no shape, such as a box plot, keeps the bar shape. A type that you give always wins. Other widgets that can draw any chart, such as ggiraph, keep the bar shape: give their kind with type.

Your own placeholder

When no built-in shape fits, give your own with skeleton. Build it from bones_block(), a grey block that takes the colours and the animation of bones, and lay the blocks out with any tags. The tags at the top level stack with a small gap. The placeholder behaves as a built-in shape does: it waits for delay, it stays for min_time, it keeps the space of the content, and the old content stays when stale is TRUE.

Layout

The wrapper keeps the space of the output until the content arrives, so the page does not move. If the output sets its own height, as plotOutput() does, the wrapper uses that height. If not, the height comes from rows, lines or n, or you can set it with height. When the content arrives, the content sets the height.

Examples

if (requireNamespace("shiny", quietly = TRUE)) {
  library(shiny)

  withBones(plotOutput("chart"))
  withBones(tableOutput("results"), rows = 8, cols = 5)
  withBones(uiOutput("cards"), type = "cards", n = 4)
  withBones(plotOutput("trend"), type = "line")

  # Your own placeholder: an avatar and two lines of text.
  withBones(
    uiOutput("profile"),
    skeleton = div(
      style = "display: flex; gap: 1rem; align-items: center;",
      bones_block("3rem", "3rem", shape = "circle"),
      div(
        style = "flex: 1; display: grid; gap: 0.5rem;",
        bones_block("60%"), bones_block("40%")
      )
    )
  )
}
#> <div class="bones-wrap bones-anim-wave" data-bones-type="custom" data-bones-stale="true" data-bones-min-time="500" data-bones-remember="true" data-bones-id="profile" style="--bones-reserve: 72px; --bones-delay: 300ms;">
#>   <div class="bones-skeleton bones-skeleton-custom" aria-hidden="true">
#>     <div style="display: flex; gap: 1rem; align-items: center;">
#>       <div class="bones-bar bones-block bones-block-circle" style="width: 3rem; height: 3rem;"></div>
#>       <div style="flex: 1; display: grid; gap: 0.5rem;">
#>         <div class="bones-bar bones-block" style="width: 60%; height: 0.75rem;"></div>
#>         <div class="bones-bar bones-block" style="width: 40%; height: 0.75rem;"></div>
#>       </div>
#>     </div>
#>   </div>
#>   <div class="bones-content">
#>     <div id="profile" class="shiny-html-output"></div>
#>   </div>
#> </div>