Inputs, Outputs, and Layouts

Author

Mark Andrews

Abstract

This guide surveys the principal input widget types and output elements available in Shiny and shows how to combine them in a coherent layout. We cover sliders, select boxes, radio buttons, checkboxes, and text inputs on the input side, and plots, tables, and formatted text on the output side. We then demonstrate how to organise multi-element applications using sidebar layouts and panel structures.

Input widget types

Shiny provides a family of input functions that each produce a different kind of control in the browser. The ID argument is always first and must be unique within the application. The label argument is the text shown to the user.

Each example below is a complete application you can copy and run.

Slider. sliderInput produces a draggable slider for numeric input. min, max, and value set the range and the starting position. step controls the increment.

ui <- fluidPage(
  sliderInput("alpha", "Significance level",
              min = 0.01, max = 0.20, value = 0.05, step = 0.01),
  verbatimTextOutput("val")
)

server <- function(input, output) {
  output$val <- renderPrint(input$alpha)
}

shinyApp(ui, server)

Select box. selectInput produces a dropdown menu. The choices argument takes a named character vector. The names are shown in the browser and the values are what the server receives.

ui <- fluidPage(
  selectInput("dist", "Distribution",
              choices = c("Normal"      = "norm",
                          "Exponential" = "exp",
                          "Uniform"     = "unif")),
  plotOutput("hist")
)

server <- function(input, output) {
  output$hist <- renderPlot({
    x <- switch(input$dist,
                norm = rnorm(500),
                exp  = rexp(500),
                unif = runif(500))
    hist(x, main = input$dist, col = "steelblue", border = "white")
  })
}

shinyApp(ui, server)

Radio buttons. radioButtons presents a set of mutually exclusive options as visible buttons. Useful when the number of choices is small and you want all options visible at once.

ui <- fluidPage(
  radioButtons("colour", "Point colour",
               choices = c("Blue" = "steelblue",
                           "Red"  = "firebrick",
                           "Grey" = "grey40")),
  plotOutput("plot")
)

server <- function(input, output) {
  output$plot <- renderPlot({
    plot(rnorm(100), rnorm(100),
         pch = 16, col = input$colour,
         xlab = "x", ylab = "y")
  })
}

shinyApp(ui, server)

Checkbox group. checkboxGroupInput allows multiple selections simultaneously. The input value is a character vector of whichever choices are currently ticked.

ui <- fluidPage(
  checkboxGroupInput("vars", "Variables to show",
                     choices  = c("mpg", "hp", "wt", "disp"),
                     selected = c("mpg", "hp")),
  tableOutput("tbl")
)

server <- function(input, output) {
  output$tbl <- renderTable({
    req(input$vars)
    head(mtcars[, input$vars, drop = FALSE])
  })
}

shinyApp(ui, server)

Single checkbox. checkboxInput creates a single on/off toggle. The server receives TRUE when ticked and FALSE when not.

library(ggplot2)

ui <- fluidPage(
  checkboxInput("line", "Show trend line", value = FALSE),
  plotOutput("plot")
)

server <- function(input, output) {
  output$plot <- renderPlot({
    p <- ggplot(mtcars, aes(x = wt, y = mpg)) +
      geom_point(colour = "steelblue") +
      theme_minimal()
    if (input$line) {
      p <- p + geom_smooth(method = "lm", se = FALSE, colour = "firebrick")
    }
    p
  })
}

shinyApp(ui, server)

Text and numeric input. textInput accepts any free text. numericInput is for numbers and provides up/down arrow controls.

ui <- fluidPage(
  textInput("title", "Plot title", value = "Random scatter"),
  numericInput("n", "Number of points", value = 50, min = 1, max = 500),
  plotOutput("plot")
)

server <- function(input, output) {
  output$plot <- renderPlot({
    plot(rnorm(input$n), rnorm(input$n),
         main = input$title,
         pch = 16, col = "steelblue")
  })
}

shinyApp(ui, server)

Action button. actionButton does not have a reactive value in the usual sense. It produces a counter that increments each time the button is clicked. Combine it with observeEvent or eventReactive to trigger computations only when the user explicitly requests them rather than on every keystroke.

ui <- fluidPage(
  actionButton("go", "Generate new sample"),
  plotOutput("plot")
)

server <- function(input, output) {
  output$plot <- renderPlot({
    input$go        # take a dependency on the button
    hist(rnorm(200), col = "steelblue", border = "white",
         main = paste("Sample", input$go))
  })
}

shinyApp(ui, server)

Output element types

Output functions create placeholders in the UI that the server fills with rendered content. Each output function has a corresponding render* function in the server.

UI function Server function Produces
plotOutput renderPlot A ggplot or base plot
tableOutput renderTable An HTML table
dataTableOutput renderDataTable A sortable DT table
verbatimTextOutput renderPrint Pre-formatted text
textOutput renderText Inline text
uiOutput renderUI Dynamic UI elements

The ID used in plotOutput("myplot") must match the name used when assigning in the server: output$myplot <- renderPlot({...}). The naming pattern is systematic: the UI function is always fooOutput and the server function is always renderFoo. Every output type follows this convention without exception.

Here is a working example of each type.

plotOutput and renderPlot produce a ggplot or base R plot, as seen in the previous guide.

tableOutput and renderTable render a plain HTML table.

ui <- fluidPage(
  sliderInput("rows", "Number of rows", min = 1, max = 32, value = 6),
  tableOutput("tbl")
)

server <- function(input, output) {
  output$tbl <- renderTable(head(mtcars, input$rows))
}

shinyApp(ui, server)

dataTableOutput and renderDataTable render a sortable, paginated table.

ui <- fluidPage(
  dataTableOutput("tbl")
)

server <- function(input, output) {
  output$tbl <- renderDataTable(mtcars)
}

shinyApp(ui, server)

verbatimTextOutput and renderPrint render pre-formatted console-style text, suitable for summary(), print(), model output, and so on.

ui <- fluidPage(
  selectInput("var", "Variable", choices = names(mtcars)),
  verbatimTextOutput("summ")
)

server <- function(input, output) {
  output$summ <- renderPrint(summary(mtcars[[input$var]]))
}

shinyApp(ui, server)

textOutput and renderText render a short string of inline text, suitable for labels, captions, or any dynamic text that should flow inside a paragraph.

ui <- fluidPage(
  sliderInput("n", "Sample size", min = 10, max = 500, value = 100),
  p("The sample mean is:", textOutput("mean", inline = TRUE))
)

server <- function(input, output) {
  output$mean <- renderText(round(mean(rnorm(input$n)), 3))
}

shinyApp(ui, server)

uiOutput and renderUI render an entire UI fragment dynamically. Useful when the number or type of controls must change based on user input.

ui <- fluidPage(
  checkboxInput("show", "Show extra control", value = FALSE),
  uiOutput("extra"),
  verbatimTextOutput("result")
)

server <- function(input, output) {
  output$extra <- renderUI({
    if (input$show) {
      sliderInput("n", "Sample size", min = 10, max = 500, value = 100)
    }
  })

  output$result <- renderPrint({
    if (!is.null(input$n)) {
      summary(rnorm(input$n))
    } else {
      cat("Tick the checkbox to reveal the control.")
    }
  })
}

shinyApp(ui, server)

Organising multiple outputs

When you want several outputs displayed side by side, use fluidRow and column. Shiny uses a 12-unit grid: column(6, ...) takes half the width.

ui <- fluidPage(
  titlePanel("Scatter and summary"),
  sidebarLayout(
    sidebarPanel(
      sliderInput("n", "Points", min = 20, max = 500, value = 100),
      radioButtons("colour", "Colour",
                   choices = c("Blue" = "steelblue",
                               "Red"  = "firebrick"))
    ),
    mainPanel(
      fluidRow(
        column(8, plotOutput("scatter")),
        column(4, verbatimTextOutput("summary"))
      )
    )
  )
)

server <- function(input, output) {
  output$scatter <- renderPlot({
    df <- data.frame(x = rnorm(input$n), y = rnorm(input$n))
    ggplot(df, aes(x = x, y = y)) +
      geom_point(colour = input$colour, alpha = 0.6) +
      theme_minimal()
  })

  output$summary <- renderPrint({
    cat("x:\n")
    print(summary(rnorm(input$n)))
    cat("\ny:\n")
    print(summary(rnorm(input$n)))
  })
}

shinyApp(ui, server)

There is a significant problem with this application that is worth pausing on. The scatter plot is drawn from one call to rnorm, and the summary statistics are drawn from two further and completely separate calls to rnorm. The scatter and the summary are therefore describing three different data sets, not one. If you change n and watch both outputs update, they are not consistent with each other.

This is a real deficiency, not just a minor inefficiency. The solution is to generate the data once and share it between both outputs. Shiny’s mechanism for doing exactly that is a reactive expression, covered in the statistical applications guide.

Providing context with helpText and wellPanel

helpText() adds a small grey instruction paragraph inside the UI, useful for telling users what a control does. wellPanel() wraps a group of controls in a bordered box to indicate they are related.

ui <- fluidPage(
  titlePanel("Distribution explorer"),
  sidebarLayout(
    sidebarPanel(
      wellPanel(
        h4("Simulation"),
        sliderInput("n", "Sample size", min = 50, max = 2000, value = 500),
        helpText("Larger samples will take slightly longer to draw.")
      ),
      wellPanel(
        h4("Appearance"),
        radioButtons("colour", "Bar colour",
                     choices = c("Blue" = "steelblue",
                                 "Red"  = "firebrick",
                                 "Grey" = "grey50"))
      )
    ),
    mainPanel(
      plotOutput("hist")
    )
  )
)

server <- function(input, output) {
  output$hist <- renderPlot({
    x <- rnorm(input$n)
    hist(x, col = input$colour, border = "white",
         main = paste("n =", input$n), xlab = "x")
  })
}

shinyApp(ui, server)

These small additions improve usability considerably when applications grow beyond a handful of controls. wellPanel makes it visually clear which controls belong together. Its effect is purely presentational: it adds a lightly shaded background and a rounded border (using Bootstrap’s well style) but has no functional effect on reactivity or on how Shiny processes the controls inside it. helpText gives users guidance without requiring them to consult any documentation.