In this chapter, we’ll focus on the front end, and give you a whirlwind tour of the HTML inputs and outputs provided by Shiny. We’ll mostly stick to the inputs and outputs built into Shiny itself. However, there is a rich and vibrant community of extension packages, like
inputID: All input functions have the same first argument: inputId. This is the identifier used to connect the front end with the back end: if your UI has an input with ID "name", the server function will access it with input$name. The inputId has two constraints:
It must be a simple string that contains only letters, numbers, and underscores (no spaces, dashes, periods, or other special characters allowed!). Name it like you would name a variable in R.
It must be unique. If it’s not unique, you’ll have no way to refer to this control in your server function!
label: Most input functions have a second parameter called label. This is used to create a human-readable label for the control. Shiny doesn’t place any restrictions on this string.
value: The third parameter is typically value, which, where possible, lets you set the default value.
Remaining parameters are unique to the specific control.
When creating an input, I recommend supplying the inputId and label arguments by position, and all other arguments by name:
sliderInput("min", "Limit (minimum)", value = 50, min = 0, max = 100)
Note
The following sections describe the inputs built into Shiny, loosely grouped according to the type of control they create. The goal is to give you a rapid overview of your options, not to exhaustively describe all the arguments. Read the documentation to get the full details!
All passwordInput() does is hide what the user is typing, so that someone looking over their shoulder can’t read it. It’s up to you to make sure that any passwords are not accidentally exposed, so we don’t recommend using passwords unless you have had some training in secure programming.
If you want to ensure that the text has certain properties you can use shiny::validate(), which we’ll come back to in Chapter 8.
2.2.3 Numeric input
To collect numeric values, create a constrained text box with numericInput() or a slider with sliderInput(). If you supply a length-2 numeric vector for the default value of sliderInput(), you get a “range” slider with two ends.
library(shiny)ui<-fluidPage(numericInput("num", "Number one", value =0, min =0, max =100),sliderInput("num2", "Number two", value =50, min =0, max =100),sliderInput("rng", "Range", value =c(10, 20), min =0, max =100))server<-function(input, output, session){}shinyApp(ui, server)
Collect a single day with shiny::dateInput() or a range of two days with shiny::dateRangeInput(). These provide a convenient calendar picker, and additional arguments like datesdisabled and daysofweekdisabled allow you to restrict the set of valid inputs.
Date format, language, and the day on which the week starts defaults to US standards. If you are creating an app with an international audience, set format, language, and weekstart so that the dates are natural to your users.
Dropdowns created with shiny::selectInput() take up the same amount of space, regardless of the number of options, making them more suitable for longer options. You can also set multiple = TRUE to allow the user to select multiple elements.
If you have a very large set of possible options, you may want to use “server-side” shiny::selectInput() so that the complete set of possible options are not embedded in the UI (which can make it slow to load), but instead sent as needed by the server. You can learn more about this advanced topic at Server-side selectize.
2.2.5.4 Check boxes
There’s no way to select multiple values with radio buttons, but there’s an alternative that’s conceptually similar: shiny::checkboxGroupInput(). For a checkbox for a single yes/no question, use shiny::checkboxInput():
library(shiny)animals<-c("dog", "cat", "mouse", "bird", "other", "I hate animals")ui<-fluidPage(checkboxGroupInput("animal", "What animals do you like?", animals),checkboxInput("cleanup", "Clean up?", value =TRUE),checkboxInput("shutdown", "Shutdown?"))server<-function(input, output, session){}shinyApp(ui, server)
You can customize the appearance using the class argument by using one of "btn-primary", "btn-success", "btn-info", "btn-warning“, or "btn-danger". You can also change the size with "btn-lg”, "btn-sm", "btn-xs“. Finally, you can make buttons span the entire width of the element they are embedded within using "btn-block".
The class argument works by setting the class attribute of the underlying HTML, which affects how the element is styled. To see other options, you can read the documentation for Bootstrap, the CSS design system used by Shiny.
When space is at a premium, it’s useful to label text boxes using a placeholder that appears inside the text entry area. How do you call shiny::textInput() to generate the UI below?
Create a slider input to select values between 0 and 100 where the interval between each selectable value on the slider is 5. Then, add animation to the input widget so when the user presses play the input widget scrolls through the range automatically.
Solution 2.5. : Slider animation
Code
ui<-fluidPage(sliderInput("num","Press play button", min =0, max =100, value =0, step =5, animate =animationOptions( interval =1000, loop =TRUE, playButton ="Play", pauseButton ="Stop")))server<-function(input, output, session){}shinyApp(ui, server)
Solution 2.6. : Slider animation
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 150
ui <- fluidPage(
sliderInput(
"num",
"Press play button",
min = 0,
max = 100,
value = 0,
step = 5,
animate = animationOptions(
interval = 1000,
loop = TRUE,
playButton = "Play",
pauseButton = "Stop"
)
)
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
If you have a moderately long list in a shiny::selectInput(), it’s useful to create sub-headings that break the list up into pieces. Read the documentation to figure out how. (Hint: the underlying HTML is called <optgroup>.)
Outputs in the UI create placeholders that are later filled by the server function. Like inputs, outputs take a unique ID as their first argument: if your UI specification creates an output with ID "plot", you’ll access it in the server function with output$plot.
Each output function on the front end is coupled with a render function in the back end. There are three main types of output, corresponding to the three things you usually include in a report: text, tables, and plots. The following sections show you the basics of the output functions on the front end, along with the corresponding render functions in the back end.
Note that the {} are only required in render functions if you need to run multiple lines of code. As you’ll learn shortly, you should do as little computation in your render functions as possible, which means you can often omit them.
renderPrint() prints the result, as if you were in an R console, and is usually paired with verbatimTextOutput(). We can see the difference with a toy app:
dataTableOutput() and renderDataTable() render a dynamic table, showing a fixed number of rows along with controls to change which rows are visible.
tableOutput() is most useful for small, fixed summaries (e.g. model coefficients); dataTableOutput() is most appropriate if you want to expose a complete data frame to the user.
If you want greater control over the output of dataTableOutput(), I highly recommend the {reactable} package by Greg Lin (Lin 2023).
If I had used library(DT) then I wouldn’t needed DT:: in front of the {DT} functions.
Note
But there is an important difference: Using the library(<package-name>) function downloads several packages via https://repo.r-wasm.org/ (The WebR binary R package repository contains more than 15,000 packages that have been built for WebAssembly and are available for download from this repository). I think therefore that is generally better to use the <package-name>:: mode if there are used only some functions from the package in question.
By default, plotOutput() will take up the full width of its container (more on that shortly), and will be 400 pixels high. You can override these defaults with the height and width arguments. We recommend always setting res = 96 as that will make your Shiny plots match what you see in RStudio as closely as possible.
Plots are special because they are outputs that can also act as inputs. plotOutput() has a number of arguments like click, dblclick, and hover. If you pass these a string, like click = "plot_click", they’ll create a reactive input (input$plot_click) that you can use to handle user interaction on the plot, e.g. clicking on the plot. We’ll come back to interactive plots in Shiny in Chapter 7.
2.3.4 Downloads
You can let the user download a file with downloadButton() or downloadLink(). These require new techniques in the server function, so we’ll come back to that in Chapter 9.
Re-create the Shiny app from Section 2.3.3, this time setting height to 300px and width to 700px. Set the plot “alt” text so that a visually impaired user can tell that its a scatterplot of five random numbers.
Solution 2.10. : Plot dimensions and alt-tag
Code
library(shiny)ui<-fluidPage(plotOutput("plot", height ="300px", width ="700px"),)server<-function(input, output, session){output$plot<-renderPlot(plot(1:5), res =96, alt ="Scatterplot of five random numbers")}shinyApp(ui, server)
Update the options in the call to DT::renderDT() below so that the data is displayed, but all other controls are suppressed (i.e., remove the search, ordering, and filtering commands). You’ll need to read Using DT in shiny and review the more complex DT options.
This chapter has introduced the major input and output functions that make up the front end of a Shiny app. This was a big info dump, so don’t expect to remember everything after a single read. Instead, come back to this chapter when you’re looking for a specific component: you can quickly scan the figures, and then find the code you need.
In the next chapter, we’ll move on to the back end of a Shiny app: the R code that makes your user interface come to life.
---execute: cache: true---# Basic UI {#sec-chap02}```{r}#| label: setup#| results: hold#| include: falsebase::source(file ="R/helper.R")ggplot2::theme_set(ggplot2::theme_bw())options(show.signif.stars =FALSE)```## Table of content for chapter 02 {.unnumbered}::::: {#obj-chap02}:::: {.my-objectives}::: {.my-objectives-header}Chapter section list:::::: {.my-objectives-container}::::::::::::## IntroductionIn this chapter, we’ll focus on the front end, and give you a whirlwind tour of the HTML inputs and outputs provided by Shiny. We’ll mostly stick to the inputs and outputs built into Shiny itself. However, there is a rich and vibrant community of extension packages, like- [shinyWidgets](https://dreamrs.github.io/shinyWidgets/index.html)[@shinyWidgets], - [colourpicker](https://daattali.com/shiny/colourInput/)[@colourpicker], and - [sortable](https://rstudio.github.io/sortable/)[@sortable]. You can find a comprehensive, [actively-maintained list of packages supporting {**shiny**}](https://github.com/nanxstats/awesome-shiny-extensions) at , maintained by [Nan Xiao](https://nanx.me/).## Inputs {#sec-02-inputs}### Common structureThere are three common parameters:1. **inputID**: All input functions have the same first argument: `inputId`. This is the identifier used to connect the front end with the back end: if your UI has an input with ID `"name"`, the server function will access it with `input$name`. The `inputId` has two constraints: - It must be a simple string that contains only letters, numbers, and underscores (no spaces, dashes, periods, or other special characters allowed!). Name it like you would name a variable in R. - It must be unique. If it’s not unique, you’ll have no way to refer to this control in your server function!2. **label**: Most input functions have a second parameter called `label`. This is used to create a human-readable label for the control. Shiny doesn’t place any restrictions on this string.3. **value**: The third parameter is typically `value`, which, where possible, lets you set the default value.Remaining parameters are unique to the specific control.When creating an input, I recommend supplying the `inputId` and `label` arguments by position, and all other arguments by name:```sliderInput("min", "Limit (minimum)", value = 50, min = 0, max = 100)```::: {.callout-note}The following sections describe the inputs built into Shiny, loosely grouped according to the type of control they create. The goal is to give you a rapid overview of your options, not to exhaustively describe all the arguments. Read the documentation to get the full details!:::### Free textCollect small amounts of text with `textInput()`, passwords with `passwordInput()`, and paragraphs of text with `textAreaInput()`.::: {.callout-warning}All `passwordInput()` does is hide what the user is typing, so that someone looking over their shoulder can't read it. It's up to you to make sure that any passwords are not accidentally exposed, so we don't recommend using passwords unless you have had some training in secure programming.:::::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-free-text-input}: Free text input::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-free-text-input-code}: Free text input examples:::::::::::::{.my-r-code-container}```{r}#| label: free-text-input-code#| eval: false#| code-fold: showlibrary(shiny)ui <-fluidPage(textInput("name", "What's your name?"),passwordInput("password", "What's your password?"),textAreaInput("story", "Tell me about yourself", rows =3))server <-function(input, output, session) { }shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-free-text-input-shiny}: Free text input examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 300library(shiny)ui<- fluidPage(textInput("name","What's your name?"),passwordInput("password","What's your password?"),textAreaInput("story","Tell me about yourself", rows = 3))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::If you want to ensure that the text has certain properties you can use `shiny::validate()`, which we’ll come back to in @sec-chap08.### Numeric input {#sec-02-numeric-input}To collect numeric values, create a constrained text box with `numericInput()` or a slider with `sliderInput()`. If you supply a length-2 numeric vector for the default value of `sliderInput()`, you get a “range” slider with two ends.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-numeric-input}: Numeric input::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-numeric-input-code}: Numeric input examples:::::::::::::{.my-r-code-container}```{r}#| label: numeric-input-code#| eval: false#| code-fold: showlibrary(shiny)ui <-fluidPage(numericInput("num", "Number one", value =0, min =0, max =100),sliderInput("num2", "Number two", value =50, min =0, max =100),sliderInput("rng", "Range", value =c(10, 20), min =0, max =100))server <-function(input, output, session) { }shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-numeric-input-shiny}: Numeric input examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 300library(shiny)ui<- fluidPage(numericInput("num","Number one", value = 0, min = 0, max = 100),sliderInput("num2","Number two", value = 50, min = 0, max = 100),sliderInput("rng","Range", value = c(10, 20), min = 0, max = 100))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::Sliders are extremely customisable and there are many ways to tweak their appearance. See [Slider Input Widget — sliderInput](https://shiny.posit.co/r/reference/shiny/latest/sliderinput.html) and [Using sliders](https://shiny.rstudio.com/articles/sliders.html) for more details. See also my experiments in @sec-annex-choose-year-via-slider.### DatesCollect a single day with `shiny::dateInput()` or a range of two days with `shiny::dateRangeInput()`. These provide a convenient calendar picker, and additional arguments like `datesdisabled` and `daysofweekdisabled` allow you to restrict the set of valid inputs.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-date-input}: Date input::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-date-input-code}: Date input examples:::::::::::::{.my-r-code-container}```{r}#| label: date-input-code#| eval: falselibrary(shiny)ui <-fluidPage(dateInput("dob", "When were you born?"),dateRangeInput("holiday", "When do you want to go on vacation next?"))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-date-input-shiny}: Date input examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 400library(shiny)ui<- fluidPage(dateInput("dob","When were you born?"),dateRangeInput("holiday","When do you want to go on vacation next?"))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::Date format, language, and the day on which the week starts defaults to US standards. If you are creating an app with an international audience, set `format`, `language`, and `weekstart` so that the dates are natural to your users.### Limited choices#### IntroductionThere are two different approaches to allow the user to choose from a prespecified set of options: `shiny::selectInput()` and `shiny::radioButtons()`.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-limited-choices}: Limited choices::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-limited-choices-code}: Limited choices: drop-down menu and radio buttons:::::::::::::{.my-r-code-container}```{r}#| label: limited-choices-code#| eval: falselibrary(shiny)animals <-c("dog", "cat", "mouse", "bird", "other", "I hate animals")ui <-fluidPage(selectInput("state", "What's your favourite state?", state.name),radioButtons("animal", "What's your favourite animal?", animals))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-limited-choices-shiny}: Limited choices: drop-down menu and radio buttons:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 400library(shiny)animals<- c("dog","cat", "mouse", "bird", "other", "I hate animals")ui<- fluidPage(selectInput("state","What's your favourite state?", state.name),radioButtons("animal","What's your favourite animal?", animals))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::See my experiment in @sec-annex-whr-exercises-year-via-drop-down.#### Radio buttonsRadio buttons have two nice features: - they show all possible options, making them suitable for *short* lists, and - via the `choiceNames`/`choiceValues` arguments, they can display options other than plain text. - `choiceNames` determines what is shown to the user; - `choiceValues` determines what is returned in your server function.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-radio-buttons}: Radio buttons::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-radio-buttons-code}: Radio buttons:::::::::::::{.my-r-code-container}```{r}#| label: radio-buttons-code#| eval: falselibrary(shiny)ui <-fluidPage(radioButtons("rb", "Choose one:",choiceNames =list(icon("angry"),icon("smile"),icon("sad-tear") ),choiceValues =list("angry", "happy", "sad") ))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-radio-buttons-shiny}: Radio buttons:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 120library(shiny)ui<- fluidPage(radioButtons("rb","Choose one:",choiceNames = list(icon("angry"),icon("smile"),icon("sad-tear")),choiceValues = list("angry","happy", "sad")))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::#### Drop-down menusDropdowns created with `shiny::selectInput()` take up the same amount of space, regardless of the number of options, making them *more suitable for longer options*. You can also set `multiple = TRUE` to allow the user to select multiple elements.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-drop-down-menus}: Drop-down menus::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-drop-down-menus-code}: Drop-down menus:::::::::::::{.my-r-code-container}```{r}#| label: drop-down-menus-code#| eval: falselibrary(shiny)ui <-fluidPage(selectInput("state", "What are your favourite states?", state.name,multiple =TRUE ))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-drop-down-menus-shiny}: Drop-down menus:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 300library(shiny)ui<- fluidPage(selectInput("state","What are your favourite states?", state.name,multiple = TRUE))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::If you have a very large set of possible options, you may want to use “server-side” `shiny::selectInput()` so that the complete set of possible options are not embedded in the UI (which can make it slow to load), but instead sent as needed by the server. You can learn more about this advanced topic at [Server-side selectize](https://shiny.rstudio.com/articles/selectize.html#server-side-selectize).#### Check boxesThere’s no way to select multiple values with radio buttons, but there’s an alternative that’s conceptually similar: `shiny::checkboxGroupInput()`. For a checkbox for a single yes/no question, use `shiny::checkboxInput()`:::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-check-boxes}: Check boxes::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-check-boxes-code}: Check boxes:::::::::::::{.my-r-code-container}```{r}#| label: check-boxes-code#| eval: falselibrary(shiny)animals <-c("dog", "cat", "mouse", "bird", "other", "I hate animals")ui <-fluidPage(checkboxGroupInput("animal", "What animals do you like?", animals),checkboxInput("cleanup", "Clean up?", value =TRUE),checkboxInput("shutdown", "Shutdown?"))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-check-boxes-shiny}: Check boxes:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 300library(shiny)animals<- c("dog","cat", "mouse", "bird", "other", "I hate animals")ui<- fluidPage(checkboxGroupInput("animal","What animals do you like?", animals),checkboxInput("cleanup","Clean up?", value = TRUE),checkboxInput("shutdown","Shutdown?"))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::### File uploads`shiny::fileInput()` requires special handling on the server side, and is discussed in detail in @sec-chap09.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-file-uploads}: File uploads::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-file-uploads-code}: File uploads:::::::::::::{.my-r-code-container}```{r}#| label: file-uploads-code#| eval: falselibrary(shiny)ui <-fluidPage(fileInput("upload", NULL))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-file-uploads-shiny}: File uploads:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 100library(shiny)ui<- fluidPage(fileInput("upload", NULL))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::### Action buttons {#sec-02-action-buttons}Let the user perform an action with `shiny::actionButton()` or `shiny::actionLink()`: Actions links and buttons are most naturally paired with `shiny::observeEvent()` or `shiny::eventReactive()` in your server function. You haven’t learned about these important functions yet, but we’ll come back to them in @sec-03-control-timing.You can customize the appearance using the class argument by using one of `"btn-primary"`, `"btn-success"`, `"btn-info"`, `"btn-warning`", or `"btn-danger"`. You can also change the size with `"btn-lg`", `"btn-sm"`, `"btn-xs`". Finally, you can make buttons span the entire width of the element they are embedded within using `"btn-block"`.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-action-buttons}: Action buttons::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-action-buttons-code}: Action buttons:::::::::::::{.my-r-code-container}```{r}#| label: action-buttons-code#| eval: falselibrary(shiny)ui <-fluidPage(fluidRow(actionButton("click", "Click me!", class ="btn-danger"),actionButton("drink", "Drink me!", class ="btn-lg btn-success") ),fluidRow(actionButton("eat", "Eat me!", class ="btn-block") ))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-action-buttons-shiny}: Action buttons:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 100library(shiny)ui<- fluidPage(fluidRow(actionButton("click","Click me!", class = "btn-danger"),actionButton("drink","Drink me!", class = "btn-lg btn-success")),fluidRow(actionButton("eat","Eat me!", class = "btn-block")))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::The class argument works by setting the class attribute of the underlying HTML, which affects how the element is styled. To see other options, you can read the [documentation for Bootstrap](http://bootstrapdocs.com/v3.3.6/docs/css/#buttons), the CSS design system used by Shiny.### Exercises#### Label as placeholder:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-input-ex-01-label-as-placeholder}: Use label as placeholder:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeWhen space is at a premium, it’s useful to label text boxes using a placeholder that appears inside the text entry area. How do you call `shiny::textInput()` to generate the UI below?###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-01-label-as-placeholder-solution}: Use label as placeholder:::::::::::::{.my-solution-container}```{r}#| label: label-as-placeholder-solution#| eval: false#| code-fold: showlibrary(shiny)ui <-fluidPage(textInput("name", label =NULL, placeholder = label ))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-01-label-as-placeholder-shiny}: Use label as placeholder:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 100library(shiny)ui<- fluidPage(textInput("name",label = NULL, placeholder = "Your name"))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::#### Create a date slider:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-input-ex-02-create-date-slider}: Create a date slider:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeCarefully read the documentation for `shiny::sliderInput()` to figure out how to create a date slider, as shown below.###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-02-create-date-slider-solution}: Create a data slider:::::::::::::{.my-solution-container}```{r}#| label: create-date-slider-solution#| eval: false#| code-fold: showlibrary(shiny)library(lubridate)ui <-fluidPage(sliderInput("deliver","When should we deliver?",timeFormat ="%F",min =as_date("2020-09-16"),max =as_date("2020-09-23"),value =as_date("2020-09-17") ))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-02-create-date-slider-shiny}: Create a date slider:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 100library(shiny)library(lubridate)ui<- fluidPage(sliderInput("deliver","When should we deliver?",timeFormat = "%F",min = as_date("2020-09-16"),max = as_date("2020-09-23"),value = as_date("2020-09-17")))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::#### Slider animation:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-input-ex-03-slider-animation}: Slider animation:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeCreate a slider input to select values between 0 and 100 where the interval between each selectable value on the slider is 5. Then, add animation to the input widget so when the user presses play the input widget scrolls through the range automatically.###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-03-slider-animation-solution}: Slider animation:::::::::::::{.my-solution-container}```{r}#| label: slider-animation-solution#| eval: false#| code-fold: showui <-fluidPage(sliderInput("num","Press play button",min =0,max =100,value =0,step =5,animate =animationOptions(interval =1000,loop =TRUE,playButton ="Play",pauseButton ="Stop" ) ))server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-03-slider-animation-shiny}: Slider animation:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 150ui<- fluidPage(sliderInput("num","Press play button",min = 0,max = 100,value = 0,step = 5,animate = animationOptions(interval = 1000,loop = TRUE,playButton = "Play",pauseButton = "Stop")))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::#### Menu with subheadings:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-input-ex-04-menu-with-subheadings}: Menu with subheadings:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeIf you have a moderately long list in a `shiny::selectInput()`, it’s useful to create sub-headings that break the list up into pieces. Read the documentation to figure out how. (Hint: the underlying HTML is called `<optgroup>`.)###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-04-menu-with-subheadings-solution}: Menu with subheadings:::::::::::::{.my-solution-container}```{r}#| label: menu-with-subheadings-solution#| eval: false#| code-fold: showlibrary(shiny) ui =fluidPage(selectInput("state", "Choose a state:",list(`East Coast`=list("NY", "NJ", "CT"),`West Coast`=list("WA", "OR", "CA"),`Midwest`=list("MN", "WI", "IA") ) ) )server <-function(input, output, session) {}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-04-menu-with-subheadings-shiny}: Menu with subheadings:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 300library(shiny)ui = fluidPage(selectInput("state","Choose a state:",list(`East Coast` = list("NY","NJ", "CT"),`West Coast` = list("WA","OR", "CA"),`Midwest` = list("MN","WI", "IA"))))server<- function(input, output, session){}shinyApp(ui, server)```:::::::::::::::::::::## Outputs {#sec-02-outputs}Outputs in the UI create placeholders that are later filled by the server function. Like inputs, outputs take a unique ID as their first argument: if your UI specification creates an output with ID `"plot"`, you’ll access it in the server function with `output$plot`.**Each output function on the front end is coupled with a render function in the back end.** There are three main types of output, corresponding to the three things you usually include in a report: text, tables, and plots. The following sections show you the basics of the output functions on the front end, along with the corresponding render functions in the back end.### TextOutput regular text with `shiny::textOutput()` and fixed code and console output with `shiny::verbatimTextOutput()`.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-text-output-examples}: Text output examples::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code1:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-text-output-examples-code1}: Text output examples:::::::::::::{.my-r-code-container}```{r}#| label: text-output-examples-code1#| eval: falselibrary(shiny)ui <-fluidPage(textOutput("text"),verbatimTextOutput("code"))server <-function(input, output, session) { output$text <-renderText({ "Hello friend!" }) output$code <-renderPrint({ summary(1:10) })}shinyApp(ui, server)```***Note that the `{}` are only required in render functions if you need to run multiple lines of code. As you’ll learn shortly, you should do as little computation in your render functions as possible, which means you can often omit them.:::::::::###### Code2:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-text-output-examples-code2}: Text output examples:::::::::::::{.my-r-code-container}```{r}#| label: text-output-examples-code2#| eval: falselibrary(shiny)ui <-fluidPage(textOutput("text"),verbatimTextOutput("code"))server <-function(input, output, session) { output$text <-renderText("Hello friend!") output$code <-renderPrint(summary(1:10))}shinyApp(ui, server)```***Here’s what the server function would look like if written more compactly.:::::::::###### Shiny1:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-text-output-examples-shiny1}: Text output examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 150ui<- fluidPage(textOutput("text"),verbatimTextOutput("code"))server<- function(input, output, session){output$text<- renderText({"Hello friend!"})output$code<- renderPrint({summary(1:10)})}shinyApp(ui, server)```:::::::::###### Shiny2:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-text-output-examples-shiny2}: Text output examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 150ui<- fluidPage(textOutput("text"),verbatimTextOutput("code"))server<- function(input, output, session){output$text<- renderText("Hello friend!")output$code<- renderPrint(summary(1:10))}shinyApp(ui, server)```:::::::::::::::::::::Note that there are two render functions which behave slightly differently:- `renderText()` combines the result into a single string, and is usually paired with `textOutput()`- `renderPrint()` prints the result, as if you were in an R console, and is usually paired with `verbatimTextOutput()`.We can see the difference with a toy app:::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-diff-renderText-renderPrint}: Difference between `renderText()` and `renderPrint()`::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-diff-renderText-renderPrint-code}: Difference between `renderText()` and `renderPrint()`:::::::::::::{.my-r-code-container}```{r}#| label: diff-renderText-renderPrint-code#| eval: falselibrary(shiny)ui <-fluidPage(textOutput("text"),verbatimTextOutput("print"))server <-function(input, output, session) { output$text <-renderText("hello!") output$print <-renderPrint("hello!")}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-diff-renderText-renderPrint-shiny}: Difference between `renderText()` and `renderPrint()`:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 100library(shiny)ui<- fluidPage(textOutput("text"),verbatimTextOutput("print"))server<- function(input, output, session){output$text<- renderText("hello!")output$print<- renderPrint("hello!")}shinyApp(ui, server)```***This is equivalent to the difference between `cat()` and `print()` in base R.:::::::::::::::::::::### TablesThere are two options for displaying data frames in tables:- `tableOutput()` and `renderTable()` render a static table of data, showing all the data at once.- `dataTableOutput()` and `renderDataTable()` render a dynamic table, showing a fixed number of rows along with controls to change which rows are visible.`tableOutput()` is most useful for small, fixed summaries (e.g. model coefficients); `dataTableOutput()` is most appropriate if you want to expose a complete data frame to the user. If you want greater control over the output of `dataTableOutput()`, I highly recommend the {**reactable**} package by Greg Lin [@reactable].::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-table-output}: Table output examples::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code1:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-table-output-examples-code1}: Table output examples:::::::::::::{.my-r-code-container}```{r}#| label: 02-table-output-examples-code1#| eval: falselibrary(shiny)ui <-fluidPage(tableOutput("static"),dataTableOutput("dynamic"))server <-function(input, output, session) { output$static <-renderTable(head(mtcars)) output$dynamic <-renderDataTable(mtcars, options =list(pageLength =5))}shinyApp(ui, server)````shiny::renderDataTable()` is deprecated as of shiny 1.8.1.Please use `DT::renderDT()` instead.Since you have a suitable version of DT (>= v0.32.1), `shiny::renderDataTable()` will automatically use `DT::renderDT()` under-the-hood.If this happens to break your app, set `options(shiny.legacy.datatable = TRUE)` to get the legacy datatable implementation (or `FALSE` to squelch this message).See <https://rstudio.github.io/DT/shiny.html> for more information.::: {.callout-warning}There are no warning/error messages in the {**shinylive-r**} mode. For debugging purposes it is necessary to use the standard `app.R` file approach.::::::::::::###### Code2:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-table-output-examples-code2}: Table output examples:::::::::::::{.my-r-code-container}```{r}#| label: 02-table-output-examples-code2#| eval: falselibrary(shiny)ui <-fluidPage(tableOutput("static"), DT::DTOutput("dynamic"))server <-function(input, output, session) { output$static <-renderTable(head(mtcars)) output$dynamic <- DT::renderDT(mtcars, options =list(pageLength =5))}shinyApp(ui, server)```If I had used `library(DT)` then I wouldn't needed `DT::` in front of the {**DT**} functions.::: {.callout-note }But there is an important difference: Using the `library(<package-name>)` function downloads several packages via <https://repo.r-wasm.org/> ([The WebR binary R package repository](https://repo.r-wasm.org/) contains more than 15,000 packages that have been built for WebAssembly and are available for download from this repository). I think therefore that is generally better to use the `<package-name>::` mode if there are used only some functions from the package in question.::::::::::::###### Shiny1:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-table-output-examples-shiny1}: Table output examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 700library(shiny)ui<- fluidPage(tableOutput("static"),dataTableOutput("dynamic"))server<- function(input, output, session){output$static<- renderTable(head(mtcars))output$dynamic<- renderDataTable(mtcars, options = list(pageLength = 5))}shinyApp(ui, server)```:::::::::###### Shiny2:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-table-output-examples-shiny2}: Table output examples:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 700library(shiny)library(DT)ui<- fluidPage(tableOutput("static"),DTOutput("dynamic"))server<- function(input, output, session){output$static<- renderTable(head(mtcars))output$dynamic<- renderDT(mtcars, options = list(pageLength = 5))}shinyApp(ui, server)```:::::::::::::::::::::### Plots {#sec-02-plots}You can display any type of R graphic (base, {**ggplot2**}, or otherwise) with `plotOutput()` and `renderPlot()`.::: {.my-code-collection}:::: {.my-code-collection-header}::::: {.my-code-collection-icon}::::::::::: {#exm-02-output-plots}: Plot output example::::::::::::::{.my-code-collection-container}::: {.panel-tabset}###### Code:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-output-plot-example-code}: Plot output example:::::::::::::{.my-r-code-container}```{r}#| label: output-plot-example-code#| eval: falselibrary(shiny)ui <-fluidPage(plotOutput("plot1", height ="400px", width ="400px"),plotOutput("plot2"))server <-function(input, output, session) { output$plot1 <-renderPlot(plot(1:5), res =96) output$plot2 <-renderPlot(plot(1:5), res =96)}shinyApp(ui, server)```:::::::::###### Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02-output-plot-example-shiny}: Plot output example:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 800library(shiny)ui<- fluidPage(plotOutput("plot1", height = "400px", width = "400px"),plotOutput("plot2"))server<- function(input, output, session){output$plot1<- renderPlot(plot(1:5), res = 96)output$plot2<- renderPlot(plot(1:5), res = 96)}shinyApp(ui, server)```:::::::::::::::::::::By default, `plotOutput()` will take up the full width of its container (more on that shortly), and will be 400 pixels high. You can override these defaults with the `height` and `width` arguments. We recommend always setting `res = 96` as that will make your Shiny plots match what you see in RStudio as closely as possible.Plots are special because they are outputs that can also act as inputs. `plotOutput()` has a number of arguments like `click`, `dblclick`, and `hover`. If you pass these a string, like `click = "plot_click"`, they’ll create a reactive input (`input$plot_click`) that you can use to handle user interaction on the plot, e.g. clicking on the plot. We’ll come back to interactive plots in Shiny in @sec-chap07.### DownloadsYou can let the user download a file with `downloadButton()` or `downloadLink()`. These require new techniques in the server function, so we’ll come back to that in @sec-chap09.### Exercises#### Render function to pair:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-output-ex-01-render-function-to-pair}: Render function to pair:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeWhich of `textOutput()` and `verbatimTextOutput()` should each of the following render functions be paired with?a) `renderPrint(summary(mtcars))`b) `renderText("Good morning!")`c) `renderPrint(t.test(1:5, 2:6))`d) `renderText(str(lm(mpg ~ wt, data = mtcars)))`###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-01-label-as-placeholder-solution}: Use label as placeholder:::::::::::::{.my-solution-container}Which of `textOutput()` and `verbatimTextOutput()` should each of the following render functions be paired with?a) `renderPrint(summary(mtcars))`: `verbatimTextOutput()`b) `renderText("Good morning!")`: `textOutput()`c) `renderPrint(t.test(1:5, 2:6))`: `verbatimTextOutput()`d) `renderText(str(lm(mpg ~ wt, data = mtcars)))`: `textOutput()`:::::::::::::::::::::#### Plot dimensions and `alt`-tag:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-output-ex-02-plot-dimensions-alt-tag}: Change plot dimensions and add `alt`-tag:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeRe-create the Shiny app from @sec-02-plots, this time setting height to 300px and width to 700px. Set the plot `“alt”` text so that a visually impaired user can tell that its a scatterplot of five random numbers.###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-output-ex-02-plot-dimensions-alt-tag-solution}: Plot dimensions and `alt`-tag:::::::::::::{.my-solution-container}```{r}#| label: plot-dimensions-alt-tag-solution#| eval: falselibrary(shiny)ui <-fluidPage(plotOutput("plot", height ="300px", width ="700px"),)server <-function(input, output, session) { output$plot <-renderPlot(plot(1:5), res =96,alt ="Scatterplot of five random numbers")}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-input-ex-02-plot-dimension-alt-tag-solution}: Plot dimensions and `alt`-tag:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 400library(shiny)ui<- fluidPage(plotOutput("plot", height = "300px", width = "700px"),)server<- function(input, output, session){output$plot<- renderPlot(plot(1:5),res = 96,alt = "Scatterplot of five random numbers")}shinyApp(ui, server)```:::::::::::::::::::::#### `DT::renderDT()` options:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-input-ex-03-renderDT-options}: `DT::renderDT()` options:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### Challenge CodeUpdate the options in the call to `DT::renderDT()` below so that the data is displayed, but all other controls are suppressed (i.e., remove the search, ordering, and filtering commands). You’ll need to read [Using DT in shiny](https://rstudio.github.io/DT/shiny.html) and review the more complex [DT options](https://datatables.net/reference/option/).:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02--output-ex-03-renderDT-options-challenge}: Standard `DT::renderDT()` options:::::::::::::{.my-r-code-container}```{r}#| label: renderDT-options-challenge-code#| eval: falselibrary(shiny)ui <-fluidPage( DT::DTOutput("table"))server <-function(input, output, session) { output$table <- DT::renderDT(mtcars, options =list(pageLength =5))}shinyApp(ui, server)```:::::::::###### Challenge Shiny:::::{.my-r-code}:::{.my-r-code-header}:::::: {#cnj-02--output-ex-03-renderDT-options-challenge}: Standard `DT::renderDT()` options:::::::::::::{.my-r-code-container}```{shinylive-r}#| standalone: true#| viewerHeight: 600library(shiny)ui<- fluidPage(DT::DTOutput("table"))server<- function(input, output, session){output$table<- DT::renderDT(mtcars, options = list(pageLength = 5))}shinyApp(ui, server)```:::::::::###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-output-ex-03-renderDT-options-solution-code}: Change `DT::renderDT()` options:::::::::::::{.my-solution-container}```{r}#| label: renderDT-options-solution-code#| eval: falselibrary(shiny)ui <-fluidPage( DT::DTOutput("table"))server <-function(input, output, session) { output$table <- DT::renderDT( mtcars,options =list(pageLength =5,searching =FALSE,ordering =FALSE,lengthChange =FALSE ) )}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-output-ex-03-renderDT-options-solution-shiny}: Change `DT::renderDT()` options:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 600library(shiny)ui<- fluidPage(DT::DTOutput("table"))server<- function(input, output, session){output$table<- DT::renderDT(mtcars,options = list(pageLength = 5,searching = FALSE,ordering = FALSE,lengthChange = FALSE))}shinyApp(ui, server)```:::::::::::::::::::::#### Use {reactable}:::::{.my-exercise}:::{.my-exercise-header}:::::: {#exr-02-output-ex-04-use-reactable}: Use reactable:::::::::::::{.my-exercise-container}::: {.panel-tabset}###### ChallengeAlternatively, read up on {**reactable**}, and convert the @sol-02-output-ex-03-renderDT-options-solution-code to use {**reactable**} instead.###### Solution (Code):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-output-ex-04-use-reactable-solution-code}: Menu with subheadings:::::::::::::{.my-solution-container}```{r}#| label: use-reactable-solution-code#| eval: falselibrary(shiny)ui <-fluidPage( reactable::reactableOutput("table"))server <-function(input, output, session) { output$table <- reactable::renderReactable({ reactable::reactable(mtcars) })}shinyApp(ui, server)```:::::::::###### Solution (Shiny):::::{.my-solution}:::{.my-solution-header}:::::: {#sol-02-output-ex-04-use-reactable-solution-shiny}: Use {**reactable**}:::::::::::::{.my-solution-container}```{shinylive-r}#| standalone: true#| viewerHeight: 700library(shiny)ui<- fluidPage(reactable::reactableOutput("table"))server<- function(input, output, session){output$table<- reactable::renderReactable({reactable::reactable(mtcars)})}shinyApp(ui, server)```:::::::::::::::::::::## SummaryThis chapter has introduced the major input and output functions that make up the front end of a Shiny app. This was a big info dump, so don’t expect to remember everything after a single read. Instead, come back to this chapter when you’re looking for a specific component: you can quickly scan the figures, and then find the code you need.In the next chapter, we’ll move on to the back end of a Shiny app: the R code that makes your user interface come to life.