Interactive Web Apps with Shiny
Prerequisites: Data visualization with ggplot2, Functions
Shiny turns R code into interactive web apps — dashboards, data explorers, calculators — without writing HTML, CSS, or JavaScript yourself.
install.packages("shiny")
A complete app
Save this as app.R and click Run App in RStudio:
library(shiny)
library(ggplot2)
ui <- fluidPage(
titlePanel("Fuel economy explorer"),
sidebarLayout(
sidebarPanel(
selectInput("class", "Car class", choices = sort(unique(mpg$class))),
sliderInput("year", "Model year", min = 1999, max = 2008, value = c(1999, 2008), sep = "")
),
mainPanel(
plotOutput("scatter"),
textOutput("summary")
)
)
)
server <- function(input, output, session) {
filtered <- reactive({
subset(mpg, class == input$class & year >= input$year[1] & year <= input$year[2])
})
output$scatter <- renderPlot({
ggplot(filtered(), aes(displ, hwy)) +
geom_point(size = 3, alpha = 0.7) +
labs(x = "Engine size (L)", y = "Highway MPG")
})
output$summary <- renderText({
sprintf("%d cars, average %.1f highway MPG", nrow(filtered()), mean(filtered()$hwy))
})
}
shinyApp(ui, server)
How it works
- The UI describes the page: inputs (
selectInput,sliderInput) and placeholders for outputs (plotOutput,textOutput). - The server function computes outputs.
input$classreads the current value of the input with id"class". render*()functions create outputs; each is assigned tooutput$<id>matching a placeholder in the UI.- Reactivity: Shiny tracks which inputs each output uses, and reruns
only what's affected when an input changes.
reactive()creates a shared, cached calculation — here, the filtered data used by both the plot and the text.
Growing an app
- Layouts and themes — the bslib package provides modern layouts
(
page_sidebar, cards, value boxes) and Bootstrap themes. - Modules — split a large app into reusable components, each with its own UI and server functions.
- Interactivity —
DTfor sortable tables;plotlyorggiraphfor interactive charts. - Testing —
shinytest2records and replays app sessions; keep data logic in plain functions tested with testthat.
Deploying
Publish to Posit Connect Cloud or shinyapps.io directly from RStudio, run your own Shiny Server or Posit Connect, or package the app in a Docker container. Shinylive can even run some apps entirely in the browser, with no server.
Common mistakes
- Doing heavy work (reading big files, fitting models) inside a
render*()function that reruns on every change. Load data once, at the top of the app, and cache expensive steps withreactive(). - Forgetting that outputs must use the same id in the UI and the server.