shiny-bslib
Modern Shiny Apps with bslib
Build professional Shiny dashboards using bslib's Bootstrap 5 components and layouts. This skill focuses on modern UI/UX patterns that replace legacy Shiny approaches.
Quick Start
Single-page dashboard:
library(shiny)
library(bslib)
ui <- page_sidebar(
title = "My Dashboard",
theme = bs_theme(version = 5), # "shiny" preset by default
sidebar = sidebar(
selectInput("variable", "Variable", choices = names(mtcars))
),
layout_column_wrap(
width = 1/3,
fill = FALSE,
value_box(title = "Users", value = "1,234", theme = "primary"),
value_box(title = "Revenue", value = "$56K", theme = "success"),
value_box(title = "Growth", value = "+18%", theme = "info")
),
card(
full_screen = TRUE,
card_header("Plot"),
plotOutput("plot")
)
)
server <- function(input, output, session) {
output$plot <- renderPlot({
hist(mtcars[[input$variable]], main = input$variable)
})
}
shinyApp(ui, server)
Multi-page dashboard:
ui <- page_navbar(
title = "Analytics Platform",
theme = bs_theme(version = 5),
nav_panel("Overview", overview_ui),
nav_panel("Analysis", analysis_ui),
nav_panel("Reports", reports_ui)
)
Core Concepts
Page Layouts
page_sidebar()-- Single-page dashboard with sidebar (most common)page_navbar()-- Multi-page app with top navigation barpage_fillable()-- Viewport-filling layout for custom arrangementspage_fluid()-- Scrolling layout for long-form content
See page-layouts.md for detailed guidance.
Grid Systems
layout_column_wrap()-- Uniform grid with auto-wrapping (recommended for most cases)layout_columns()-- 12-column Bootstrap grid with precise control
See grid-layouts.md for detailed guidance.
Cards
Primary container for dashboard content. Support headers, footers, multiple body sections, and full-screen expansion.
See cards.md for detailed guidance.
Value Boxes
Display key metrics and KPIs with optional icons, sparklines, and built-in theming.
See value-boxes.md for detailed guidance.
Navigation
- Page-level:
page_navbar()for multi-page apps - Component-level:
navset_card_underline(),navset_tab(),navset_pill()for tabbed content
See navigation.md for detailed guidance.
Sidebars
- Page-level:
page_sidebar()orpage_navbar(sidebar = ...) - Component-level:
layout_sidebar()within cards - Supports conditional content, dynamic open/close, accordions
See sidebars.md for detailed guidance.
Filling Layouts
The fill system controls how components resize to fill available space. Key concepts: fillable containers, fill items, fill carriers. Fill activates when containers have defined heights.
See filling.md for detailed guidance.
Theming
bs_theme()with Bootswatch themes for quick styling- Custom colors:
bg,fg,primaryaffect hundreds of CSS rules - Fonts:
font_google()for typography - Dynamic theming:
input_dark_mode()+session$setCurrentTheme()
See theming.md for detailed guidance.
UI Components
- Accordions -- Collapsible sections, especially useful in sidebars
- Tooltips -- Hover-triggered help text
- Popovers -- Click-triggered containers for secondary UI/inputs
- Toasts -- Temporary notification messages
See accordions.md, tooltips-popovers.md, and toasts.md.
Icons
Recommended: bsicons package (Bootstrap Icons, designed for bslib):
bsicons::bs_icon("graph-up")
bsicons::bs_icon("people", size = "2em")
Browse icons: https://icons.getbootstrap.com/
Alternative: fontawesome package:
fontawesome::fa("envelope")
Accessibility for icon-only triggers: When an icon is used as the sole trigger for a tooltip, popover, or similar interactive element (no accompanying text), it must be accessible to screen readers. By default, icon packages mark icons as decorative (aria-hidden="true"), which hides them from assistive technology.
bsicons::bs_icon(): Providetitle— this automatically setsa11y = "sem"tooltip( bs_icon("info-circle", title = "More information"), "Tooltip content here" )fontawesome::fa(): Seta11y = "sem"and providetitletooltip( fa("circle-info", a11y = "sem", title = "More information"), "Tooltip content here" )
The title should describe the purpose of the trigger (e.g., "More information", "Settings"), not the icon itself (e.g., not "info circle icon").
Special Inputs
input_switch()-- Toggle switch (modern checkbox alternative)input_dark_mode()-- Dark mode toggleinput_task_button()-- Button for long-running operationsinput_code_editor()-- Code editor with syntax highlightinginput_submit_textarea()-- Textarea with explicit submission
See inputs.md for detailed guidance.
Common Workflows
Building a Dashboard
- Choose page layout:
page_sidebar()(single-page) orpage_navbar()(multi-page) - Add theme with
bs_theme()(consider Bootswatch for quick start) - Create sidebar with inputs for filtering/controls
- Add value boxes at top for key metrics (set
fill = FALSEon container) - Arrange cards with
layout_column_wrap()orlayout_columns() - Enable
full_screen = TRUEon all visualization cards - Add
thematic::thematic_shiny()for plot theming
Modernizing an Existing App
See migration.md for a complete mapping of legacy patterns to modern equivalents. Key steps:
- Replace
fluidPage()withpage_sidebar()orpage_navbar() - Replace
fluidRow()/column()withlayout_columns() - Wrap outputs in
card(full_screen = TRUE) - Add
theme = bs_theme(version = 5) - Convert key metrics to
value_box()components - Replace
tabsetPanel()withnavset_card_underline()
Guidelines
- Prefer bslib page functions (
page_sidebar(),page_navbar(),page_fillable(),page_fluid()) over legacy equivalents (fluidPage(),navbarPage()) - Use
layout_column_wrap()orlayout_columns()for grid layouts instead offluidRow()/column(), which don't support filling layouts - Wrap outputs in
card(full_screen = TRUE)when building dashboards -- full-screen expansion is a high-value feature - Set
fill = FALSEonlayout_column_wrap()containers holding value boxes (they shouldn't stretch to fill height) - Pin Bootstrap version: include
theme = bs_theme(version = 5)or a preset theme - Use
thematic::thematic_shiny()in the server so base R and ggplot2 plots match the app theme - Use responsive widths like
width = "250px"inlayout_column_wrap()for auto-adjusting columns - Group sidebar inputs with
accordion()when sidebars have many controls - See migration.md for mapping legacy Shiny patterns to modern bslib equivalents
Avoid Common Errors
- Avoid directly nesting
card()containers.navset_card_*()functions are already cards;nav_panel()content goes directly inside them without wrapping incard() - Only use
layout_columns()andlayout_column_wrap()for laying out multiple elements. Single children should be passed directly to their container functions. - Never nest
page_*()functions. Only use one top-level page function per app.
Reference Files
- migration.md -- Legacy Shiny to modern bslib migration guide
- page-layouts.md -- Page-level layout functions and patterns
- grid-layouts.md -- Multi-column grid systems
- cards.md -- Card components and features
- value-boxes.md -- Value boxes for metrics and KPIs
- navigation.md -- Navigation containers and patterns
- sidebars.md -- Sidebar layouts and organization
- filling.md -- Fillable containers and fill items
- theming.md -- Basic theming (colors, fonts, Bootswatch). See shiny-bslib-theming skill for advanced theming
- accordions.md -- Collapsible sections and sidebar organization
- tooltips-popovers.md -- Hover tooltips and click-triggered popovers
- toasts.md -- Temporary notification messages
- inputs.md -- Special bslib input widgets
- best-practices.md -- bslib-specific patterns and common gotchas