| Type: | Package |
| Title: | Content-Shaped Loading Placeholders for 'shiny' Outputs |
| Version: | 0.1.1 |
| Description: | Shows a grey placeholder in the shape of the content while a 'shiny' output loads, in place of a spinner. The user can thus see what is coming, and not only that something happens. The shape comes from the type of the output. The placeholder keeps the space of the content, so the page does not move when the content arrives. When an output calculates again, the old content stays on the screen, dimmed, and a placeholder does not replace it. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Language: | en-GB |
| Depends: | R (≥ 4.1.0) |
| Imports: | htmltools (≥ 0.5.4), utils |
| Suggests: | bslib, DT, echarts4r, gt, highcharter, plotly, reactable, rhandsontable, shiny (≥ 1.7.0), shinytest2, testthat (≥ 3.0.0), withr |
| URL: | https://github.com/tenmeh/bones, https://tenmeh.github.io/bones/ |
| BugReports: | https://github.com/tenmeh/bones/issues |
| Config/testthat/edition: | 3 |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-10-06 17:11:59 UTC; tchan |
| Author: | Tanmay Chanda [aut, cre, cph] |
| Maintainer: | Tanmay Chanda <tanmaychanda96@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-10-16 12:30:02 UTC |
bones: Content-Shaped Loading Placeholders for 'shiny' Outputs
Description
Shows a grey placeholder in the shape of the content while a 'shiny' output loads, in place of a spinner. The user can thus see what is coming, and not only that something happens. The shape comes from the type of the output. The placeholder keeps the space of the content, so the page does not move when the content arrives. When an output calculates again, the old content stays on the screen, dimmed, and a placeholder does not replace it.
Author(s)
Maintainer: Tanmay Chanda tanmaychanda96@gmail.com [copyright holder]
Authors:
Tanmay Chanda tanmaychanda96@gmail.com [copyright holder]
See Also
Useful links:
Report bugs at https://github.com/tenmeh/bones/issues
Wrap every Shiny output in a UI in a skeleton placeholder
Description
Gives skeletons to an app with no change to each output. bones_auto()
looks through a UI and puts each Shiny output in withBones(). Each one
gets the shape of its output, as with withBones(output): a
plotOutput() gets a chart, a tableOutput() gets rows, a DT table
gets the shape of DT, and so on.
Usage
bones_auto(ui, ..., exclude = NULL)
Arguments
ui |
A UI: a page, a tag, or a tag list. |
... |
Arguments for |
exclude |
The ids of outputs to leave alone. |
Details
Give it the whole page, or only a part of it:
ui <- bones_auto(page_sidebar(...)) ui <- page_sidebar(sidebar = ..., bones_auto(navset_card_tab(...)))
Value
ui, with each output in a placeholder container. A page keeps
its class and its theme.
What it leaves alone
An output that is already in
withBones(). To give one output its own shape or options, wrap it yourself:bones_auto()keeps that wrapper.An output whose id is in
exclude.An inline output, such as
textOutput("n", inline = TRUE). The wrapper is a block, so it would break the line of text.
A uiOutput() can hold anything, so it gets the text shape. Wrap it
yourself to give it another shape, such as type = "cards".
bones_auto() sees only the UI that you give it. Outputs that a
renderUI() makes later are not in it. Call bones_auto() on the UI
inside that renderUI() to give them skeletons too.
Examples
if (requireNamespace("shiny", quietly = TRUE)) {
library(shiny)
ui <- bones_auto(fluidPage(
plotOutput("chart"),
tableOutput("results"),
textOutput("count", inline = TRUE), # inline: left alone
withBones(uiOutput("cards"), type = "cards") # wrapped already: kept
))
}
A grey block to build your own placeholder
Description
The building block of a placeholder that you make yourself. Give the
blocks to withBones() as skeleton, laid out with any tags. Each block
takes the colours, the corner radius and the animation of bones, so your
placeholder moves and looks like the built-in shapes.
Usage
bones_block(width = "100%", height = "0.75rem", shape = c("rect", "circle"))
Arguments
width |
A CSS width, or a number of pixels. |
height |
A CSS height, or a number of pixels. The default is the height of one line of text in the built-in shapes. |
shape |
|
Details
The tags at the top level of skeleton stack with a small gap. Inside a
tag of your own, you set the layout: for example display: flex for a
row, or display: grid; gap: 0.5rem for a column.
Value
An htmltools::tag.
See Also
withBones() and its section "Your own placeholder".
Examples
# A line of text, 60% wide.
bones_block("60%")
# An avatar.
bones_block(48, 48, shape = "circle")
if (requireNamespace("shiny", quietly = TRUE)) {
library(shiny)
# A list of three people: an avatar, a name and a line of text each.
person <- div(
style = "display: flex; gap: 0.75rem; align-items: center;",
bones_block(40, 40, shape = "circle"),
div(
style = "flex: 1; display: grid; gap: 0.4rem;",
bones_block("40%"), bones_block("80%", "0.6rem")
)
)
withBones(uiOutput("people"), skeleton = tagList(person, person, person))
}
Set the placeholder defaults for the session
Description
Sets the defaults that withBones() uses for the rest of the session.
An argument that is NULL does not change its default. An argument that
you give to withBones() is stronger than a default from here.
Usage
bones_defaults(
animation = NULL,
color = NULL,
highlight = NULL,
radius = NULL,
speed = NULL,
stale = NULL,
delay = NULL,
min_time = NULL,
remember = NULL
)
Arguments
animation |
How the placeholder moves:
Users who ask their system to reduce motion always get |
color |
The colour of the placeholder, as a CSS colour. By default it comes from the theme of the page: the text colour of the bslib theme, made faint. A colour given here is stronger than the theme. |
highlight |
The colour of the band of light in the |
radius |
The CSS corner radius of the placeholder bars. By default it follows the rounding of the theme. |
speed |
The time of one animation cycle, in seconds. |
stale |
The default for the |
delay, min_time |
The defaults for the |
remember |
The default for the |
Value
The old values, invisibly, in the form that options() uses.
Give them to options() to restore them. Do not give them to
bones_defaults(): NULL there means "do not change", so an option
that had no value before would keep the new value.
Examples
old <- bones_defaults(animation = "pulse", radius = "0.5rem")
bones_skeleton("text")
options(old)
The bones HTML dependency
Description
withBones() and bones_skeleton() attach this for you. It is exported
for HTML that you make yourself with the bones-* classes, which needs
the stylesheet and the script.
Usage
bones_dependency()
Value
An htmltools::htmlDependency().
Examples
bones_dependency()
Make a skeleton placeholder
Description
Makes the grey placeholder on its own, with no output in it. You do not
need this for a Shiny output, because withBones() makes the
placeholder for you. Use this function where there is no Shiny output:
for example, inside a renderUI() while you get data, or to look at a
shape while you design a page.
Usage
bones_skeleton(
type = "text",
rows = 6L,
cols = 4L,
lines = 3L,
n = 3L,
height = NULL
)
Arguments
type |
The shape: |
rows |
The number of body rows, for |
cols |
The number of columns, for |
lines |
The number of lines, for |
n |
The number of cards or values, for |
height |
A CSS height for the placeholder, or a number of pixels.
|
Details
The skeleton uses the animation and the colours from bones_defaults().
Value
An htmltools::tag, with the stylesheet attached.
Chart shapes
A chart has a shape for each common kind: "bar", "line",
"scatter", "area", "histogram", "pie", "heatmap", "map",
"network", "timeline" and "wordcloud". "plot" is the same as
"bar". withBones() finds the shape of a map, network, timeline or
word cloud widget from its output. plotOutput() cannot say which kind it will
hold, because renderPlot() decides that later, on the server. So you
name the kind yourself.
Table shapes
A table from a table package has a shape that copies that package:
"dt", "reactable", "gt" and "rhandsontable". withBones() finds
it from the output by itself.
Examples
bones_skeleton("text", lines = 4)
bones_skeleton("table", rows = 8, cols = 5)
bones_skeleton("plot", height = "300px")
bones_skeleton("scatter", height = "300px")
bones_skeleton("dt", rows = 10, cols = 5)
Wrap a Shiny output in a skeleton placeholder
Description
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 |
type |
The shape of the skeleton: |
rows, cols |
The number of body rows and columns, when |
lines |
The number of lines, when |
n |
The number of cards or values, when |
height |
A CSS height for the placeholder, or a number of pixels, as
in Shiny. |
animation |
|
stale |
|
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. |
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 |
remember |
|
skeleton |
Your own placeholder, in place of a built-in shape: a tag
or a tag list, made of |
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%")
)
)
)
}