DISCLAIMER: This document demonstrates what’s possible today with bootstraplib theming and is still a work in progress. It should become more complete and keep improving in the coming months.

The following sections contain “recipes” for custom theming of shiny apps and rmarkdown documents via bootstraplib. Most the example code here is specifically for shiny, so you’ll see repeated use of bs_theme_new() to start a new theme; but note that calling bs_theme_new() inside rmarkdown documents is not necessary (the bootstrap_version and theme arguments to rmarkdown::html_document should instead be used to choose the version and bootswatch theme).

For the sake of novelty, some of the more specific recipes focus on theming shiny apps with Bootstrap 4 Sass, but many of the same ideas can also translate to Bootstrap 3 Sass. For those looking to learn more about the foundational bootstraplib and sass tools that make these recipes possible, see the foundations article.


The quickest and simplest way to provide a custom color palette to a bootstraplib theme is to use bs_theme_base_colors() and bs_theme_accent_colors(). These functions are designed to work with any version of Bootstrap and control numerous color styling defaults in shiny apps and rmarkdown documents that use bootstraplib::bootstrap(). More specifically, these functions set numerous Bootstrap Sass variable defaults via bs_theme_add_variables() (you can always use this function to set specific Sass variable defaults, if you wish).

# Color palette derives from https://tombrow.com/dark-mode-website-css
bs_theme_base_colors(bg = "#444", fg = "#e4e4e4")
bs_theme_accent_colors(primary = "#e39777", secondary = "#fdce93")


Use the bs_theme_fonts() function to easily set the default (i.e., base) font family, as well as families for code, headings, and input:

bs_theme_fonts(base = "Roboto Condensed", code = "Monaco")

As for font size, there is one main Sass variable, named $font-size-base (in both Bootstrap 3 and 4) that can be used to increase or decrease the base font size. For more granular control over font-sizes, consider searching through the Sass variables for font-size (for example) and setting variable(s) of interest.

bs_theme_add_variables("font-size-base" = "1.5rem")

For non-standard font families, you may want to consider downloading the relevant font files and serving those font files with your application. The gfonts package offers a nice way to download_font()s from Google Fonts. For example, if our working directory was a shiny app directory, and we want the Monserrat font, we could run:

font_dir <- "www/fonts"
if (!dir.exists(font_dir)) dir.create(font_dir)
download_font("montserrat", output_dir = font_dir)

Then, use bs_theme_fonts() to let Bootstrap know to use the Monserrat font as well as bs_theme_add()+generate_css() to let the browser know where the find the font files:

bs_theme_fonts(base = "Monserrat")
bs_theme_add(rules = generate_css("montserrat", font_dir = font_dir))


In a future version of shiny, renderPlot() (and renderCachedPlot()) will be able to use the foreground and background color of its HTML container to automatically apply intelligent theming defaults to ggplot2, lattice, and base graphics. That means, by just setting shinyOptions(plot.autotheme = TRUE) and (optionally) using bs_theme_base_colors(), your plots with change their default styling to look consistent with the rest of the application. Notice how, in addition to the foreground and background color, auto-theming will also use the link color (i.e. primary) as an accent color (see ?shiny::autoThemeOptions for more info).

# remotes::install_github("rstudio/shiny#2740")
shinyOptions(plot.autotheme = TRUE)


A future version of DT will gain the ability to automatically theme itself based on currently set bootstraplib theme, which works with DT::dataTableOutput() inside shiny as well as with DT::datatable() in rmarkdown.

# remotes::install_github("rstudio/DT#740")

For more granular control, you can set the Bootstrap table Sass variables more directly via bs_theme_add_variables() and DT should style itself sensibly based on these values. Alternatively, you can also override dataTables Sass variable defaults directly using DT::datatableThemeVariables(). The upside of the former (i.e., bs_theme_add_variables()) approach is that you can theme both DT::dataTableOutput() as well as the more basic shiny::dataTableOutput() all at once. The upside of the latter approach (i.e., DT::datatableThemeVariables()) is that you can theme DT outside of shiny and rmarkdown and without depending on Bootstrap.

Other shiny outputs

Core shiny outputs like renderPlot(), renderTable(), renderPrint(), etc. have support for auto-theming based on the current bootstraplib theme. However, at least initially, other third-party outputs won’t auto-theme themselves, but developers will have the opportunity to implement such behavior by adding a shiny-report-theme class to their output’s HTML container (htmlwidgets can do this by setting reportTheme = TRUE in shinyWidgetOutput()) and using shiny::getCurrentOutputInfo() server-side to obtain relevant computed styles (as shown below).

      class = "shiny-report-theme"
  function(input, output) {
    output$info <- renderPrint(str(getCurrentOutputInfo()))


Core shiny UI functions like wellPanel() and sidebarPanel() are based on the BS3 well class. When version = "4+3", this class extends the BS4 card class, making it feasible to style wells using some of the card theming variables. We also introduce our own $well-bg variable to set the background color to get the right default styling. Here we’ll set that Sass variable to a mix of 80% of the base background color and 20% of the foreground color:

  "well-bg" = "mix(#444444, #e4e4e4, 80%)",
  "card-border-color" = "darken($well-bg, 5%)",
  "card-border-radius" = 0,
  "card-border-width" = "0.5rem"


Core shiny UI functions like downloadButton() and actionButton() emit HTML markup with the BS3 btn-default class, which was dropped in BS4. When version = "4+3", the btn-default class extends the BS4 btn-secondary class, which is why the default button picked up on bs_theme_accent_colors(secondary = "#fdce93") (i.e., the yellow-ish color):

The other buttons contain different background colors because they contain btn-* modifier classes via a class argument to actionButton() (e.g., actionButton(..., class = "btn-primary")). 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 it’s top-level HTML container, which is why actionButton(..., class = "btn-primary") add the class in the appropriate place. More generally, in other cases where ... doesn’t route to the HTML tag target of interest (i.e., for utility classes), the next best option is to add a class via tagAppendAttributes(ui, class = 'some-class').

Other shiny inputs

In a future version of shiny, input widgets (e.g., sliderInput(), selectizeInput(), dateInput(), etc) will inherit styling from Bootstrap Sass variable values, but we will likely also provide Sass variables for more direct control over their styling.