Objectives

In this chapter I will design the user interface with {bslib}. I will provide data only if it necessary to show the functionality of the UI design.

I am condensing the most important section for my project of the {bslib} Dashboard article.

7.1 Plainest Design

There only three layout elements available in the standard layout:

  • the title,
  • the sidebar for the controls and
  • the main content area.
Hint 7.1: How to interact with the shinylive-r output?

In the above first example there is only a minimal interaction possible. Clicking on the right top arrow in the sidebar (<) will close the sidebar and therefore enlarge the main content area. But in this {shinylive} mode you can also change the code and rerun the program by clicking on the top right filled arrow (▶). Try it out to change one of the text strings.

7.2 Minimum content

Both the sidebar and main content areas can hold any arbitrary collection of UI elements, but it’s good practice to keep inputs in the sidebar and outputs in the main content area. Also, consider wrapping outputs in a card() and sidebar contents in a sidebar() object to add a title and/or customize styling, positioning, etc.

Even with the minimum card content we needed about 15 code lines. This is just a simple template. All parts can be arbitrary complex - title() could be for example a complex expression - sidebar() can hold many input controls and directives for the layout (e.g., several cards in a specific arrangement), - card() can hold complex results and server() is the most tricky part, because it needs reactivity (reactive() functions) to work together with the input controls.

Hint 7.2: Minimum content as template

I have developed a template as an RStudio snippet with the minimum content example. I am using it to start new Shiny apps or a shinylive-r code chunks.

I will stop here with the minimum content example. It works as a template for new apps resp. shinylive-r code chunks. But there are still to cover many other {bslib} design features and functions. Whenever the need arises I will either come back here to this section or write my notes in the appropriate project section.

7.3 Variable & Countries

7.4 Navigation

7.4.1 General consideration

It turned out that an incremental construction of the user interface is not productive. To have one part of the UI working — as I have succeeded in Listing / Output 7.1 — does not prevent me from a complete overhaul of the navigation structure for other functionality. It is therefore better to start with the general UI structure and then to develop the dashboard functions for the different visualizations.

For instance: To compare several indicators of one country is a complete different approach than to compare the development of one value in different countries. Together with other approaches (presenting a heat map of all countries, or displaying all relevant indicators for one specific country) it shows the need of a choice distinction at the global level of the dashboard.

7.4.2 Components

In Listing / Output 7.1 the user can choose between the main variable (score or rank) together with the global or component value. This UI has several disadvantages:

  1. Different number of years: For the global values 23 years (2022-2025 with 2011 missing) but for the component values we have currently (2025) only four years (2022-2025). This small number of years is not a problem by itself, but it indicates a different methodological approach and significance.
  2. Components belong together: From a methodological point of view it is more relevant, that the user could only choose one component at a time. But the five components are forming one global value and therefore their relation is important for the interpretation. For instance it is important to know which of the five components had the most influence for a given change.

In my application transfer for line and bump charts with real data values (Chapter 8) I have therefore limited the possible selection to just the global indicators of scores and ranks for a chosen set of countries.

7.4.3 Scores & ranks separated?

It was a nice feature in Listing / Output 7.1 that I can change between score and rank charts immediately, without adding countries again. But in this case I haven’t differentiated between the two different kinds of graphs: line chart versus bump chart. For this reason it is better to separate scores from ranks visualizations. Independently of this UI separation it should also possible to keep the country names constant whenever a user changes between these two different types of visualizations.

Another reason for the separation is the possibility to get another rank visualization by listing all countries by there ranks together with other information like rank values, flag, scores of global values and of component ranks. (At the moment there are only values for each components without a rank order of these component values. But these data shouldn’t be difficult to compute.)

Implementing this separation I have to abandon the idea of a navigation built on different visualization modes: maps, line charts and country information. Anyway, to distinguish modes is a idea far away from the everyday interests and practices of users. User want to know the scores, ranks etc. directly and not to think about the constitutional parts of abstract categories.

7.4.4 Missing features

At the moment I am missing some other possible features of my planned RWB dashboard:

  • Years: At the moment users can only select values for all years but it would be nice to select also a specific year, a span of years or a selection of specific years.
  • Regions: Currently user can select years but it would be important also to compare countries of a specific region or to contrast different regions.
ToDo 7.1: Limit number of countries to display at once

I have used as color palette the Paired {RColowBrewer} palette with 12 colors. Adding countries should therefore be limited with 12 lines of different colors. More than 12 lines are difficult to distinguish, so it is a good practice to limit the number of countries.

But how is this limit to apply? I need to track down the number of countries chosen, because user can raise or reduce the number of countries. Besides of this functionality I would also need a message that explains why adding another countries after the limit of 12 is not allowed.

7.4.5 Ideas and plans

7.4.5.4 conditionalPanel()

But with conditionalPanel() I also had to overcome two difficulties:

  1. I didn’t know how to build the connection between conditionalPanel() and nav_panel(). I learned that in both functions I had to use the same ID.
  2. I didn’t know what the === sign mean. After an internet research it turned out that this is the equal-test in JavaScript. One has to use Javascript code to coincide conditionalPanel() with nav_panel(). For this reason one has to use a dot notation to call the input value of the nav_panel() ID (e.g., “input.nav”) and not the common $ operator. Because of the JavaScript notation one must also include the code into quotes and need therefore two quote levels (e.g., "input.nav === 'Country'").

7.5 page_navbar() demo

R Code 7.4 : page_navbar() demonstration with dummy values

Listing / Output 7.2: Demo of the planned navigation structure. For details see Section 7.4
#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 500
#| components: [editor, viewer]
#| layout: vertical

## file: app.R
## path: app-071-page-navbar-demo/app.R
## my book: @sec-071-page-navbar-demo

## suppressWarnings(suppressPackageStartupMessages({
    library(shiny)
    library(shinythemes)
    library(bslib)
## })) # these suppresion does not work with  shinylive-r

sidebar_map <- layout_sidebar(
    sidebar = "Controls for map"
)

sidebar_chart <- layout_sidebar(
    sidebar = "Control for chart"
)

sidebar_country <- layout_sidebar(
    sidebar = "Control for country"
)

cards <- list(
    card(
        full_screen = TRUE,
        card_header("This is a map"),
    ),
    card(
        full_screen = TRUE,
        card_header("This is a line chart")
    ),
    card(
        full_screen = TRUE,
        card_header("This is country information")
    )
)

ui <- page_navbar(
    ## choose a bootstrap version and theme
    ## only themes from {shinythemes} are allowed
        ## cerulean, cosmo, cyborg, darkly, flatly, journal,
        ## lumen, paper, readable, sandstone, simplex, slate,
        ## spacelab, superhero, united, and yeti.
    theme = bs_theme(5, bootswatch = "cosmo"),
    ## next line does not work with shinylive-r
    ## navbar_options = navbar_options(class = "bg-primary", theme = "dark"),
    title = "Reporters Without Borders",
    id = "nav",
    sidebar = sidebar(
        conditionalPanel(
            "input.nav === 'Map'",
            "Map controls"
        ),
        conditionalPanel(
            "input.nav === 'Chart'",
            "Chart controls"
        ),
        conditionalPanel(
            "input.nav === 'Country'",
            "Country controls"
        )
    ),
    nav_spacer(),
    nav_panel("Map", cards[[1]]),
    nav_panel("Chart", cards[[2]]),
    nav_panel("Country", cards[[3]]),
    nav_item(tags$a("About", href = "https://peter.baumgartner.name", target="_blank"))
)

server <-  function(input, output) {}

shinyApp(ui, server)

Glossary Entries

term definition
Snippet An RStudio snippet is a text macro or code template used to quickly insert commonly used pieces of code, automating the process of typing repetitive or boilerplate code. See: https://rstudio.github.io/rstudio-extensions/rstudio_snippets.html

Session Info

Session Info

Code
xfun::session_info()
#> R version 4.5.1 (2025-06-13)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sequoia 15.6.1
#> 
#> Locale: en_US.UTF-8 / en_US.UTF-8 / en_US.UTF-8 / C / en_US.UTF-8 / en_US.UTF-8
#> 
#> Package version:
#>   askpass_1.2.1      base64enc_0.1.3    bslib_0.9.0        cachem_1.1.0      
#>   cli_3.6.5          commonmark_2.0.0   compiler_4.5.1     cpp11_0.5.2       
#>   curl_7.0.0         digest_0.6.37      dplyr_1.1.4        evaluate_1.0.5    
#>   farver_2.1.2       fastmap_1.2.0      fontawesome_0.5.3  fs_1.6.6          
#>   generics_0.1.4     ggplot2_3.5.2      glossary_1.0.0     glue_1.8.0        
#>   graphics_4.5.1     grDevices_4.5.1    grid_4.5.1         gtable_0.3.6      
#>   highr_0.11         htmltools_0.5.8.1  htmlwidgets_1.6.4  httr_1.4.7        
#>   isoband_0.2.7      jquerylib_0.1.4    jsonlite_2.0.0     kableExtra_1.4.0  
#>   knitr_1.50         labeling_0.4.3     lattice_0.22.7     lifecycle_1.0.4   
#>   litedown_0.7       magrittr_2.0.3     markdown_2.0       MASS_7.3.65       
#>   Matrix_1.7.4       memoise_2.0.1      methods_4.5.1      mgcv_1.9.3        
#>   mime_0.13          nlme_3.1.168       openssl_2.3.3      pillar_1.11.0     
#>   pkgconfig_2.0.3    R6_2.6.1           rappdirs_0.3.3     RColorBrewer_1.1-3
#>   renv_1.1.5         rlang_1.1.6        rmarkdown_2.29     rstudioapi_0.17.1 
#>   rversions_2.1.2    rvest_1.0.5        sass_0.4.10        scales_1.4.0      
#>   selectr_0.4.2      splines_4.5.1      stats_4.5.1        stringi_1.8.7     
#>   stringr_1.5.1      svglite_2.2.1      sys_3.4.3          systemfonts_1.2.3 
#>   textshaping_1.0.1  tibble_3.3.0       tidyselect_1.2.1   tinytex_0.57      
#>   tools_4.5.1        utf8_1.2.6         utils_4.5.1        vctrs_0.6.5       
#>   viridisLite_0.4.2  withr_3.0.2        xfun_0.53          xml2_1.4.0        
#>   yaml_2.3.10