Search code examples
cssrshinyrenderui

Why are renderUI() elements ignoring CSS?


I'm building a simple Shiny App, and have a decent understanding (or so I thought) of CSS and how to use it to style different UI elements (background color, size, etc.).

The issue I'm having is that I'm using renderUI() a lot in my app, because I need the inputs to be reactive to one another. When I move the selectInput() from the ui portion of the app into the server section (and use it inside renderUI()), it now seemingly ignores the CSS. What gives?

Here's the first example, NOT using renderUI(), and the CSS (in this case, just a smaller 6px font-size for the selectInput() text) works fine:

library(shiny)

ui <- fluidPage(
  tags$head(tags$style(HTML('.selectize-input {font-size: 6px;}'))),
  shiny::selectInput("input_1", label = NULL, choices = c("a", "b", "c"))
)
server <- function(input, output, session){}
shinyApp(ui = ui, server = server)

Result: enter image description here

Then I move the selectInput() call into a renderUI() function in the server section, and the CSS doesn't get applied, even though I'm not changing that part at all. Shouldn't the CSS rules still be applied to all .selectize-input elements?

library(shiny)

ui <- fluidPage(
  tags$head(tags$style(HTML('.selectize-input {font-size: 6px;}'))),
  shiny::uiOutput(outputId = "output_1")
)

server <- function(input, output, session){
  
  output$output_1 = shiny::renderUI({
    shiny::selectInput("input_1", label = NULL, choices = c("a", "b", "c"))
  })
  
}
shinyApp(ui = ui, server = server)

Result: enter image description here


Solution

  • In the head tag of an HTML document generally the order matters, e.g. if you have multiple stylesheets. Shiny constructs the head tag in a sense of 'as needed'.

    • In your first example where you don't render something inside the server, the head tag looks like this:

      ... 
      <link href="selectize-0.15.2/css/selectize.bootstrap3.css" rel="stylesheet"> 
      ... 
      <style> .selectize-input {font-size: 6px;}</style> 
      ...
      

      Your custom style is loaded at the end and hence overwrites the selectize styles.

    • However, in your second example, where you use renderUI around the selectInput inside the server and then put an uiOutput in the ui, the head tag has this order:

      ... 
      <style> .selectize-input {font-size: 6px;}</style> 
      ... 
      <link href="selectize-0.15.2/css/selectize.bootstrap3.css" rel="stylesheet" type="text/css"> 
      ... 
      

      Your custom style gets overwritten by the selectize styles (here: font-size: inherit). The order is expectable because your own styles may need to be relevant before the selectInput gets rendered (it is not rendered 'immediately').

    While it is a possibility to edit the CSS in combination with e.g. adding additional classes, this may not be the most desirable approach in particular in larger apps where you have a lot of custom styles. If it is suitable for you that in the second example the order of the head tag is the same as in the first example, one approach to set this would be like this: shiny has a function insertUI() which can be used for adding arbitrary UI elements into the app, and below the ui is an htmltools::htmlDependency() which contains the custom style without additional editing. It gets loaded after the selectize styles. There might be a more straightforward method, though.

    library(shiny)
    
    ui <- fluidPage(
      shiny::uiOutput(outputId = "output_1", class = "myClass")
    )
    
    server <- function(input, output, session){
      
      output$output_1 = shiny::renderUI({
        shiny::selectInput("input_1", label = NULL, choices = c("a", "b", "c"))
      })
      
      observe({
        insertUI(
          selector = "head",
          ui = htmltools::htmlDependency(
            name = "myCSS",
            version = "1.0",
            src = ".",
            package = "shiny",
            head = "<style> .myClass {font-size: 6px;} </style>"
          )
        )
      })
      
    }
    shinyApp(ui = ui, server = server)