How to achieve layout stability in shiny apps when the browser window is resized? Or, how to disable responsiveness (or achieve a similar UX)?

I'm developing a Shiny app for desktop use only. When the app is resized (i.e., when the web browser is resized), the layout of the app adjusts automatically. Of course, this is due to the underlying responsive logic in Bootstrap. The problem is, if the browser window is reduced to half its maximum width or more, the resulting layout is visually unappealing, at best. At worst, the layout takes on a configuration which hides important UI elements so grossly that it should be considered a bug in my application logic. Here's the behavior I'm trying to achieve: When the size of the browser window is reduced from its default, I'd like the sizes and layout of all the UI components to remain fixed. Instead, I'd like horizontal and vertical scroll bars to appear, such that the user must scroll the viewport over the underlying content of fixed size. This, as opposed to squeezing the content to fit into the viewport.

I'm searching for a solution to avoid the responsive features of Bootstrap that are driving the layout changes upon resizing. I could just invest more effort into ensuring that the application responds well during resizing, but I'd prefer to avoid the extra complexity and development/testing time to ensure I get reasonable results.

My app is based on the navbarPage. If the option to disable responsiveness were still available in Bootstrap 3, I'd use this option, and I think my problems would be solved. However, this option is now deprecated, because the underlying Bootstrap 3 framework assumes everyone wants responsive logic in their apps.

I've tried to remove the responsive logic directly from the bootstrap.css file that shiny uses behind the scenes (in addition to a small modification to bootstrap.R), but I'm struggling to make this work, despite following all of the guidance at https://getbootstrap.com/docs/3.4/getting-started/#disable-responsive. Even if I could get it to work, using this method would require me to deploy a custom version of the shiny package alongside my application package, which is not ideal.

I've also tried to set the min-width of a container which encapsulates the navbarPage, but this doesn't work either (the UI components within the parent container still collapse upon resizing). Interestingly, it does cause scroll bars to appear when the window width is less than the min width of the container. But it doesn't stop the collapsing behavior of the components inside the container.

I'd be eternally grateful if someone would provide some advice/guidance here. How can I enforce that my shiny app layout remains a constant while the browser window is resized?

Thanks,
Justin

P.S. Here is a simple example to reproduce the behavior I'm describing:

library(shiny)

ui <- tagList(
  tags$style(HTML(".container {
                  margin: 0 auto;
                  padding:0;
                  min-width: 1200px;
                  width: auto !important;
                  }
                  .container-fluid {
                  min-width: 1200px;
                  width: auto !important;
                  }")),
  tags$div(class="container",
           navbarPage(title=div(HTML('<strong>Example GUI</strong>')),
             tabPanel("Analysis 1", "TODO", value="anly1_tab", icon=icon("city")),
             tabPanel("Analysis 2", "TODO", value="anly2_tab", icon=icon("dna")),
             tabPanel("Analysis 3", "TODO", value="anly3_tab", icon=icon("bullseye")),
             tabPanel("Analysis 4", "TODO", value="anly4_tab", icon=icon("link")),
             tabPanel("Analysis 5", "TODO", value="anly5_tab", icon=icon("cubes")),
             tabPanel("Analysis 6", "TODO", value="anly6_tab", icon=icon("search")),
             id="navbar",
             selected="anly1_tab",
             position="fixed-top",
             collapsible=TRUE,
             fluid=TRUE,
             header=fluidRow(
               absolutePanel(fixedRow(column(6, div(HTML('<i class="fa fa-microscope" style = "color:#C8C8C8;"></i> &nbsp;<strong>Dataset</strong>')), style="padding:7px; text-align:center;"),
                                      column(6, selectInput("dataset_selector", label=NULL, choices=c("Dataset 1", "Dataset 2")), style="height:36px; align:center; padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px;"),
                                      style="padding-top:50px; padding-bottom:0px; margin-top:4px; background-color:#0072B2;"
               ), top=0, bottom=0, left="15px", width="300px"),
               absolutePanel(fixedRow(column(12, textOutput("dataset_desc", inline=FALSE), style="height:36px; padding:7px; text-align:left;"),
                                      style="padding-top:50px; padding-bottom:0px; margin-top:4px; background-color:#0072B2;"
               ), top=0, bottom=0, left="345px", right="15px"),
               style="height=50px; padding-top:50px; padding-bottom:0px; padding-left:0px; padding-right:0px; margin-top:0px; margin-left:0px; margin-right:0px; background-color:#0072B2;"
             ),
             theme=shinythemes::shinytheme("slate")
           )
  )
)

server <- function(input, output, session) {
  output$dataset_desc <- renderText({"A long-winded description of generic Dataset #1"})
}

shinyApp(ui=ui, server=server)

I'm answering my own question. Here is the code that achieves the desired result:

library(shiny)

ui <- tagList(
  tags$style(HTML(".nbcontainer .container{
                    margin: 0 auto;
                    padding:0;
                    min-width: 1200px;
                    width: auto !important;
                  }
                  .container-fluid {
                    min-width: 1200px;
                    width: auto !important;
                  }
                  .navbar.navbar-default.navbar-static-top {
                    margin-bottom:0px;
                  }")),
  tags$div(class="nbcontainer",
           navbarPage(title=div(HTML('<strong>Example GUI</strong>')),
             tabPanel("Analysis 1", "TODO", value="anly1_tab", icon=icon("city")),
             tabPanel("Analysis 2", "TODO", value="anly2_tab", icon=icon("dna")),
             tabPanel("Analysis 3", "TODO", value="anly3_tab", icon=icon("bullseye")),
             tabPanel("Analysis 4", "TODO", value="anly4_tab", icon=icon("link")),
             tabPanel("Analysis 5", "TODO", value="anly5_tab", icon=icon("cubes")),
             tabPanel("Analysis 6", "TODO", value="anly6_tab", icon=icon("search")),
             id="navbar",
             selected="anly1_tab",
             position="static-top",
             collapsible=FALSE,
             fluid=TRUE,
             # Note that the header row must be specified as a column!
             # See https://groups.google.com/forum/#!topic/shiny-discuss/DVF2YQeYfiE
             header=column(12,
               absolutePanel(fluidRow(column(12, div(HTML('<i class="fa fa-microscope" style = "color:#C8C8C8;"></i> &nbsp;<strong>Dataset</strong>')), style="height:36px; padding:7px; text-align:center;"),
                                      style="padding-top:0px; padding-bottom:0px; margin-top:1px; background-color:#0072B2;"
               ), top=0, bottom=0, left=0, width="150px"),
               absolutePanel(fluidRow(column(12, selectInput("dataset_selector", label=NULL, choices=c("Dataset 1", "Dataset 2")), style="height:36px; align:center; padding-top:0px; padding-bottom:0px; padding-left:0px; margin-top:0px; margin-bottom:0px;"),
                                      style="padding-top:0px; padding-bottom:0px; margin-top:1px; background-color:#0072B2;"
               ), top=0, bottom=0, left="165px", width="150px"),
               absolutePanel(fluidRow(column(12, textOutput("dataset_desc", inline=FALSE), style="height:36px; padding:7px; text-align:left;"),
                                      style="padding-top:0px; padding-bottom:0px; margin-top:1px; background-color:#0072B2;"
               ), top=0, bottom=0, left="345px", right="15px"),
               style="padding-top:0px; padding-bottom:0px; padding-left:0px; padding-right:0px; margin-top:0px; margin-left:0px; margin-right:0px; background-color:#0072B2;"
             ),
             theme=shinythemes::shinytheme("slate")
           )
  )
)

server <- function(input, output, session) {
  output$dataset_desc <- renderText({"A long-winded description of generic Dataset #1/2"})
}

shinyApp(ui=ui, server=server)

There were several important elements to the solution. The first was to recognize that the header should be specified as a column. The second was to switch the position of the navbar to "static-top", which ensures that the header is always visible, even when the navbar is redrawn during resizing. The third key, and this one is the most interesting, is to understand the logic by which responsive websites work. UI elements that are in separate columns will be collapsed onto separate rows when the browser window is made too narrow. However, UI elements in the same column will not be compressed this way. Once one starts to understand the layout semantics that drive the behavior of responsiveness, the design is no longer so mysterious and frustrating.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.