library(shiny)
ui <- fluidPage(
p("Hello, world!")
)
server <- function(input, output) {}
shinyApp(ui, server)Introduction to Shiny
This guide introduces the Shiny framework for building interactive web applications in R. We examine the three-part structure of every Shiny application, the reactive programming model that automatically propagates changes from inputs to outputs, and the basic patterns for connecting interface elements to R computations. By the end you will be able to build simple interactive graphics with slider and selection controls and understand why Shiny code is structured as nested function calls rather than a linear script.
A first application
Before examining the structure in detail, run the smallest possible Shiny application. Copy this into a new R script and run it.
A browser window opens showing a page with the text “Hello, world!”. Three things happened: a UI was defined, a server function was provided (empty, because this application does nothing interactive), and shinyApp bound them together and started a local web server. That three-part pattern is present in every Shiny application, however complex.
The three-part structure
Every Shiny application has three components: a user interface (UI) definition, a server function, and a call to shinyApp() that binds them together. The UI definition describes the layout and controls that the user sees in the browser. The server function contains the R code that processes inputs and produces outputs. shinyApp() creates the application object and, when run interactively, starts a local web server so the browser can display it.
The minimal application looks like this:
library(shiny)
ui <- fluidPage(
sliderInput("n", "Number of points", min = 10, max = 500, value = 100),
plotOutput("scatter")
)
server <- function(input, output) {
output$scatter <- renderPlot({
x <- rnorm(input$n)
y <- rnorm(input$n)
plot(x, y)
})
}
shinyApp(ui, server)Run this in RStudio by clicking “Run App” or by calling shinyApp(ui, server) in the console. A browser window or RStudio viewer pane opens showing a slider above a scatter plot. Moving the slider immediately redraws the plot — this automatic propagation is the reactive programming model.
Breaking down the minimal application
Here is the same code again, with a brief comment on each line:
library(shiny)
# The UI describes the page layout shown in the browser
ui <- fluidPage( # responsive page container
sliderInput("n", # unique string ID
"Number of points", # label shown to the user
min = 10, max = 500, value = 100), # range and starting value
plotOutput("scatter") # placeholder for the plot output
)
# The server contains the R computations
server <- function(input, output) {
output$scatter <- renderPlot({ # fills the "scatter" placeholder
x <- rnorm(input$n) # reads the current slider value
y <- rnorm(input$n)
plot(x, y) # ordinary R code
})
}
shinyApp(ui, server) # bind UI and server; start the appWhat each part does:
fluidPage creates a responsive HTML page that stretches to fill the browser window. Every argument passed to it becomes a child element of that page. Most Shiny applications use fluidPage as their outermost container.
sliderInput("n", ...) creates a draggable slider in the browser. The first argument is the ID, an arbitrary string that must be unique within the application. Shiny uses it as the bridge between browser and server: whatever value the slider holds is available in the server as input$n. The min, max, and value arguments set the range and the starting position.
plotOutput("scatter") creates an empty placeholder in the page. It does nothing by itself. The server fills it by assigning to output$scatter, and Shiny matches them by the shared ID "scatter".
server <- function(input, output) is the server function. Shiny calls it once when the application starts. input is a list-like object holding the current value of every input widget. output is where rendered results are assigned.
renderPlot({...}) wraps an R code block that produces a plot. Shiny records which input$ values the block reads (here, input$n) and re-runs the entire block whenever any of those values change. The R code inside is completely ordinary: you can call any function or use any package.
shinyApp(ui, server) binds the UI and server into a single application object. Run interactively, it starts a local web server and opens the application in the browser or RStudio viewer pane.
Inputs and outputs
Input functions create the controls users interact with. Each has a unique string ID as its first argument. sliderInput("n", ...) creates a slider whose current value is accessible in the server as input$n. Output functions create placeholders in the UI where results will appear. plotOutput("scatter") creates a placeholder that the server fills by assigning to output$scatter.
The server function must use a render* function that matches the output type. renderPlot({...}) evaluates its code block and produces a plot. Other common pairs are tableOutput / renderTable and verbatimTextOutput / renderPrint.
ui <- fluidPage(
sliderInput("n", "Sample size", min = 10, max = 500, value = 100),
verbatimTextOutput("summary")
)
server <- function(input, output) {
output$summary <- renderPrint({
x <- rnorm(input$n)
summary(x)
})
}
shinyApp(ui, server)Why reactive programming differs from scripts
In an ordinary R script, commands execute top to bottom once. In Shiny, the server function never executes from top to bottom in the normal sense. Instead, Shiny builds a reactive graph: each render* expression records which input$ values it reads, and Shiny re-executes that expression whenever any of those inputs change.
This means you do not write loops or callbacks. You write expressions that describe their outputs in terms of their inputs, and Shiny handles the rest. The consequence is that Shiny code often looks unusual at first: there are no explicit calls to redraw, no event handlers in the traditional sense, just expressions that declare dependencies.
A first interactive plot with ggplot2
Here is a slightly more realistic example that uses ggplot2 inside a reactive context. The key point is that input$bins is simply read inside the renderPlot block like any other R variable.
ui <- fluidPage(
sliderInput("bins", "Number of bins", min = 5, max = 50, value = 20),
plotOutput("hist")
)
server <- function(input, output) {
output$hist <- renderPlot({
df <- data.frame(x = rnorm(500))
ggplot(df, aes(x = x)) +
geom_histogram(bins = input$bins, fill = "steelblue", colour = "white") +
theme_minimal()
})
}
shinyApp(ui, server)Adjust the slider and notice that Shiny re-runs the entire code block inside renderPlot on each change. For this simple example that is fine. Later we will see how to avoid redundant computation when outputs share the same data.
Building up to multiple inputs
A single application can have as many input controls and output elements as needed. Each additional input creates a new reactive dependency for any output that reads it.
ui <- fluidPage(
sliderInput("n", "Sample size", min = 50, max = 1000, value = 200),
sliderInput("mean", "Mean", min = -5, max = 5, value = 0, step = 0.5),
sliderInput("sd", "SD", min = 0.1, max = 5, value = 1, step = 0.1),
plotOutput("hist")
)
server <- function(input, output) {
output$hist <- renderPlot({
x <- rnorm(input$n, mean = input$mean, sd = input$sd)
df <- data.frame(x = x)
ggplot(df, aes(x = x)) +
geom_histogram(bins = 30, fill = "steelblue", colour = "white") +
labs(title = paste("n =", input$n)) +
theme_minimal()
})
}
shinyApp(ui, server)Changing any of the three sliders triggers a redraw. The renderPlot block reads input$n, input$mean, and input$sd, so it depends on all three.