Skip to contents

Cards are a common organizing unit for modern user interfaces (UI). At their core, they’re just rectangular containers with borders and padding. However, when utilized properly to group related information, they help users better digest, engage, and navigate through content. This is why most successful dashboard/UI frameworks make cards a core feature of their component library. This article provides an overview of the API that bslib provides to create Bootstrap cards.

One major feature that bslib adds to Bootstrap cards is the ability to expand the card to a full screen view. Often this feature wants output that resizes itself to fit its card container. To do this as advertised, make sure you have the latest version of shiny and htmlwidgets:

install.packages("shiny")
install.packages("htmlwidgets")

Since this article is statically hosted (i.e., not powered by Shiny), it uses statically rendered htmlwidgets like plotly and leaflet (but don’t worry, card()s work in Shiny equally as well). Here’s some code to create those widgets:

library(bslib)
library(shiny)
library(htmltools)
library(plotly)
library(leaflet)

plotly_widget <- plot_ly(x = diamonds$cut) %>%
  config(displayModeBar = FALSE) %>%
  layout(margin = list(t = 0, b = 0, l = 0, r = 0))

leaflet_widget <- leaflet() %>%
  addTiles()

Hello card()

A card() is designed to handle any number of “known” card items (e.g., card_header(), card_body(), etc) as unnamed arguments (i.e., children). As we’ll see shotly, card() also has some useful named arguments (e.g., full_screen, height, etc).

At their core, card() and card items are just an HTML div() with a special Bootstrap class, so you can use Bootstrap’s utility classes to customize things like colors, text, borders, etc.

card(
  card_header(
    class = "bg-dark",
    "A header"
  ),
  card_body(
    markdown("Some text with a [link](https://github.com)")
  )
)
A header

Some text with a link

Implicit card_body()

If you find yourself using card_body() without changing any of its defaults, consider dropping it altogether since any direct children of card() that aren’t “known” card() items, are wrapped together into an implicit card_body() call.1 For example, the code to the right generates HTML that is identical to the previous example:

card(
  card_header(
    class = "bg-dark",
    "A header"
  ),
  markdown("Some text with a [link](https://github.com)")
)
A header

Some text with a link

Fixed sizing

By default, a card()’s size grows to accommodate the size of it’s contents. Thus, if some portion of the card_body() contains a large amount of text, table(s), etc., consider setting a fixed height. And in that case, if the contents exceed the specified height, they’ll be scrollable.

card(
  card_header(
    "A long, scrolling, description"
  ),
  card_body(
    height = 150, 
    lorem_ipsum_dolor_sit_amet
  )
)
A long, scrolling, description
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id nibh tortor id aliquet lectus proin nibh nisl. Adipiscing at in tellus integer feugiat. Arcu bibendum at varius vel pharetra vel turpis nunc eget. Cursus sit amet dictum sit amet justo. Sit amet consectetur adipiscing elit. Vestibulum mattis ullamcorper velit sed ullamcorper. Enim facilisis gravida neque convallis a. Elit duis tristique sollicitudin nibh sit amet. Magna eget est lorem ipsum. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim. Mauris in aliquam sem fringilla ut morbi. Id semper risus in hendrerit gravida rutrum quisque non tellus. At erat pellentesque adipiscing commodo elit at imperdiet dui. Fames ac turpis egestas maecenas pharetra convallis posuere morbi. Duis convallis convallis tellus id interdum velit laoreet id. Aliquet lectus proin nibh nisl. Nunc vel risus commodo viverra maecenas accumsan lacus vel facilisis. Bibendum enim facilisis gravida neque convallis a.

Alternatively, you can also set the height of the card to a fixed size and set fill = TRUE to have the card_body() container shrink/grow to fit the available space in a card(). Note that, by doing this, the children of the card_body() aren’t necessarily allowed to shrink/grow to fit the card_body(), which card_body_fill() (aka “responsize sizing”) is designed to do.

card(
  height = 200,
  card_header(
    "A long, scrolling, description"
  ),
  card_body(
    fill = TRUE,
    lorem_ipsum_dolor_sit_amet
  )
)
A long, scrolling, description
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id nibh tortor id aliquet lectus proin nibh nisl. Adipiscing at in tellus integer feugiat. Arcu bibendum at varius vel pharetra vel turpis nunc eget. Cursus sit amet dictum sit amet justo. Sit amet consectetur adipiscing elit. Vestibulum mattis ullamcorper velit sed ullamcorper. Enim facilisis gravida neque convallis a. Elit duis tristique sollicitudin nibh sit amet. Magna eget est lorem ipsum. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim. Mauris in aliquam sem fringilla ut morbi. Id semper risus in hendrerit gravida rutrum quisque non tellus. At erat pellentesque adipiscing commodo elit at imperdiet dui. Fames ac turpis egestas maecenas pharetra convallis posuere morbi. Duis convallis convallis tellus id interdum velit laoreet id. Aliquet lectus proin nibh nisl. Nunc vel risus commodo viverra maecenas accumsan lacus vel facilisis. Bibendum enim facilisis gravida neque convallis a.

Responsive sizing

Unlike card_body(), card_body_fill() encourages its children to grow and shrink vertically as needed in response to its card()’s height. Responsive sizing is particularly useful for card(full_screen = TRUE, ...), which adds an icon (displayed on hover) to expand the card() to a full screen view.

Since many htmlwidgets (like plotly::plot_ly()) and Shiny output bindings (like shiny::plotOutput()) default to a fixed height of 400 pixels, but are actually capable of responsive sizing, you’ll get a better result with card_body_fill() instead of card_body() in these cases (compare the “Responsive” with the “Fixed” result using the tabs to the right).

card(
  height = 250, full_screen = TRUE,
  card_header("Responsive sizing"),
  card_body_fill(plotly_widget),
  card_footer(
    class = "fs-6",
    "Copyright 2022 RStudio, PBC"
  )
)
Responsive sizing
card(
  height = 250, full_screen = TRUE,
  card_header("Fixed sizing"),
  plotly_widget,
  card_footer(
    class = "fs-6",
    "Copyright 2022 RStudio, PBC"
  )
)
Fixed sizing