Shiny is an R package that makes it easy to build interactive web
apps directly from R.
It bridges the gap between statistical computing and user-friendly
interfaces, enabling data scientists to create dynamic dashboards and
applications without needing HTML, CSS, or JavaScript knowledge.
You can learn more at the official Shiny website.
Every Shiny app has two main parts:
library(shiny)
# Define UI
ui <- fluidPage(
titlePanel("Hello Shiny!"),
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 1, max = 1000, value = 500)
),
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic
server <- function(input, output) {
output$distPlot <- renderPlot({
hist(rnorm(input$obs))
})
}
# Run the app
shinyApp(ui = ui, server = server)Save this in an .R file and click Run App from RStudio, or run it inline using RMarkdown with:
shinyApp(ui = ui, server = server)runExample("01_hello") # a histogram\
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 apps are built around user inputs and rendered outputs. Users interact with the app through inputs like sliders, text boxes, and dropdowns β and in response, Shiny displays outputs like text, tables, or plots.
Shiny provides a variety of input functions. Here are some of the most commonly used:
sliderInput("obs", "Number of observations:", min = 1, max = 1000, value = 500)
textInput("caption", "Caption:", "My Plot")
selectInput("dataset", "Choose a dataset:", choices = c("rock", "pressure", "cars"))Each input has an ID (e.g., βobsβ, βcaptionβ) used to reference it in the server() function.
To display content dynamically, Shiny provides output functions:
textOutput("caption") # Displays reactive text
plotOutput("distPlot") # Displays a reactive plot
tableOutput("dataTable") # Displays a static tableThese are placed in the UI and linked to corresponding render
functions in the server.
The magic of Shiny is its reactive programming model. When an input changes, outputs that depend on it are automatically updated.
output$caption <- renderText({
input$caption
})
output$distPlot <- renderPlot({
hist(rnorm(input$obs))
})These render functions:
Start with render*() (e.g., renderText, renderPlot)
Are paired with output elements like textOutput() or plotOutput()
You can use reactive() to store a block of code that is used in multiple places:
data <- reactive({
rnorm(input$obs)
})
output$distPlot <- renderPlot({
hist(data())
})This way, if you need to reuse the same logic across outputs, you write it only once.
library(shiny)
ui <- fluidPage(
titlePanel("Reactive Inputs and Outputs"),
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", min = 1, max = 1000, value = 500),
textInput("caption", "Plot title:", "My Histogram")
),
mainPanel(
textOutput("caption"),
plotOutput("distPlot")
)
)
)
server <- function(input, output) {
output$caption <- renderText({ input$caption })
output$distPlot <- renderPlot({
hist(rnorm(input$obs), main = input$caption)
})
}
shinyApp(ui = ui, server = server)Displaying tables and plots is one of the most common uses of Shiny apps. You can show both static tables and interactive plots, depending on the user needs.
Shiny supports two main ways to show tabular data:
tableOutput() + renderTable()For simple, static tables:
output$summary <- renderTable({
head(cars)
})In the UI:
tableOutput("summary")Using renderPlot() and plotOutput()
The most common way to show plots is with base R or ggplot2.
output$distPlot <- renderPlot({
hist(rnorm(input$obs))
})UI:
plotOutput("distPlot")You can also use ggplot2:
output$gg <- renderPlot({
library(ggplot2)
ggplot(mtcars, aes(x = mpg, y = wt)) +
geom_point()
})library(shiny)
library(DT)
library(ggplot2)
library(plotly)
ui <- fluidPage(
titlePanel("Tables and Plots"),
sidebarLayout(
sidebarPanel(
sliderInput("obs", "Number of observations:", 1, 1000, 500)
),
mainPanel(
tabsetPanel(
tabPanel("Table", DT::dataTableOutput("datatable")),
tabPanel("Plot", plotOutput("distPlot")),
tabPanel("Interactive Plot", plotly::plotlyOutput("interactivePlot"))
)
)
)
)
server <- function(input, output) {
output$datatable <- DT::renderDataTable({
head(cars, 10)
})
output$distPlot <- renderPlot({
hist(rnorm(input$obs))
})
output$interactivePlot <- plotly::renderPlotly({
plot_ly(data = mtcars, x = ~mpg, y = ~wt, type = "scatter", mode = "markers")
})
}
shinyApp(ui = ui, server = server)Shiny can be extended to handle spatial data using
packages like leaflet, sf, and
mapview. These tools allow users to explore geographic data
interactively β zoom, pan, click, and even draw on maps.
The leaflet package is the easiest way to display maps
in Shiny. It integrates smoothly with Shiny inputs and outputs.
ui <- fluidPage(
titlePanel("Simple Leaflet Map"),
leafletOutput("map")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addMarkers(lng = -81.6557, lat = 30.3322, popup = "Jacksonville, FL")
})
}Run the app
If youβre working with shapefiles or spatial objects (e.g., sf), load them and add them directly to the map.
library(sf)
nc <- st_read(system.file("shape/nc.shp", package="sf"))
leaflet(nc) %>%
addTiles() %>%
addPolygons()In Shiny:
output$map <- renderLeaflet({
leaflet(nc) %>%
addTiles() %>%
addPolygons(color = "blue", weight = 2)
})
You can dynamically filter features based on user input. For
example:
filtered_data <- reactive({
nc[nc$AREA > input$area_thresh, ]
})
output$map <- renderLeaflet({
leaflet(filtered_data()) %>%
addTiles() %>%
addPolygons()
})Use this with a sliderInput() to let users control which polygons are shown.
You can dynamically filter features based on user input. For example:
filtered_data <- reactive({
nc[nc$AREA > input$area_thresh, ]
})
output$map <- renderLeaflet({
leaflet(filtered_data()) %>%
addTiles() %>%
addPolygons()
})Use this with a sliderInput() to let users control which polygons are shown.
##Spatial Input: Map Clicks and Selections You can also capture user input on the map β clicks, bounds, or shapes.
observeEvent(input$map_click, {
click <- input$map_click
showModal(modalDialog(
title = "You clicked on the map!",
paste("Longitude:", click$lng, "<br>Latitude:", click$lat)
))
})This opens up a wide range of interactive GIS applications.
library(shiny)
library(leaflet)
ui <- fluidPage(
titlePanel("Spatial Data in Shiny"),
leafletOutput("map"),
verbatimTextOutput("click_info")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addMarkers(lng = -81.6557, lat = 30.3322, popup = "Jacksonville, FL")
})
output$click_info <- renderPrint({
if (is.null(input$map_click)) return("Click on the map")
paste("Latitude:", input$map_click$lat,
"Longitude:", input$map_click$lng)
})
}
shinyApp(ui, server)Spatio-temporal apps allow users to explore how geographic phenomena
evolve over time. In Shiny, this can be done using time sliders,
animated maps, and reactive filtering of time-stamped spatial
data.
Packages like leaflet, mapdeck,
gganimate, and shinyWidgets help bring these
visualizations to life.
You can use a sliderInput() with leaflet or
other spatial data to allow users to select a time window.
library(shiny)
library(leaflet)
# Sample data
years <- 2010:2020
locations <- data.frame(
year = rep(years, each = 1),
lat = runif(length(years), 25, 30),
lon = runif(length(years), -85, -80)
)
ui <- fluidPage(
titlePanel("Spatio-temporal Viewer"),
sliderInput("year", "Select Year:", min = 2010, max = 2020, value = 2015, step = 1, sep = ""),
leafletOutput("map")
)
server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>% addTiles()
})
observe({
year_data <- subset(locations, year == input$year)
leafletProxy("map") %>%
clearMarkers() %>%
addCircleMarkers(data = year_data, lng = ~lon, lat = ~lat, radius = 6, color = "red")
})
}
shinyApp(ui, server)As you build more complex Shiny apps, itβs helpful to follow best practices and learn from reliable, up-to-date resources.
modules for reusable UI/server componentsreactive() and
observe() carefully β avoid unnecessary
re-computationreactiveValues() for shared state
instead of global variablesshinycssloaders, shinybusy) to improve user
experienceprofvis() and
shiny::reactlog()