ADVANCED R PROGRAMMING, SUMMER 2025 EDITION



Application structure in the Shiny R package

The Shiny package is used to easily build interactive web applications whose primary purpose is to explore and/or present a data set. The package is installed in a standard way.

install.packages("shiny")

Eleven examples showing how Shiny works have been added to the package. Each example is a standalone application.

library (shiny)
runExample ("01_hello")   # a histogram


Other examples:

runExample ("02_text")       # tables and data frames
runExample ("03_reactivity") # a reactive expression
runExample ("04_mpg")        # global variables
runExample ("05_sliders")    # slider bars
runExample ("06_tabsets")    # tabbed panels
runExample ("07_widgets")    # help text and submit buttons
runExample ("08_html")       # Shiny app built from HTML
runExample ("09_upload")     # file upload wizard
runExample ("10_download")   # file download wizard
runExample ("11_timer")      # an automated timer


Shiny applications are contained in a single app.R script. If the script is placed in the my_app folder, the application will be launched by running the command:

runApp ("my_app")

The app.R script contains three parts:

Below is the content of the script that runs the Hello Shiny! example.

library(shiny)

# Define UI for app that draws a histogram ----
ui <- fluidPage (

  # App title ----
  titlePanel("Hello Shiny!"),

  # Sidebar layout with input and output definitions ----
  sidebarLayout(

    # Sidebar panel for inputs ----
    sidebarPanel(

      # Input: Slider for the number of bins ----
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)

    ),

    # Main panel for displaying outputs ----
    mainPanel(

      # Output: Histogram ----
      plotOutput(outputId = "distPlot")

    )
  )
)

# Define server logic required to draw a histogram ----
server <- function (input, output) {

  # Histogram of the Old Faithful Geyser Data ----
  # with requested number of bins
  # This expression that generates a histogram is wrapped in a call
  # to renderPlot to indicate that:
  #
  # 1. It is "reactive" and therefore should be automatically
  #    re-executed when inputs (input$bins) change
  # 2. Its output type is a plot
  output$distPlot <- renderPlot({

    x    <- faithful$waiting
    bins <- seq(min(x), max(x), length.out = input$bins + 1)

    hist(x, breaks = bins, col = "#007bc2", border = "white",
         xlab = "Waiting time to next eruption (in mins)",
         main = "Histogram of waiting times")

    })
}

# Run the app
shinyApp (ui = ui, server = server)

The bare minimum needed to run a blank application is as follows:

library(shiny)

# Define UI
ui <- fluidPage (
  
)

# Define server logic
server <- function (input, output) {
  
}

# Run the app
shinyApp (ui = ui, server = server)


Building the user interface


Application layout

Shiny uses the fluidPage function to adjust automatically to the user’s browser window size. The two most common elements added to fluidPage are titlePanel and sidebarLayout. The first one contains, of course, the title of the application, while the second one takes at least two arguments: sidebarPanel and mainPanel.

ui <- fluidPage (
  titlePanel ("title panel"),

  sidebarLayout (
    sidebarPanel ("sidebar panel"),
    mainPanel ("main panel")
  )
)


sidebarPanel is located on the left side by default, but this can be changed using the position parameter:

ui <- fluidPage (
  titlePanel ("title panel"),
  
  sidebarLayout (position = "right",
                 sidebarPanel ("sidebar panel"),
                 mainPanel ("main panel")
  )
)

Source: https://shiny.rstudio.com/tutorial/written-tutorial/lesson2/
titlePanel and sidebarLayout generate the basic look for the Shiny application, but a different layout can be used. Using fluidRow and column allows you to build applications based on any grid, while using navbarPage you can get multiple tabs placed in the navigation bar. These layouts are discussed in the Shiny Application Layout Guide.


HTML functions

The content inside functions from the *Panel family can be formatted using commands known from the HTML language:

Function HTML5 tag Action
p <p> New paragraph of text
h1 <h1> First level heading
h2 <h2> Second level heading
h3 <h3> Third level heading
h4 <h4> Fourth level heading
h5 <h5> Fifth level heading
h6 <h6> Sixth level heading
a <a> Hyperlink
br <br> Linebreak
div <div> A section of text with a uniform style
span <span> Inline section of text without style
pre <pre> Fixed-width preformatted text
code <code> Source code format
img <img> Image
strong <strong> Bold
em <em> Italic
HTML Directly passes the string as HTML
ui <- fluidPage (
  titlePanel ("My Star Wars App"),
  sidebarLayout (
    sidebarPanel (),
    mainPanel (
      h6("Episode IV", align = "center"),
      h6("A NEW HOPE", align = "center"),
      h5("It is a period of civil war.", align = "center"),
      h4("Rebel spaceships, striking", align = "center"),
      h3("from a hidden base, have won", align = "center"),
      h2("their first victory against the", align = "center"),
      h1("evil Galactic Empire.")
    )
  )
)
ui <- fluidPage (
  titlePanel ("My Shiny App"),
  sidebarLayout (
    sidebarPanel (),
    mainPanel (
      p("A new p() command starts a new paragraph. Supply a style attribute to change the format
        of the entire paragraph.", style = "font-family: 'times'; font-si16pt"),
      strong("strong() makes bold text."),
      em("em() creates italicized (i.e, emphasized) text."),
      br(),
      code("code displays your text similar to computer code"),
      div("div creates segments of text with a similar style. This division of text is all blue
          because I passed the argument 'style = color:blue' to div", style = "color:blue"),
      br(),
      p("span does the same thing as div, but it works with",
        span("groups of words", style = "color:blue"),
        "that appear inside a paragraph.")
    )
  )
)


Images

The img (src = "my_image.png") function is responsible for adding images to the application, the first argument of which is the name of the file containing the graphics that we want to place in the application. Within the function, it is also possible to manipulate the height and width of the image (which are given in pixels).

img (src = "my_image.png", height = 72, width = 72)

The file location is an important issue. The src argument should not contain a path, only a name. The file should be placed in the www folder, which in turn should be placed in the same folder as the app.R file.

A screenshot showing the folder arrangement in the Shiny app.
A screenshot showing the folder arrangement in the Shiny app.
ui <- fluidPage (
  titlePanel ("My Shiny App"),
  sidebarLayout(
    sidebarPanel(),
    mainPanel(
      img(src = "rstudio.png", height = 140, width = 400)
    )
  )
)


Widgets

Widgets, i.e., control components, are used to retrieve numerical or text data from the user. It is through them that the user interacts with the application. In the Shiny package, we can choose from many different controls presented in the image below.

Controls are placed in the panel using appropriate functions:

Function Widget
actionButton Button
checkboxGroupInput Checkbox group
checkboxInput Single checkbox
dateInput Calendar for selecting a single date
dateRangeInput A pair of calendars to choose a time period
fileInput File selection window
helpText Supporting text
numericInput Field to enter a number
radioButtons Radiobuttons group
selectInput Dropdown list
sliderInput Slider
submitButton Submit button
textInput Field to enter a text

Each of the above functions may require several arguments, including two:

  • inputId - the user does not see it, but allows the value to be retrieved from the control,
  • label - a caption that will appear next to the control; can be the empty string "".

The remaining arguments depend on the specific widget and include, for example, initial values or ranges. The following code calls the application from the image above.

library(shiny)

# Define UI
ui <- fluidPage(
  titlePanel("Basic widgets"),
  
  fluidRow(
    
    column(3,
           h3("Buttons"),
           actionButton("action", "Action"),
           br(),
           br(), 
           submitButton("Submit")),
    
    column(3,
           h3("Single checkbox"),
           checkboxInput("checkbox", "Choice A", value = TRUE)),
    
    column(3, 
           checkboxGroupInput("checkGroup", 
                              h3("Checkbox group"), 
                              choices = list("Choice 1" = 1, 
                                             "Choice 2" = 2, 
                                             "Choice 3" = 3),
                              selected = 1)),
    
    column(3, 
           dateInput("date", 
                     h3("Date input"), 
                     value = "2014-01-01"))   
  ),
  
  fluidRow(
    
    column(3,
           dateRangeInput("dates", h3("Date range"))),
    
    column(3,
           fileInput("file", h3("File input"))),
    
    column(3, 
           h3("Help text"),
           helpText("Note: help text isn't a true widget,", 
                    "but it provides an easy way to add text to",
                    "accompany other widgets.")),
    
    column(3, 
           numericInput("num", 
                        h3("Numeric input"), 
                        value = 1))   
  ),
  
  fluidRow(
    
    column(3,
           radioButtons("radio", h3("Radio buttons"),
                        choices = list("Choice 1" = 1, "Choice 2" = 2,
                                       "Choice 3" = 3),selected = 1)),
    
    column(3,
           selectInput("select", h3("Select box"), 
                       choices = list("Choice 1" = 1, "Choice 2" = 2,
                                      "Choice 3" = 3), selected = 1)),
    
    column(3, 
           sliderInput("slider1", h3("Sliders"),
                       min = 0, max = 100, value = 50),
           sliderInput("slider2", "",
                       min = 0, max = 100, value = c(25, 75))
    ),
    
    column(3, 
           textInput("text", h3("Text input"), 
                     value = "Enter text..."))   
  )
  
)

# Define server logic
server <- function (input, output) {
  
}

# Run the app
shinyApp (ui = ui, server = server)

All controls and their code have been collected in the Shiny Widgets Gallery.


Reactive components

Placing widgets in the user interface is only the first of three steps to building a fully functional application. Values from the controls are stored in a special input list, from where they can be retrieved inside the server() function. There, they are used in functions from the render*() family, which generate charts, tables, images, etc. What the render*() functions return must be saved to a special output list and then used in functions from the *Output() family, which are placed inside ui(), most often in the main application panel.


Shiny application structure diagram
Shiny application structure diagram


Let’s carefully follow all the steps that need to be taken to create a working Shiny application.

Step 1: Place widgets in the UI.

This step was discussed above in the section Widgets.

Step 2: In the server function, add rendering functions that will return output for responsive components.

An example implementation of this step might look like this:

server <- function(input, output) {
  
  output$selected_var <- renderText({ 
    paste("You have selected", input$var)
  })
  
}

In the above example, the value from the var control is used inside the renderText function, and the created object is added to the output list under the name selected_var. Shiny will automatically make the object reactive if it uses a value stored in the input list. Whenever the user changes the value of the control, Shiny will call the rendering function to update the object. Other rendering functions:

Rendering function Returned object
renderDataTable DataTable
renderImage Image
renderPlot Plot
renderPrint Content to print or save to a file
renderTable Data frame, matrix, other tabular structures
renderText Text
renderUI Shiny or HTML tag
Step 3: Place responsive components in the UI.

Referring to the example from the previous step, we can add the textOutput() function to the main panel:

mainPanel (
      textOutput ("selected_var")
)

Each *Output() function takes a single string argument, the name of the reactive element given inside the server() function. In the example above, it is selected_var. List of all functions from the *Output() family:

Function Displayed object
dataTableOutput DataTable
imageOutput Image
plotOutput Plot
htmlOutput Raw HTML code
tableOutput Data frame, matrix, other tabular structures
textOutput Text
verbatimTextOutput Text

Sample application containing the above code snippets:

library(shiny)

ui <- fluidPage(
  titlePanel("censusVis"),
  
  sidebarLayout(
    sidebarPanel(
      helpText("Create demographic maps with 
                information from the 2010 US Census."),
      
      selectInput("var", 
                  label = "Choose a variable to display",
                  choices = c("Percent White", 
                              "Percent Black",
                              "Percent Hispanic", 
                              "Percent Asian"),
                  selected = "Percent White"),
      
      sliderInput("range", 
                  label = "Range of interest:",
                  min = 0, max = 100, value = c(0, 100))
    ),
    
    mainPanel(
      textOutput("selected_var"),
      textOutput("min_max")
    )
  )
)

server <- function(input, output) {
  
  output$selected_var <- renderText({ 
    paste("You have selected", input$var)
  })
  
  output$min_max <- renderText({ 
    paste("You have chosen a range that goes from",
          input$range[1], "to", input$range[2])
  })
  
}

shinyApp(ui, server)


Scripts, packages and data sets

Other R packages, built-in or external data sets, or finally, our functions are often used to build applications. It is crucial where in the app.R script we place the code responsible for this. Three places in the code differ significantly in execution frequency:

Therefore, the best place to place code to be executed only once is at the beginning of the app.R script, preferably before the ui() function. Additionally, suppose we use multiple proprietary functions to avoid unnecessarily lengthening the app.R script. In that case, placing them in an external script in the same folder as app.R is best. We run such an additional script with the source() command.

source ("helpers.R")

If the application needs to read data from a file, placing it in the data subfolder is best.

counties <- readRDS ("data/counties.rds")

We enable additional packages in the normal way, preferably immediately after calling the shiny package. This is what the app.R script looks like using additional packages, proprietary functions, and an external data set.

library(shiny)
library(maps)
library(mapproj)
source("helpers.R")
counties <- readRDS("data/counties.rds")

ui <- fluidPage(
  titlePanel("censusVis"),
  
  sidebarLayout (
   
    sidebarPanel (
      
      helpText ("Create demographic maps with 
                information from the 2010 US Census."
                ),
      
      selectInput ("var", 
                   label = "Choose a variable to display",
                   choices = c("Percent White", "Percent Black",
                              "Percent Hispanic", "Percent Asian"),
                   selected = "Percent White"),
      
      sliderInput ("range", 
                   label = "Range of interest:",
                   min = 0, max = 100, value = c(0, 100))
      ),
    mainPanel (
      plotOutput ("map")
    )
  )
)

server <- function(input, output) {
  
  output$map <- renderPlot({
    
    data <- switch (input$var, 
                    "Percent White" = counties$white,
                    "Percent Black" = counties$black,
                    "Percent Hispanic" = counties$hispanic,
                    "Percent Asian" = counties$asian)
    
    color <- switch (input$var, 
                     "Percent White" = "darkgreen",
                     "Percent Black" = "black",
                     "Percent Hispanic" = "darkorange",
                     "Percent Asian" = "darkviolet")
    
    legend <- switch (input$var, 
                      "Percent White" = "% White",
                      "Percent Black" = "% Black",
                      "Percent Hispanic" = "% Hispanic",
                      "Percent Asian" = "% Asian")
    
    percent_map (var = data,
                 color = color,
                 legend.title = legend,
                 max = input$range[2],
                 min = input$range[1])
  })
    
}

shinyApp (ui = ui, server = server)

The switch() function can easily convert the output value from a control to anything else. Here, it turns a text into a vector from a data frame, color, or other text.


Reactive expressions

Shiny applications respond quickly to user commands, but what if the application sometimes has to perform many tedious calculations? Using reactive expressions, we can precisely control which elements of the application should be updated at a given moment, thus avoiding unnecessary calculations that slow down the application. An example is the application below, located in the stockVis folder.

# Load packages ----
library(shiny)
library(quantmod)

# Source helpers ----
source("helpers.R")

# User interface ----
ui <- fluidPage(
  titlePanel("stockVis"),

  sidebarLayout(
    sidebarPanel(
      helpText("Select a stock to examine.

        Information will be collected from Yahoo finance."),
      textInput("symb", "Symbol", "SPY"),

      dateRangeInput("dates",
                     "Date range",
                     start = "2013-01-01",
                     end = as.character(Sys.Date())),

      br(),
      br(),

      checkboxInput("log", "Plot y axis on log scale",
                    value = FALSE),

      checkboxInput("adjust",
                    "Adjust prices for inflation", value = FALSE)
    ),

    mainPanel(plotOutput("plot"))
  )
)

# Server logic
server <- function(input, output) {

  output$plot <- renderPlot({
    data <- getSymbols(input$symb, src = "yahoo",
                       from = input$dates[1],
                       to = input$dates[2],
                       auto.assign = FALSE)

    chartSeries(data, theme = chartTheme("white"),
                type = "line", log.scale = input$log, TA = NULL)
  })

}

# Run the app
shinyApp(ui, server)


The application in the form above is not fully functional. In particular, the Adjust prices for inflation checkbox does not work. The application relies mainly on two functions from the quantmod package:

An adjust() function in the helpers.R script corrects the data for inflation.

if (!exists(".inflation")) {
  .inflation <- getSymbols('CPIAUCNS', src = 'FRED', 
     auto.assign = FALSE)
}  

# adjusts Google finance data with the monthly consumer price index 
# values provided by the Federal Reserve of St. Louis
# historical prices are returned in present values 
adjust <- function(data) {

      latestcpi <- last(.inflation)[[1]]
      inf.latest <- time(last(.inflation))
      months <- split(data)               
      
      adjust_month <- function(month) {               
        date <- substr(min(time(month[1]), inf.latest), 1, 7)
        coredata(month) * latestcpi / .inflation[date][[1]]
      }
      
      adjs <- lapply(months, adjust_month)
      adj <- do.call("rbind", adjs)
      axts <- xts(adj, order.by = time(data))
      axts[ , 5] <- Vo(data)
      axts
}

However, there is an optimization problem here. Each time the user checks the Plot y axis on log scale checkbox, all contents of the renderPlot() function must be executed, meaning the data is re-downloaded. This is unnecessary because the user did not want new data but only wanted to change the presentation of already downloaded data. The answer to this problem is the reactive() function, which, similarly to functions from the render*() family, will be executed every time the user changes the value of the control. We can place any expression or multiple expressions inside the reactive() function. What reactive() returns is called a reactive expression and is executed in the same way as a regular function. This is what the improved server() function will look like for the above example:

server <- function(input, output) {

  dataInput <- reactive({
    getSymbols(input$symb, src = "yahoo",
      from = input$dates[1],
      to = input$dates[2],
      auto.assign = FALSE)
  })

  output$plot <- renderPlot({    
    chartSeries(dataInput(), theme = chartTheme("white"),
    type = "line", log.scale = input$log, TA = NULL)
  })
}

It may seem that the above change won’t do much because the dataInput expression is called every time inside renderPlot. Does this mean that the data will be downloaded every time anyway? No, because a reactive expression is smarter than a function and remembers the last value returned. He also knows whether its value is still valid. Regarding aesthetic changes to the chart, the dataInput expression does not change its value and will return its last value without executing any code. What if we change the value of input$symp, used in the reactive expression but not in the rendering function? Will Shiny know that after changing this control, not only dataInput but also renderPlot should be called? Yes, it will be. Shiny knows which values in the output list depend on which reactive expressions and when to update them.

Now, let’s try to fix the Adjust prices for inflation checkbox. A possible solution is below, but it has a drawback.

server <- function (input, output) {

  dataInput <- reactive({
    getSymbols(input$symb, src = "yahoo",
        from = input$dates[1],
        to = input$dates[2],
        auto.assign = FALSE)
  })

  output$plot <- renderPlot({   
    data <- dataInput()
    if (input$adjust) data <- adjust(dataInput())

    chartSeries(data, theme = chartTheme("white"),
        type = "line", log.scale = input$log, TA = NULL)
  })
}

Calling the adjust() function inside renderPlot() will cause this function to execute every time the user changes the y-axis to logarithmic. Again, this can be avoided by using a regular expression.

server <- function(input, output) {

  dataInput <- reactive({
    getSymbols(input$symb, src = "yahoo",
               from = input$dates[1],
               to = input$dates[2],
               auto.assign = FALSE)
  })
  
  finalInput <- reactive({
    if (!input$adjust) return(dataInput())
    adjust(dataInput())
  })

  output$plot <- renderPlot({

    chartSeries(finalInput(), theme = chartTheme("white"),
                type = "line", log.scale = input$log, TA = NULL)
  })
}


Application sharing

After writing the application, we would naturally like to present it to the world. If the application users have R and Shiny installed, pack the archive with the script and additional files (for example, the www or data folder) and send it the standard way. After unpacking, the application will be immediately ready to run, like when working on it, i.e., with the runApp (path_to_dir) command or the button in RStudio. Additionally, R has three built-in commands that make it easy to run applications directly from the hosting server:

runGist ("eb3470beb1c0252bd0289cbc89bcf36f")

Most often, however, users who want to use our application do not have R and Shiny installed. In this case, we must make our application available as a website. We can do this in one of three ways:


Instructions for sharing the application on shinyapps.io

  1. Install the rsconnect package.
install.packages ("rsconnect")
  1. Create an account on the website shinyapps.io. It is possible to use a Google or GitHub account.

  2. Configure rsconnect. This process takes place in two steps:

  • You need to generate a token and the so-called secret on shinyapps.io. To do this, click on the menu in the upper right corner and select Tokens.
  • In the R console, use the setAccountInfo function to connect rsconnect to your shinyapps.io account.
rsconnect::setAccountInfo(name="<ACCOUNT>", token="<TOKEN>", secret="<SECRET>")
  • If you use RStudio, you can perform the second configuration step from the Tools -> Global Options -> Publishing menu.
  1. Send the finished application to the server using the deployApp() function. Alternatively, you can use the Publish button in RStudio in the upper right corner of the running application. After sending the application, your browser should automatically open a window with the application running on the server.
rsconnect::deployApp()

For more information on working with shinyapps.io, please refer to the user manual.


Advanced course

Visit https://shiny.rstudio.com/tutorial for further steps in improving your application writing skills in Shiny. The intermediate course can be found here. In addition to the course, the website includes a link to a free book, videos on YouTube and additional workshops conducted by the most renowned Shiny developers.