Shiny for R updates: new default look, dark mode, shinylive updates, and more

An overview of recent Shiny for R updates, a new default look, dark mode, shinylive updates, and more.


Carson Sievert

Garrick Aden-Buie


November 30, 2023

The Shiny team is pleased to announce another round of updates for 13 different R packages, including shiny and bslib. There are too many improvements to cover in a single post, but we’d like to highlight some of the more notable additions. For a detailed list of changes, be sure to check out the release notes section of this post.

bslib brings modern Bootstrap versions and new user layouts and inputs to Shiny, the web framework for data scientists. Install the latest versions of shiny and bslib from CRAN with:

install.packages(c("shiny", "bslib"))

In this post, we’ll cover three main topics: the new shiny look, what’s new in shinylive and a long-awaited update to selectize.js in shiny.

A shiny new look

In our last post, we previewed a new look for bslib-powered UIs, which is designed with dashboards in mind. This release of bslib adds more polish to this new “preset” theme and makes it the default for bslib powered UIs.

To use the new layouts, simply create an app using any page_*() function in bslib for the UI. Here’s a very simple template you can use to get started. Notice that we’ve used shinylive to make this example interactive – the entire app is running in your browser, no server required! You can even edit the app right here in this post and see the changes live when you press the play button. We’ll talk more about shinylive later in this post.

We’re calling this new look the shiny “preset” theme, because it’s a great place to start building your own theme. Remember, you can always customize the preset by passing additional arguments to bs_theme(). You can even switch back to the default preset = "bootstrap" look by using the following theme value in your page_*() function1:

# use default Bootstrap styles
theme = bs_theme(preset = "bootstrap")

The new shiny preset is designed with dashboards in mind. Here’s a more complete example using a full dashboard app (source, demo) for exploring flight data from Chicago. Toggle between the new and old look to see what’s changed with this release.

Flights dashboard with the new styles, featuring a white navbar and white sidebar framing the dashboard area. Cards are also entirely white on a light gray background, with subtle depth created by drop shadows under the cards. Blue accents are found throughout the dashboard in plots and icons.
Flights dashboard with the old styles, features solid blue navbar and solid blue value boxes. The sidebar and card headers and footers all have a light gray background.

The rest of this section will explore a few highlights of the new default look, including:

Quarto and PyShiny dashboards

Dashboards are coming to Quarto!

The new Quarto dashboard format, as well as newer PyShiny components, are built on the same foundation as bslib. Thus, the concepts you’ll learn while building dashboards with bslib should also largely apply there as well.

Page-level styling

As we noted above, the new shiny preset is design with dashboards in mind, but it will make any shiny app look great. The new default look is designed to be light and minimal, with a white navbar and white sidebar framing the dashboard area. Cards are also entirely white, with subtle depth created by drop shadows under the cards. Value boxes provide colorful accents, as will the plots you add to showcase your data.

Here’s an example taken straight from the getting started guide for bslib dashboards.

New page_sidebar() styling featuring a light, minimal look with a white title bar, white sidebar and white cards and value boxes. The cards and value boxes have subtle shadows on a light gray background. Value boxes icons are blue with a subtle gradient
Old page_sidebar() styling with a dark title bar. Sidebars and card headers and footers have a light gray background. Cards and value boxes do not have shadows and value box icons are solid dark gray.

To achieve the full dashboard effect, though, you have opt into the light gray background by adding class = "bslib-page-dashboard" to your page_sidebar() or the nav_panel() items in your page_navbar(). This class enables a few additional features, namely adding a soft gray background to the main content area under cards and value boxes that help them stand out. You can also add the class directly to page_fillable() or page() to get the same effect in apps with custom layouts.


ui <- page_sidebar(
  title = "My Dashboard",
  class = "bslib-page-dashboard",
  sidebar = sidebar(
    title = "Settings",
    # ... sidebar inputs ...
  # ... dashboard content ...

ui <- page_navbar(
  title = "My Dashboard",
    title = "Page 1",
    class = "bslib-page-dashboard",
    # ... dashboard content ...
  nav_panel("About", "Regular content")

ui <- page_fillable(
  title = "My Dashboard",
  class = "bslib-page-dashboard",
  # ... custom layout and dashboard content ...

Built-in dark mode support ☀️ 🌙

This release of bslib brings built-in dark mode support to any Shiny app that uses bs_theme(), thanks to Bootstrap 5.3’s new client-side color mode feature! To enable dark mode in your app, add input_dark_mode() somewhere in your UI. In the example below, we’ve put in the navbar.

Flights dashboard in dark mode. All white areas are now a deep dark gray. The blue accents remain.
Flights dashboard in light mode as previously described.

For the best results, make sure you have the latest version of shiny. Dark mode works with nearly any Bootstrap theme created with bs_theme(), including the new shiny preset, but it tends to work best when the theme is designed around light mode first. For matching plots and widgets to the current color mode, you can use thematic to automatically style plots or shiny::getCurrentOutputInfo() to manually set the colors of your R outputs.

By default, the color mode is picked from the user’s system settings – i.e. choosing dark mode if their system is also in dark mode – but you can choose the initial color mode via the mode argument. If you give input_dark_mode() an id, it reports the current color mode as either "light" or "dark".


ui <- page_navbar(
  title = "Dashboard",
  nav_spacer(), # push nav items to the right
  nav_panel("Page 1", "Dashboard content"),
    input_dark_mode(id = "dark_mode", mode = "light")

server <- function(input, output, server) {
  observeEvent(input$dark_mode, {
    if (input$dark_mode == "dark") {
      showNotification("Welcome to the dark side!")

shinyApp(ui, server)

Value box styling

The new default look includes improved styling for value_box() outputs, which are commonly used in dashboards. We’re also excited to announce a new Build-a-Box app to help build and explore value boxes themes and options in a live Shiny app.

Use the tabs below to learn more about several new features and themes supported by value_box().

Refreshed Shiny UI

The new default look includes a refreshed Shiny UI, which includes new styling for inputs, modals, notifications, and more.

New inputs styles, in particular a minimal slider input.
New notification styles for default, message, warning and error notifications. Notifications have shadows and have a large amount of padding around the edges compared with Shiny's default design.
New progress bar notification styles, which are similar to notifications.
An example modal with the new shiny preset style. The modal is minimal with additional padding and the backdrop has a soft blur effect.

Shinylive updates

Thanks to the exceptional work by George Stagg on webR in collaboration with the Shiny team, shinylive can now run Shiny applications entirely in a web browser, without the need for a separate server running R!

While sharing a traditional Shiny app requires you to deploy the app to a server, such as, shinylive allows you to share your app by simply sharing a URL or by embedding the shinylive app in a Quarto webpage. The app runs entirely in the browser, directly on the user’s device.

We’re please to announce several venues for writing and sharing Shiny apps via shinylive:

  1. contains a gallery of example Shiny apps that you can run in your browser. You can also use as an online playground to write and share your own apps.

  2. The shinylive R package is now on CRAN! This package helps you turn an existing Shiny app into a ready-to-share shinylive app.

  3. The shinylive Quarto extension now supports both R and Python Shiny apps – even on the same page! With the shinylive-r and shinylive-python code cells, you can embed Shiny apps directly in Quarto web documents. This is perfect for blog posts, like this one! See the example near the start of this post.

webR and shinylive are under active development, so expect ongoing updates and improvements. Currently, shinylive apps download packages from webR’s CRAN-like repository at run time, which adds a delay to the initial startup time. In the future, we hope to make this faster and to allow package installation from more sources. We’re also really excited that R-universe now builds WASM binaries for R packages!

Selectize.js update

Shiny’s selectInput() and selectizeInput() functions create dropdown menus that allow users to select one or more items from a list. These inputs are powered by the selectize.js library, and shiny 1.8.0 upgrades selectize.js from version 0.12.4 to 0.15.2.

This upgrade resolved a number of outstanding bugs and issues with selectizeInput() (as well as introducing some new ones that we had to squash before release). Most users won’t notice a difference in the select inputs – now they’ll just work better – but if you do notice a change in behavior, please let use know by filing an issue.

Power users will find even more selectize.js options now available, including more plugins. We highly recommend trying both the clear_button and remove_button plugins to give users a clear visual cue for removing options:

A demo with two select inputs. The first allows a single value and uses the clear_button plugin. An X on the select input allows the user to clear the selection. The second input allows multiple values and uses the remove_button plugin. Each selected value has an X that allows the user to remove the value.


ui <- page_fixed(
        "single", "Single select",,
        options = list(plugins = "clear_button")
        "multiple", "Multiple select",,
        multiple = TRUE,
        options = list(plugins = "remove_button")

server <- function(input, output, session) {


shinyApp(ui, server)

Release notes

This post doesn’t cover all of the changes and updates that happened in the Shiny universe in this release cycle. To learn more about specific changes in each package, dive into the release notes linked below!

Big shout out to everyone involved! 💙 We’d want to extend a huge thank you to everyone who contributed pull requests, bug reports and feature requests. Your contributions make Shiny brilliant!

bslib v0.6.0

@antoine4ucsd, @awcm0n, @barnesparker, @cpsievert, @ctrlxctrlc, @daattali, @DavZim, @durraniu, @gadenbuie, @gsmolinski, @jcheng5, @jmbarbone, @JohnCoene, @kelly-sovacool, @lmullany, @m-austen, @MayaGans, @mhanf, @ncullen93, @ngoodkindGSI, @oude-gao, @schloerke, @scrapeable, @tuge98, and @wch.

bsicons v0.1.2


crosstalk v1.2.1

@cpsievert, @ctedja, @daattali, @danielludolf, @DataStrategist, @dmresearch15, @gadenbuie, @helgasoft, @hlydecker, @JacobBraslaw22, @jcheng5, @jonathanmburns, @jonspring, @LDSamson, @MichaelChirico, @mmfc, @novotny1akub, @oobd, @pfh, @schloerke, @tbrittoborges, @ThierryO, @tomsing1, @ulyngs, @warnes, @yb2125, and @yogat3ch.

histoslider v0.1.1


htmltools v0.5.7

@bjcarothers, @cpsievert, @gadenbuie, @HenningLorenzen-ext-bayer, @mgirlich, and @stla.

htmlwidgets v1.6.3

@barracuda156, @cpsievert, @DavisVaughan, @dmurdoch, @gadenbuie, @pietrodito, and @yihui.

httpuv v1.6.12

@Camilo-Mora, @gadenbuie, @jcheng5, @jeroen, @nealrichardson, and @wfulp.

leaflet.providers v2.0.0

@gadenbuie, @schloerke, and @SimonGoring.

leaflet v2.2.1

@barracuda156, @Bryan1qr, @gadenbuie, @gtalavera, @jmelichar, @mjdzr, and @PietrH.

learnr v0.11.4

@davidkane9, @gadenbuie, @jimjam-slam, @katieravenwood, and @NaturallyAsh.

plotly v4.10.3

@AdroMine, @AlexisDerumigny, @Apompetti-Cori, @cashfields, @cpsievert, @CristianRiccio, @davidhodge931, @DrMattG, @geejaytee, @jacole3, @jrbarber37, @lennartraman, @LouisLeNezet, @MichalLauer, @mjdzr, @mumbarkar, @Obsidian-user, @olivroy, @OverLordGoldDragon, @rsbivand, @stephanmg, @stla, @TheAnalyticalEdge, @ThierryO, @tomasnobrega, @tvedebrink, @uriahf, @whitejf, @wholmes105, @wmay, @yogat3ch, and @zeehio.

shiny v1.8.0

@avsdev-cw, @bathyscapher, @chlebowa, @cpsievert, @deining, @flachboard, @gadenbuie, @jcheng5, @karangattu, @nstrayer, @wbakerrobinson, and @wch.

shinyvalidate v0.1.3

@BajczA475, @bhogan-mitre, @chlebowa, @cleber-n-borges, @cpsievert, @dependabot[bot], @DivadNojnarg, @doncqueurs, @Sebastian-T-T, @stefanoborini, @stephenwilliams22, @Teebusch, and @Wezz0234.

thematic 0.1.4

@AlbertRapp, @cpsievert, and @jfulponi.