Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to switch external css file based on user input in rshiny?

Tags:

css

r

shiny

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?

like image 707
shivam_data_scientist Avatar asked Nov 18 '25 12:11

shivam_data_scientist


1 Answers

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)

result

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)

result


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)
like image 75
ismirsehregal Avatar answered Nov 20 '25 02:11

ismirsehregal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!