Skip to contents

This (short) article on value_box() assumes you’ve loaded the following packages:

Hello value_box()

A value_box() has 4 main parts:

  1. value: Some text value.
  2. title: Optional text to display above value.
  3. showcase: Optional UI element(s) to display alongside the value.
  4. ...: Any other text/UI elements to appear below value.

As we’ll see later, one can be clever with what goes in the showcase, but in many cases an icon provides enough visual context for the box to feel “complete”. We recommend using the new bsicons package since it’s designed with Bootstrap in mind, but you could also use fontawesome or {icons}.

By default, the showcase is displayed to the left of the value, but it can also appear to the right by giving showcase_top_right() to showcase_layout. The color of the value box may also be customized via the theme_color argument.

value_box(
  title = "I got", 
  value = "99 problems",
  showcase = bs_icon("music-note-beamed"),
  p("bslib ain't one", bs_icon("emoji-smile")),
  p("hit me", bs_icon("suit-spade"))
)

I got

99 problems

bslib ain't one

hit me

value_box(
  title = "I got", 
  value = "99 problems",
  showcase = bs_icon("music-note-beamed"),
  showcase_layout = showcase_top_right(),
  theme_color = "secondary",
  p("bslib ain't one", bs_icon("emoji-smile")),
  p("hit me", bs_icon("suit-spade"))
)

I got

99 problems

bslib ain't one

hit me

Dynamic rendering (Shiny)

When using Shiny to dynamically render value_box() contents, it’s good practice to use textOutput() to serve as a placeholder for value, title, etc. This way, if the value takes a moment to compute, the value box will appear before the value is ready, and thus reduces “layout shift” when the value is actually rendered.

ui <- page_fixed(
  value_box(
    title = "The current time",
    value = textOutput("time"),
    showcase = bs_icon("clock")
  )
)

server <- function(input, output) {
  output$time <- renderText({
    invalidateLater(1000)
    format(Sys.time())
  })
}

shinyApp(ui, server)

Multiple value boxes

To layout multiple value boxes, it’s recommended to use layout_column_wrap() (or layout_columns()), which ensures a uniform height and width (at least by default) across the boxes.

vbs <- list(
  value_box(
    title = "1st value", 
    value = "123",
    showcase = bs_icon("bar-chart"),
    p("The 1st detail")
  ),
  value_box(
    title = "2nd value", 
    value = "456",
    showcase = bs_icon("graph-up"),
    p("The 2nd detail"),
    p("The 3rd detail")
  ),
  value_box(
    title = "3rd value", 
    value = "789",
    showcase = bs_icon("pie-chart"),
    p("The 4th detail"),
    p("The 5th detail"),
    p("The 6th detail")
  )
)

layout_column_wrap(
  width = "250px",
  !!!vbs
)

1st value

123

The 1st detail

2nd value

456

The 2nd detail

The 3rd detail

3rd value

789

The 4th detail

The 5th detail

The 6th detail


And, when incorporating multiple value boxes into a larger filling layout, it’s good practice to set fill = FALSE on the layout container since that’ll prevent the boxes from using up more space than they really need. For example, try resizing the following example vertically. Notice how the height of the value boxes don’t change, but the height of the plot does (and it isn’t allowed to shrink below 200 pixels):

page_fillable(
  layout_column_wrap(
    width = "250px",
    fill = FALSE,
    vbs[[1]], vbs[[2]]
  ),
  card(
    min_height = 200,
    plotly::plot_ly(x = rnorm(100))
  )
)

Expandable sparklines

Under-the-hood, value_box() is implemented using card(), mainly to inherit it’s full_screen capabilities. Expanding a value_box() to full screen isn’t so useful when the showcase is something simple like an icon, but it becomes quite compelling for something like an “expandable sparkline”. The code to the right demonstrates one way you might go about that with plotly.

Note that, since this example is statically rendered (outside of Shiny), we make use of htmlwidgets::onRender() to add some JavaScript that effectively says: “Show the xaxis of the chart when it’s taller than 200 pixels; otherwise, hide it”.

Those of you who aren’t wanting to write JavaScript can achieve similar behavior (i.e., displaying a different chart depending on it’s size) via shiny::getCurrentOutputInfo(), as mentioned in the article on cards. In fact, here’s the source code for a Shiny app does effectively the same thing without any JavaScript (note how it also leverages other getCurrentOutputInfo() values to avoid hard coding "white" into the colors of the sparklines).

library(plotly)

sparkline <- plot_ly(economics) %>%
  add_lines(
    x = ~date, y = ~psavert,
    color = I("white"), span = I(1),
    fill = 'tozeroy', alpha = 0.2
  ) %>%
  layout(
    xaxis = list(visible = F, showgrid = F, title = ""),
    yaxis = list(visible = F, showgrid = F, title = ""),
    hovermode = "x",
    margin = list(t = 0, r = 0, l = 0, b = 0),
    font = list(color = "white"),
    paper_bgcolor = "transparent",
    plot_bgcolor = "transparent"
  ) %>%
  config(displayModeBar = F) %>%
  htmlwidgets::onRender(
    "function(el) {
      var ro = new ResizeObserver(function() {
         var visible = el.offsetHeight > 200;
         Plotly.relayout(el, {'xaxis.visible': visible});
      });
      ro.observe(el);
    }"
  )


value_box(
  title = "Personal Savings Rate",
  value = "7.6%",
  p("Started at 12.6%"),
  p("Averaged 8.6% over that period"),
  p("Peaked 17.3% in May 1975"),
  showcase = sparkline,
  full_screen = TRUE,
  theme_color = "success"
)

Personal Savings Rate

7.6%

Started at 12.6%

Averaged 8.6% over that period

Peaked 17.3% in May 1975