Continuing Education - DIME Analytics
Development Impact Evaluation (DECDI)
Thursday, the 29th of May, 2025
Welcome to this interactive Shiny session! In the next 90 minutes, we will:
UI + Serverui.R, server.R, global.R)This session is live at: 🔗 https://ce-wb-shiny.netlify.app
You can find the quarto presentation and the final solutions (both single-file and multiple-file apps) in our GitHub repository: 📦 https://github.com/dime-wb-trainings/shiny-training
Shiny is a web application framework for R that allows you to turn analyses into interactive web apps — all in R.
Why use it?
A Shiny app has two core components:
Apps are served to users via a host and port. The R session running the server reacts to user actions, computes results, and sends them back.
Client: The web browser where the user interacts with the app
Host:Port: Shiny app is served at an IP (host) and port
Server: Runs R session to monitor inputs and respond with outputs
Step-by-step instructions:
5. Choose Single File option when prompted:
6. Name your folder and click OK
7. Click Run App in the top-right corner
8. 🎉 You’re running your first Shiny app!
Go to the app you just created and let’s explore each element
ui — User InterfaceLayout elements
fluidPage() is the container for the app interface, the layout in which your content is. This is the most common, but there are other types of layouts. Check hereGo to the app you just created and let’s explore each element
ui — User InterfaceLayout elements
sidebarLayout() splits the layout into sidebar (sidebarPanel()) and main area (mainPanel()). This is also optional.Go to the app you just created and let’s explore each element
ui — User InterfacePage content
sidebarPanel() contains one input field (sliderInput()) called “bins”. This is a slider that lets a user choose a number. As you can infer from the name, in this case, it’s the number of histogram bins.Go to the app you just created and let’s explore each element
ui — User InterfacePage content
mainPanel() contains a histogram (plotOutput()), which will be defined in the server function. The name, or id, of this histogram is “distPlot”server — Server LogicThe server() function takes two arguments:
input: a reactive list of input values from the UIoutput: a reactive list where you assign render functionsReactive lists are “special” lists used in reactive programming — a way to make your app automatically update outputs when inputs change.
server — Server LogicLet’s take a look at reactivity inside a simple server( ):
renderFunction: A function like renderPlot(), renderTable(), etc. used to render an output (a plot, a table…)outputId: Identifies the rendered output in the output list (output$) for the UIvalue every time the input field referenced by inputID in the input list changes.server — Server Logicserver <- function(input, output) {
output$distPlot <- renderPlot({
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = 'darkgray', border = 'white',
xlab = 'Waiting time to next eruption (in mins)',
main = 'Histogram of waiting times')
})
}In our case:
the server contains the logic to create the histogram distPlot in the output list (output$), using the render function renderPlot().
distPlot depends on one user input (input$bins), which pulls the number from the slider input in the UI.
➡️️ Result: the histogram updates as the slider moves!
Remember these connections?
| UI | Server |
|---|---|
plotOutput("distPlot") |
output$distPlot <- renderPlot() |
sliderInput("bins", ...) |
input$bins |
Shiny apps are built by connecting inputs (from the UI) to outputs (rendered in the server).
| Part | Role | Examples |
|---|---|---|
ui |
Define layout and inputs/outputs | sliderInput(), plotOutput() |
server |
Logic to render outputs based on inputs | renderPlot(), renderText() |
🔁 Reactivity connects them:
input$... pulls values from UI controlsoutput$... <- render...() generates dynamic contentinput and output behave like reactive lists — not regular R lists, but special objects in Shiny.input$bins = 5.input list — and any render function using it will re-execute.output$distPlot <- renderPlot({ ... input$bins ... }) updates instantly.Together, input, output, and render*() functions form the reactive backbone of your app.
Shiny includes many built-in widgets to capture user input:
| Widget | Purpose | Example Use |
|---|---|---|
numericInput() |
Enter a number | Age, price |
sliderInput() |
Select from a range | Histogram bins |
selectInput() |
Choose from a list | Country selector |
radioButtons() |
Choose one option | Plot type |
textInput() |
Enter text | Comments, filters |
fileInput() |
Upload a file | CSV, Excel |
actionButton() |
Trigger an action manually | Run, Submit |
📚 See the full gallery: ➡️ Shiny Widgets Gallery
#ca8dfd (a shade of purple)After your modifications the app should look like this:
Before you close the app, check the R console. You’ll see something like:
🔍 What it means: - 127.0.0.1 refers to your local machine (“localhost”) - 3827 is a random port number - You can open the app in any browser using this address
⛔ While the app is running: - The R console is blocked (no new commands allowed) - A stop sign appears in the RStudio toolbar
🛑 To stop the app: - Click the stop sign icon - Press Esc (or Ctrl + C in terminal) - Close the Shiny app window
As your app grows, managing everything in a single file becomes difficult. That’s why it’s a good idea to switch to a multi-file structure — this is the recommended approach.
Let’s walk through how to set it up!
In RStudio, go to
File > New File > Shiny Web App…
This time, choose “Multiple File” when prompted:
3. Name your project folder and click OK. This will automatically create two files.
4. Lastly let’s create an extra file global.R. (Optional but recommended) This file is useful for loading packages and defining global objects or functions used by both ui.R and server.R.
5. Click the Run App button in the top-right corner of RStudio.
You’ve set up a multiple-file Shiny app—great start! Now let’s customize it together.
We’ll go through a series of hands-on exercises using the faithful dataset to:
After each exercise, we’ll do a live walkthrough to see how the changes integrate into the app.
📝 Note: While we’re using the files created by RStudio as a starting point, you’re not limited to that setup. You can always:
.R scripts as ui.R and server.Rapp.R file if you prefer that styleLet’s improve the layout and presentation of your app!
In ui.R, replace the titlePanel() with:
h3()Let’s make the histogram more interactive!
Add a selectInput() to the sidebarPanel() so users can choose a color for the histogram
Then use input$color inside renderPlot() to apply the color.
UI:
Server:
See that if I don’t add the input$color in the server inside the hist() function, the color will not change.
Ok! now let’s make this more challenging! Let’s give the user control over the type of plot they see!
Add a radioButtons() input to let the user choose between:
"Histogram" of waiting times"Density of eruption duration vs. waiting timeModify renderPlot() in server.R to change behavior based on selection
UI:
In server.R, check the value of input$plot_type to decide which plot to draw.
Full server logic:
server.Rfunction(input, output, session) {
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
if (input$plot_type == "Histogram") {
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = input$color, border = 'white',
xlab = 'Waiting time to next eruption (in mins)',
main = 'Histogram of waiting times')
} else if (input$plot_type == "Density")
{
ggplot(faithful, aes(x=x)) +
geom_density(alpha = 0.5, color = input$color) +
labs(x = 'Waiting time to next eruption (in mins)',
title = 'Density Plot of Waiting Times') +
theme_minimal()
}
})
}It’s always good practice to explain what your app does. For this, we can create an intro tab — like a README page — that gives your users helpful context.
Add a tabsetPanel() inside the mainPanel().
Create two tabs:
In the Intro tab, write a short description of what the app does (in plain text or with HTML).
Here’s how your ui.R could look after adding the tabs:
# Define UI for application that draws a histogram
navbarPage("Faithful Geyser Data - Customized",
tabPanel("Introduction",
h3("Exploring the Faithful Geyser Data"),
p("This application allows you to visualize the waiting times between eruptions of the Old Faithful geyser.
You can choose between a histogram and a density plot, adjust the number of bins, and select a color for the plot.")
),
tabPanel("Plots",
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
selectInput("color", "Choose a color:", choices = c("turquoise", "plum", "orchid")),
radioButtons("plot_type", "Choose a plot type:",
choices = c("Histogram", "Density"))
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
))Want your app to look more polished? Shiny supports easy theming with the {bslib} package.
👉 Your task: Add a custom theme to your app!
Load the bslib package in your global.R file:
If you don’t have it installed, run:
Wrap your navbarPage() in a thematic Bootstrap theme ui.R file:
Save and re-run your app!
"flatly" (clean + modern)"darkly" (dark mode)"minty" (playful + bright)"journal" (serif style)Let’s allow users to download the dataset they are exploring!
downloadButton() to the UI so users can download the data.server.R, define a downloadHandler() to write the faithful dataset as a CSV file.downloadButton() in the ui and downloadHandler() in the server.ui.Rserver.Routput$download_data <- downloadHandler(
filename = function() { "faithful_data.csv" },
content = function(file) {
write.csv(faithful, file, row.names = FALSE)
}
)Let’s add a new tab to display the faithful dataset as a table.
ui.R called “Table”.tableOutput("data_table") to display the dataset.server.R, create a new output called data_table that renders the faithful dataset as a table.renderTable() in the server to display the dataset.ui.RtabPanel("Table",
h2("Faithful Geyser Data Table"),
p("Below is the table of the Old Faithful geyser data. You can view the waiting times between eruptions."),
tableOutput("data_table"))server.R{DT} or {reactable} to make it look better.For both:
Push-button publishing from RStudio or publish directly from GitHub
Request Posit Connect access as a Software Request
Want to go further with Shiny? Here are some helpful resources:
🧪 Shiny Tutorial (Official Getting Started Guide) here
📘 Mastering Shiny by Hadley Wickham (Free online book) here
💡 Shiny Widgets Gallery here
🧩 Awesome Shiny Extensions (Community plugins) here
🌐 Shiny Community (Forums, discussions) here
📦 Building Web Applications (Training) here
🧱 Adding multiple objects in layout here
Shiny App Gallery here
California Schools Climate Hazards Dashboard here
New Zealand Trade Intelligence Dashboard here
Locating Blood Banks in India here
Understanding voters’ profile in Brazilian elections here
DIME theme for Quarto Presentations. Code available on GitHub.