I am looking to build a dark mode in my application. I realize we can achieve that using bs_theme(), but my application has many more settings in an external css file that do not get read when I use bs_theme().
I want to have 2 separate CSS files, one for light theme and one for dark theme. Based on the user input, the relevant theme file should be loaded in my Rshiny app. Any suggestion on how this could be done?
I'd create two different css classes in this case.
Here you'll find how to include css files (as many as you like) into a shiny app.
To switch between the classes we can use addCssClass/ removeCssClass (or toggleCssClass) from library(shinyjs):
library(shiny)
library(shinyjs)
if(!dir.exists("www")){
dir.create("www")
}
writeLines(".dark {
background-color: black;
color: white; /* text color */
}", con = "www/dark_mode.css")
writeLines(".light {
background-color: white;
color: black; /* text color */
}", con = "www/light_mode.css")
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
),
radioButtons("mode", "Select mode", choices = c("dark", "light"), selected = "light")
)
server <- function(input, output, session) {
observeEvent(input$mode, {
if(input$mode == "dark"){
addCssClass(class = "dark", selector = "body")
removeCssClass(class = "light", selector = "body")
} else {
addCssClass(class = "light", selector = "body")
removeCssClass(class = "dark", selector = "body")
}
})
}
shinyApp(ui, server)

The same can be done via Shiny.addCustomMessageHandler and some custom JS using e.g. element.classList.add("myclass"); (see this).
Edit: Apply addCssClass to inputs of different classes:
library(shiny)
library(shinyjs)
if(!dir.exists("www")){
dir.create("www")
}
writeLines(".dark {
background-color: black !important;
color: white; /* text color */
}", con = "www/dark_mode.css")
writeLines(".light {
background-color: white !important;
color: black; /* text color */
}", con = "www/light_mode.css")
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
),
radioButtons("mode", "Select mode", choices = c("dark", "light"), selected = "light"),
selectizeInput("Test", "Test Input", choices = 1:10),
actionButton("testButton", "Test Button")
)
server <- function(input, output, session) {
observeEvent(input$mode, {
applyTo <- list(".selectize-input", ".btn-default")
if(input$mode == "dark"){
lapply(applyTo, function(x){
addCssClass(class = "dark", selector = x)
removeCssClass(class = "light", selector = x)
})
} else {
lapply(applyTo, function(x){
addCssClass(class = "light", selector = x)
removeCssClass(class = "dark", selector = x)
})
}
})
}
shinyApp(ui, server)

Edit: using forEach:
library(shiny)
library(shinyjs)
if(!dir.exists("www")){
dir.create("www")
}
writeLines(".dark {
background-color: black !important;
color: white; /* text color */
}", con = "www/dark_mode.css")
writeLines(".light {
background-color: white !important;
color: black; /* text color */
}", con = "www/light_mode.css")
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "dark_mode.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "light_mode.css")
),
radioButtons("mode", "Select mode", choices = c("dark", "light"), selected = "light"),
selectizeInput("Test", "Test Input", choices = 1:10),
actionButton("testButton", "Test Button")
)
server <- function(input, output, session) {
dm_classes <- paste(c(".selectize-input", ".btn-default"), collapse = ", ")
observeEvent(input$mode, {
if(input$mode == "dark"){
runjs(sprintf("document.querySelectorAll('%s').forEach(x=>x.classList.add('dark'));
document.querySelectorAll('%s').forEach(x=>x.classList.remove('light'));", dm_classes, dm_classes))
} else {
runjs(sprintf("document.querySelectorAll('%s').forEach(x=>x.classList.add('light'));
document.querySelectorAll('%s').forEach(x=>x.classList.remove('dark'));", dm_classes, dm_classes))
}
})
}
shinyApp(ui, server)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With