Page Layouts in bslib
This reference covers the page-level layout functions in bslib that structure entire Shiny applications. These are the top-level containers that determine the overall architecture of your app.
Table of Contents
- Dashboard Layouts
- Basic Page Layouts
- Filling vs Scrolling Behavior
- Mobile Considerations
- Production Best Practices
Dashboard Layouts
page_sidebar()
The primary function for creating single-page dashboards with a sidebar and main content area.
Basic structure:
ui <- page_sidebar(
title = "My dashboard",
sidebar = sidebar("Sidebar content"),
"Main content area"
)
Best practice: Keep inputs in the sidebar and outputs in the main content area. Wrap outputs in card() and sidebar contents in sidebar() for titles and custom styling.
Example with cards:
ui <- page_sidebar(
title = "Penguins Dashboard",
sidebar = sidebar(
selectInput("species", "Species", choices = unique(penguins$species))
),
card(
full_screen = TRUE,
card_header("Bill Length"),
plotOutput("bill_length")
),
card(
card_header("Summary Statistics"),
verbatimTextOutput("summary")
)
)
Key parameters:
title: App title displayed at the topsidebar: Asidebar()object with inputs/controlstheme: Optionalbs_theme()object for stylingfillable: Whether the page should fill the viewport height (default TRUE)fillable_mobile: Whether fillable behavior applies on mobile (default FALSE)class = "bslib-page-dashboard": Adds a light gray background behind the main area, which looks best when cards are used as the primary content containers
page_navbar()
Use page_navbar() for multi-page dashboards with a top navigation bar. Each page is defined with nav_panel().
Basic structure:
ui <- page_navbar(
title = "Multi-Page Dashboard",
nav_panel("Page 1", "Content for page 1"),
nav_panel("Page 2", "Content for page 2"),
nav_panel("Page 3", "Content for page 3")
)
With sidebar:
ui <- page_navbar(
title = "Penguins Dashboard",
sidebar = sidebar(
selectInput("color_by", "Color by", choices = c("species", "island"))
),
nav_spacer(),
nav_panel("Bill Length", card(...)),
nav_panel("Bill Depth", card(...)),
nav_panel("Body Mass", card(...)),
nav_item(tags$a("Documentation", href = "https://example.com"))
)
Important caveat: page_navbar()'s sidebar argument puts the same sidebar on every page. If you need different sidebars per page or conditional sidebar contents, see the sidebars reference for strategies.
Key parameters:
title: App title in the navbarsidebar: Optionalsidebar()shown on all pagesid: ID for tracking the active page (accessible asinput$<id>)fillable: Can be TRUE (all pages), FALSE (no pages), or a vector of page namestheme: Optionalbs_theme()object
Dashboard appearance: Add class = "bslib-page-dashboard" to individual nav_panel() containers (not the page_navbar() itself) to get a light gray background on specific pages:
page_navbar(
title = "My App",
nav_panel("Dashboard", class = "bslib-page-dashboard",
card(...),
card(...)
),
nav_panel("About", "Plain white background here")
)
Navigation helpers:
nav_spacer(): Adds spacing/pushes subsequent items rightnav_item(): Adds arbitrary HTML (e.g., links) to navbarnav_menu(): Creates dropdown menus
page_fillable()
A screen-filling page layout where content grows/shrinks to fit the browser window. This is the foundation for filling layouts in bslib.
Key behavior: Direct children of page_fillable() become fill items, meaning they'll resize to fill available space. This is ideal for dashboards where you want outputs to adapt to the viewport size. Typical usage wraps layout_columns() (or similar layout containers) and card() components as direct children.
When to use:
- Dashboards with plots/maps that should expand to fill the screen
- Single-page apps where all content should be visible without scrolling
- Apps with dynamic layouts that adapt to window size
Note: page_sidebar() and page_navbar() are built on top of page_fillable() and inherit its filling behavior by default.
Basic Page Layouts
Beyond dashboard layouts, bslib provides traditional page functions:
page_fluid(): Full-width page that resizes horizontally but scrolls verticallypage_fixed(): Fixed-width page (940px default) that scrolls verticallypage(): Most flexible option with manual control
These are useful when you don't want filling behavior and prefer traditional scrolling layouts.
Filling vs Scrolling Behavior
Filling Layouts (Default for Dashboard Pages)
Both page_sidebar() and page_navbar() default to fillable = TRUE, where outputs are encouraged to grow/shrink to fit the browser window.
Benefits:
- Content adapts to available screen space
- Professional dashboard appearance
- No scrolling needed when content fits
Considerations: When content has large intrinsic minimum heights:
- Set
heighton cards that shouldn't resize - Set
min_heighton cards that need a minimum size - Set
max_heighton cards that shouldn't grow too large
Example:
layout_columns(
card(min_height = 200, max_height = 400, plotOutput("plot1")),
card(height = 300, lorem::ipsum(10))
)
Scrolling Layouts
For pages with many outputs or long content, set fillable = FALSE to disable filling behavior. This causes outputs to fall back to their default heights (~400px for plots) with page scrolling enabled. Pass fillable = FALSE directly to page_sidebar() or page_navbar().
For page_navbar(), use selective filling:
ui <- page_navbar(
title = "Mixed Layout",
fillable = c("Overview", "Analysis"), # Only these pages fill
nav_panel("Overview", ...), # Fills viewport
nav_panel("Analysis", ...), # Fills viewport
nav_panel("Details", ...) # Scrolls normally
)
Mobile Considerations
By default, filling layout is disabled on mobile devices to prevent awkward resizing on small screens. Set fillable_mobile = TRUE on page_sidebar() or page_navbar() to enable filling on mobile.
Best practices for mobile:
- Use
min_heighton cards to prevent excessive shrinking - Sidebars collapse by default on mobile (configurable via
sidebar(open = ...)) - Test responsive breakpoints using browser dev tools
- Consider using
layout_column_wrap()for responsive multi-column layouts
Production Best Practices
Pin Bootstrap Version
Before deploying to production, hard-code the Bootstrap version to prevent breakage on updates. Pass theme = bs_theme(version = 5) to any page function. This ensures your app uses Bootstrap 5 (recommended for modern features) and won't break if bslib's default version changes.
Theming
Pass a bs_theme() object to the theme parameter of any page function to customize appearance. See theming.md for comprehensive theming guidance.
Plot Styling
Use the thematic package to ensure plots match your theme. Call thematic::thematic_shiny() in your server function or app.R to automatically style plotOutput() to match your CSS theme colors.
Performance Tips
- For apps with many outputs, consider using
fillable = FALSEand letting users scroll - Use
card(full_screen = TRUE)to allow expanding individual visualizations - Consider using
navset_card_tab()to organize related outputs within a single card - Profile your app with
profvisto identify rendering bottlenecks