Bootstrap 4 (and higher) comes with a suite of handy utility classes for handling common yet fickle styling issues like spacing, borders, sizing, layout, colors, and more.

Add margin

If you look at the source for the theme demo app, the actionButton() logic makes use of the margin spacing (m-2) to add some spacing between the buttons (they also make use of button modifier classes to achieve their different background colors).

actionButton("primary", "Primary", icon("product-hunt"), class = "btn-primary m-2")
actionButton("secondary", "Secondary (default)", class = "m-2")
actionButton("success", "Success", icon("check"), class = "btn-success m-2")

It’s worth noting that actionButton(), like many other core shiny UI components, doesn’t actually have an explicit class argument, but it does route implicit arguments (i.e., ...) to its top-level HTML tag, which is why actionButton(..., class = "btn-primary") add the class in the appropriate place. Another place where this top-level class can be useful is for customizing the body of a tabPanel().

Add padding, borders, and alignment

By default, tabset panels don’t come with any padding or border(s) around the body’s content, which can make it appears though the content is just floating in space:

tabsetPanel(
  tabPanel("One", "No padding"),
  tabPanel("Two", "Very sad!")
)

We can help solve this issue by adding some padding the tab’s content (p-3) as well as a nice border that matches up with the tab’s borders.

tab <- function(...) {
  shiny::tabPanel(..., class = "p-3 border border-top-0 rounded-bottom")
}
tabsetPanel(
  tab("One", "With padding"),
  tab("Two", "Nice!")
)

And for tabsetPanel(type="pills") , it looks a bit better to have a full, rounded, border:

pill <- function(...) {
  shiny::tabPanel(..., class = "p-3 border rounded")
}
tabsetPanel(
  type = "pills",
  pill("One", "With padding"),
  pill("Two", "Nice!")
)

It’s also worth noting that you can also add these classes to rmarkdown tabsets, like so:

Unfortunately, we can’t always rely on the class argument placing itself on the HTML tag we need to achieve our styling goals. For example, we can’t simply do tabsetPanel(class = "justify-content-center", ...) to horizontally center a tabset. We can, however, leverage Sass’s @extend rule to essentially add utility classes to the relevant HTML element(s):

fluidPage(
  theme = bs_theme() %>% 
    bs_add_rules("#my-nav { @extend .justify-content-center }"),
  tabsetPanel(
    type = "pills", id = "my-nav",
    pill("One", "With padding"),
    pill("Two", "Nice!")
  )
)

Add background color

Add a bg-* class to any HTML element to set not only its background color to a theme color (e.g., primary, secondary, etc), but note that it’ll also make sure the foreground color properly contrasts the background color (i.e., the foreground switches from white/black intelligently based on dark/light background). Here we use bg-primary on a card component containing a {DT} table.

bs4_card <- function(body, title) {
  div(
    class = "card",
    div(class = "card-header bg-primary", title),
    div(class = "card-body d-flex justify-content-center", body)
  )
}

shinyApp(
  fluidPage(
    theme = bslib::bs_theme(primary = "orange"),
    uiOutput("dat")
  ),
  function(input, output) {
    output$dat <- renderUI({
      table <- DT::datatable(mtcars, fillContainer = TRUE, style = "bootstrap4", rownames = FALSE)
      bs4_card(table, "The mtcars dataset")
    })
  }
)

Create new utility classes

Bootstrap Sass also has a handful of Sass maps which allow for easy creation and/or modification of utility classes. One such case is for creating your own background color utility classes, say bg-flair via the $theme-colors map. This could be useful if you wanted to let the $primary be the default $blue, but use a bg-flair instead of bg-primary to color the card’s title:

bs_theme("theme-colors" = "('flair': orange)")