Skip to contents

bslibdash ships a single brand theme, [brand_bs_theme()], that every page constructor applies by default. This vignette shows how to use it as-is, tweak individual Bootstrap variables, add your own SCSS, swap to a different Bootswatch preset, and tune the theme interactively with bslib::bs_themer().

For the building blocks underneath, see the bslib theming articles.

The default theme

brand_bs_theme() returns a bslib::bs_theme() built on Bootstrap 5 (bootswatch = "bootstrap") with the bslibdash brand colour and typography defaults. Component CSS is not baked into the theme - each bslibdash component attaches its own theme-aware SCSS via bslib::bs_dependency_defer(), so swapping theme does not drop bslibdash styles. Key variable defaults:

Variable Value
primary #0460A9
body-color #111827
border-radius .75rem
font-family System UI stack
enable-shadows TRUE (subtle, layered)

Overriding theme variables

Use bslib::bs_add_variables() to override individual Bootstrap Sass variables and pass the result to any page constructor:

library(shiny)
library(bslibdash)

theme <- brand_bs_theme() |>
  bslib::bs_add_variables(
    primary       = "#8B0000",
    `body-color`  = "#1F2937",
    `font-size-base` = "0.95rem"
  )

ui <- dashboardPage(
  header  = dashboardHeader(title = "Maroon dashboard"),
  sidebar = dashboardSidebar(),
  body    = dashboardBody(),
  theme   = theme
)

Component status colours (primary, success, …) automatically pick up the new variable values, so cards, buttons, badges and value boxes re-skin together.

Adding custom SCSS

Add bespoke rules with bslib::bs_add_rules():

theme <- brand_bs_theme() |>
  bslib::bs_add_rules("
    .app-header { background: linear-gradient(90deg, #0460A9, #003a6e); }
    .app-footer { font-size: 0.85rem; }
  ")

Switching the Bootswatch preset

theme <- bslib::bs_theme(version = 5, bootswatch = "flatly") |>
  bslib::bs_add_variables(primary = "#0460A9")

dashboardPage(
  header = dashboardHeader(title = "Flatly + brand blue"),
  sidebar = dashboardSidebar(),
  body = dashboardBody(h2("Flatly + brand blue")),
  theme = theme
)

Note: bslibdash component CSS is attached by the components themselves, so any bslib::bs_theme() - including a plain bslib::bs_theme(version = 5) - still renders the package’s custom classes (.bslibdash-value-box, .bslibdash-info-box, .app-sidebar, …). Use brand_bs_theme() when you want the bslibdash brand defaults; use your own bs_theme() when you do not.

Real-time theming

bslib::bs_themer() works against bslibdash pages out of the box - call it once from your server to interactively tune colours and typography during development:

server <- function(input, output, session) {
  bslib::bs_themer()
}

See the bslib real-time theming article for the underlying widget.

Dynamic theming

session$setCurrentTheme() swaps the active bs_theme() at runtime without a page reload, which makes it easy to let users flip between light/dark variants or any other named presets. Because each bslibdash component attaches its SCSS via bslib::bs_dependency_defer(), the components automatically recompile against the new theme:

library(shiny)
library(bslib)
library(bslibdash)

themes <- list(
  light = brand_bs_theme(),
  dark  = bs_theme(
    version = 5,
    bg      = "#0B0F19",
    fg      = "#E5E7EB",
    primary = "#60A5FA"
  )
)

ui <- dashboardPage(
  title  = "Theme switcher",
  header = dashboardHeader(
    rightUi = radioButtons(
      "mode", NULL,
      choices = c("light", "dark"),
      inline  = TRUE
    )
  ),
  sidebar = dashboardSidebar(
    sidebarMenu(
      id = "sidebar",
      menuItem("Overview", tabName = "overview", icon = icon("house"))
    )
  ),
  body = dashboardBody(
    fluidRow(
      valueBox(1200, "Total sales",     icon = icon("graph-up"),             color = "success"),
      valueBox(53,   "Open issues",     icon = icon("exclamation-triangle"), color = "warning"),
      valueBox(8,    "Critical alerts", icon = icon("bell"),                 color = "danger")
    ),
    fluidRow(
      infoBox(title = "CPU usage", value = "48%",
              subtitle = "Average over last 5 min",
              icon = icon("cpu"), color = "primary"),
      infoBox(title = "Queue length", value = 17,
              subtitle = "Waiting jobs",
              icon = icon("inboxes"), color = "warning")
    ),
    fluidRow(
      box(title = "Status", width = 6, status = "primary",
          "All systems nominal."),
      box(title = "Notes",  width = 6, status = "info",
          collapsible = TRUE,
          "Toggle between light and dark in the header to see the theme swap.")
    )
  ),
  theme = themes$light
)

server <- function(input, output, session) {
  observeEvent(input$mode, {
    session$setCurrentTheme(themes[[input$mode]])
  })
}

shinyApp(ui, server)

Use the same pattern to expose any other curated set of presets (for example, a Bootswatch picker) - just build the bs_theme() objects ahead of time and hand the chosen one to setCurrentTheme(). The dark variant above is built with bs_theme(bg = ..., fg = ..., primary = ...) rather than bs_add_variables() on the brand theme because the former makes bslib rederive a coherent palette (greys, borders, link colours, secondary/tertiary backgrounds) from bg/fg, while the latter only patches the named variables. For the underlying mechanics, see ?bslib::bs_dependency_defer.