Last week, I gave a talk at RLadies Jakarta 6th meetup about an intro to R Shiny. Although I already published the presentation on R-Ladies Jakarta’s GitHub, I still feel the urge of sharing it in a blog format. That way, I can explain it in more detail. So, here we go. Enjoy 🙂
What is Shiny?
No, this is not the South Korean boy band.
Shiny is an R package that uses R to build interactive and reactive web apps straight from R, such as data explorers and dashboards. It can be hosted as a standalone app on a webpage or embed them in R Markdown documents. It also can be extended with CSS themes, HTML widgets, and JavaScript actions. What kind of interaction can we add to a Shiny app? Many kinds, from inputting numbers, text, or moving the slider to upload a file. Figure 1 gives the illustration of Shiny in a nutshell and figure 2 gives an example of a beautiful complex shiny App.


In general, building shiny app consists of 4 steps:
- Install and load the shiny package.
- Create the Shiny UI (webpage) with
fluidPage()
.
Everything related to customizing UI will happen inside the functionfluidPage()
. In fact, the entire UI is built by supplying thefluidPage()
function with as many arguments as you want. - Create a Shiny server.
The server is where the logic of the app is implemented - Combine UI and server and run the app.
The four steps above are wrapped into what is called The Shiny Template as follows:
1 2 3 4 5 6 7 |
install.packages('shiny') library(shiny) ui <- fluidPage( input1, inputn, output1, outputn) server <- function(input, output, session) {} shinyApp(ui = ui, server = server) |
Note that the argument session
is optional in the Shiny server. The template above will result in an empty Shiny app. Now, we can start embellishing it.
I. Add Text to Shiny App
- Add text as argument to fluidPage().
- Add several numbers of text by separating them with commas.
- Format the text using
h1()
for primary header,h2()
for secondary header,strong()
for bold font, andem()
for the italicized font. Explore other formatting functions available on R Documentation.
For example:
123ui <- fluidPage(h1("LEARN SHINY"),"with",strong("Erika Siregar"))
II. Customizing Layouts using sidebarLayout()
The sidebar layout provides a layout that consists of 2 columns, 1 smaller column on the left for the sidebar, and 1 bigger column on the right for the main panel (see Figure 2).

Sidebar layout may consist of:
- sidebarPanel(UI_elements) –> for the menu
- mainPanel(UI_elements, width = 8) –> for the result of whatever is being clicked on the menu.
-
conditionalPanel(condition, UI_elements) –> the panel that is only visible on a certain condition such as the user chooses a certain option, etc.
- The condition is written using
.
instead of$
. So, we writecondition = "input.source == 'file'"
instead ofcondition = "input$source == 'file'"
. - it is put inside the
sidebarPanel()
.
For example:
123456789101112131415161718192021222324ui <- fluidPage(h1("Play with layout"),sidebarLayout(sidebarPanel(radioButtons(inputId = "source",label = "Word source",choices = c("Art of War" = "book","Use your own words" = "own","Upload a file" = "file")),conditionalPanel(condition = "input.source == 'own'",textAreaInput("text", "Enter text", rows = 7)),numericInput("num", "Maximum number of words", value = 100, min = 5),colourInput("col", "Background color", value = "white")),mainPanel(wordcloud2Output("cloud"))))
- The condition is written using
III. Add Input Components
Format for accessing input:
input$id.
-
textInput(id, label, default_value) .
Example: textInput("title", "Plot title", "Car speed vs distance to stop") . -
textAreaInput(inputId, label, value = "", width = NULL, height = NULL) .
- support longer text (multiple rows) than
textInput()
does. - have a vertical scrollbar, as well as a
rows
parameter that can determine how many rows are visible.
- support longer text (multiple rows) than
-
numericInput(numeric_input_id, label, default_value, min, step, max) –> notes that default_value, min, step, and max are optional.
We get a numeric input box with up and down arrow.
Example 1: numericInput("num", "Number of flowers to show data for", 10, 1, nrow(iris)) .
Output:Figure 4. Example of numericInput(). -
sliderInput() .
Format:
1234sliderInput(inputId, label, min, max, value, step = NULL, round =https://www.rdocumentation.org/packages/shiny/topics/fileInput FALSE,format = NULL, locale = NULL, ticks = TRUE, animate = FALSE,width = NULL, sep = ",", pre = NULL, post = NULL, timeFormat = NULL,timezone = NULL, dragRange = TRUE)Notes:
– value = The initial value of the slider. A numeric vector of length one will create a regular slider; a numeric vector of length two will create a double-ended range sliderExample 1:
sliderInput("years", "Years", min(gapminder$year), max(gapminder$year), value = c(1977, 2002))Example 2:
sliderInput("height", "Height", min = 66, max = 264, value = 264) .Output:
Figure 5. Example of sliderInput(). -
checkboxInput(inputId, label, value = FALSE, width = NULL) .
Example 1:
checkboxInput("fit", "Add line of best fit") .
Output:Figure 6. Example of checboxInput() Example 2: do something when the box is checked. Note that this script is written inside server <- function(input, output) .
1if (input$fit) { p <- p + geom_smooth(method = "lm") }Figure 7. Interaction between the checboxInput() and the plot. -
radioButtons(inputId, label, choices = NULL, selected = NULL, inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) .
Example:
UI:
radioButtons("color", "Point color", c("blue", "red", "green", "black"))
Server:123p <- ggplot(gapminder, aes(gdpPercap, lifeExp)) +# Use the value of the color input as the point colourgeom_point(size = input$size, col = input$color)Figure 8. Interaction between radioButtons() with the plot. - selectInput a.k.a dropdown
Format: selectInput(inputId, label, choices, selected = NULL, multiple = FALSE, selectize = TRUE, width = NULL, size = NULL) .
Notes:
– choices = List of values to select from
– selected = the initial selected value
– multiple = Is selection of multiple items allowed? –> value = TRUE or FALSE (default).
Example:1234selectInput("continents", "Continents",choices = levels(gapminder$continent),multiple = TRUE,selected = "Europe")Figure 9. Example of selectInput() -
colourInput() .
For providing a color input. Must install the librarycolourpicker
before being able to use the functioncolourInput()
.
Format:
colourInput(inputId, label, value = "white", showColour = c("both", "text", "background"), palette = c("square", "limited") .Notes:
– value = default colourExample:
colourInput("color", "Point color", "blue") .Figure 10. Example of colorInput() -
fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL, buttonLabel = "Browse...", placeholder = "No file selected") .
- file upload control.
- Notes on the function’s parameters:
- multiple = if
TRUE
–> allow uploading multiple files at once. - accept = A character vector of MIME types; gives the browser a hint of what kind of files the server is expecting.
e.g: accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv") . - placeholder = text that will be shown when there is no file uploaded, yet.
Figure 11. placeholder.
- multiple = if
- After the user selects a file, that file gets uploaded to the computer that runs the Shiny app, and it becomes available on the server.
- Accessing the uploaded file –>
input$id
.- It will return a
data.frame
that contains metadata about the selected file. One of the returned metadata isdatapath
. - Use
input$id$datapath
to directly access the intended file. - We can then proceed to read the files using any file-reading function that accepts a file path as an argument. For example:
read.csv()
–> for reading a CSV file.readLines()
–> for simply reading all the lines in the file.
- It will return a
- UI example:
fileInput("file", "Select a file") . - Server example:
1234567input_file <- input$file({if (is.null(input$file)) {return("")}# Read the text in the uploaded filereadLines(input$file$datapath)})
- dateInput()
dateInput(inputId, label, value = NULL, min = NULL, max = NULL, format = "yyyy-mm-dd") .Example:
dateInput("date1", "Date:", value = "2012-02-29") .
IV. Add Output Components
Format for accessing output: output$id.
Plot
- UI:
12plotOutput(outputId, width = "100%", height = "400px", click = NULL, dblclick = NULL, hover = NULL, hoverDelay = NULL, hoverDelayType = NULL,brush = NULL, clickId = NULL, hoverId = NULL, inline = FALSE) - server:
1234output$plot_output_id <- renderPlot(<strong>{expr}</strong>, width = "auto", height = "auto", res = 72,..., <br>env = parent.frame(), quoted = FALSE,execOnResize = FALSE, <br>outputArgs = list())
notes that the wordplot
written afteroutput$
represent theid
of the input component defined in theUI
. - when displaying a plot in a Shiny app using
plotOutput()
, the height of the plot by default will be 400 pixels. The functionplotOutput()
has some parameters that can be used to modify the height or width of a plot.
Table
- Table with tableOutput()
- UI: tableOutput("table_output_id") .
- server:
1234output$table_output_id <- renderTable(<strong>{expr}</strong>, striped = FALSE, hover = FALSE, bordered = FALSE,spacing = c("s", "xs", "m", "l"), width = "auto", align = NULL,rownames = FALSE, colnames = TRUE, digits = NULL, na = "NA",..., env = parent.frame(), quoted = FALSE, outputArgs = list())
- Table with package
DT
.
The packageDT
comes with several benefits that are not available in the basictableOutput()
that are (1) pagination, (2) dropdown list to choose how many entries to show on each page, (3) column sorting, (4) search box, (5) row highlighting.Figure 12. The Perks of Using the DT
Package.Start using
DT
package by installing and loading the library.12install.packages("DT"))library(DT)Then, use
DT::dataTableOutput('id')
andDT::renderDataTable()
, which is analogous to that ofoutputTable('id')
andrenderTable()
pair. Example:12345678910111213ui <- fluidPage(# replace tableOutput("table")DT::dataTableOutput('table'))server <- function(input, output) {# Replace the renderTable() with DT's versionoutput$table <- DT::renderDataTable({data <- filtered_data()data})}shinyApp(ui, server)
Text
- UI: textOutput("id") .
- Server:
output$text_output_id <- renderText({expr}, env = parent.frame(), quoted = FALSE, outputArgs = list()) or we can also use
renderPrint() . Example:
123456789101112131415161718192021222324252627282930313233343536# Load the shiny packagelibrary(shiny)# Define UI for the applicationui <- fluidPage(# Add a sidebar layout to the applicationsidebarLayout(# Add a sidebar panel around the text and inputssidebarPanel(h4("Plot parameters"),textInput("title", "Plot title", "Car speed vs distance to stop"),numericInput("num", "Number of cars to show", 30, 1, nrow(cars)),sliderInput("size", "Point size", 1, 5, 2, 0.5)),# Add a main panel around the plot and tablemainPanel(plotOutput("plot"),tableOutput("table"),textOutput("text"))))# Define the server logicserver <- function(input, output) {output$plot <- renderPlot({plot(cars[1:input$num, ], main = input$title)})output$table <- renderTable({cars[1:input$num, ]})output$text <- renderText()}# Run the applicationshinyApp(ui = ui, server = server)
Output:
Figure 13. Example of Shiny App with more components. , l Figure 14. Code with some explanation.
downloadButton
Format UI:
downloadButton(download_id, label = "Download", class = NULL, ...) .
Format Server:
output$download_id <- downloadHandler(filename, content, contentType = NA, outputArgs = list()) .
Notes:
– filename = name of the downloaded file
– content = the content of the downloaded file, the argument is the path.
Example:
1. UI:
1 2 3 |
ui <- fluidPage( downloadButton(outputId = "download_data", label = "Download") ) |
2. Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
server <- function(input, output) { output$download_data <- downloadHandler( # The downloaded file is named "gapminder_data.csv" filename = "gapminder_data.csv", content = function(file) { # the code for filtering the data is copied from the renderTable() function data <- subset(gapminder, lifeExp >= input$life[1] & lifeExp <= input$life[2]) if(input$continent != "All") { data <- subset(data, continent == input$continent) } # write the filtered data into a CSV file write.csv(data, file, row.names = FALSE) })} |
Word Cloud
- use the library
wordcloud2
–>install.packages('wordcloud2')
. - UI –> wordcloud2Output(id) .
- Server –>
123output$cloud <- renderWordcloud2({create_wordcloud(data)})
The function create_wordcloud is a self-defined function and not part of thewordcloud
package. It utilizes the functionwordcloud2()
from the librarywordclud2
.
12345678910111213141516171819202122232425function(data, num_words = 100, background = "white") {# If text is provided, convert it to a dataframe of word frequenciesif (is.character(data)) {corpus <- Corpus(VectorSource(data))corpus <- tm_map(corpus, tolower)corpus <- tm_map(corpus, removePunctuation)corpus <- tm_map(corpus, removeNumbers)corpus <- tm_map(corpus, removeWords, stopwords("english"))tdm <- as.matrix(TermDocumentMatrix(corpus))data <- sort(rowSums(tdm), decreasing = TRUE)data <- data.frame(word = names(data), freq = as.numeric(data))}# Make sure a proper num_words is providedif (!is.numeric(num_words) || num_words < 3) {num_words <- 3}# Grab the top n most common wordsdata <- head(data, n = num_words)if (nrow(data) == 0) { return(NULL) }wordcloud2(data, backgroundColor = background)}
Image
- UI: imageOutplut()
- Server: renderImage()
V. Write the Logic in server
The activities in server basically consists of 3 steps:
- renderComponent({...}) –> renderText({...}) , renderTable({...}) , renderPlot({...}) .
- write what to render inside the renderComponent function.
- Getting the value of an input:
input$input_id.
Example:
12345output$name <- renderText({input$name# 'name' is the id for the selectInput() component.# we want to render as text the value of the selectInput('name')})
- Getting the value of an input:
input$input_id.
- assign the result to a variable named
output$output_id
.
VI. Split the Outputs into Tabs (optional)
- wrap UI elements in
tabPanel()
. - then wrap all tabs inside
tabsetPanel()
–> function to create a container for the tab panels. - Usage example:
123456fluidPage(tabsetPanel(tabPanel(title = "tab 1", "first tab content goes here"),tabPanel(title = "tab 2", "second tab", plotOutput("plot")),tabPanel(title = "tab 3", textInput("text", "Name", "")))) - Output example:
Figure 16. Example of Tabs
VII. Modify the App’s Look
- utilizing CSS
- implement the CSS inside the UI (
fluidPage()
) using functiontags$style('put the CSS here')
. - Example:
Figure 17. Using CSS to stylize Shiny
VIII. Run Your Shiny App
After designing the UI and setup the server code as above, the last step left to do is run the shinyApp using this simple syntax: shinyApp(ui = ui, server = server). See the complete example with the explanation below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# Load the shiny package library(shiny) # Define UI for the application ui <- fluidPage( sidebarLayout( sidebarPanel( textInput("name", "What is your name?", "Dean"), numericInput("num", "Number of flowers to show data for", 10, 1, nrow(iris)) ), mainPanel( textOutput("greeting"), plotOutput("cars_plot"), tableOutput("iris_table") ))) # Define the server logic server <- function(input, output) { # Create a plot of the "cars" dataset output$cars_plot <- renderPlot({ plot(cars) }) # Render a text greeting as "Hello <name>" output$greeting <- renderText({ paste("Hello", input$name) }) # Show a table of the first n rows of the "iris" data output$iris_table <- renderTable({ data <- iris[1:input$num, ] data })} # Run the application shinyApp(ui = ui, server = server) |
Output:
Figure 18. Example of Shiny with a plot, table, textInput, and numericInputCode with explanation:

IX. Shiny + Plotly
Why combine Shiny with Plotly? The answer is to make the shiny plot even more interactive because plotly
already comes with its own interactive toolbar menu and tooltip labels as can be seen in the illustration below.

- install and load the
plotly
library:
12install.packages('plotly')library('plotly')
Note thatplotly
will also require us to install the dependent libraryhexbin
which also requires our machine to have agfortran
compiler installed. For those who use Manjaro, you can installgfortran
compiler using commandsudo pacman -S gcc-fortran
. - UI: plotlyOutput(id) .
- Server:
output$plot_output_id <- renderPlotly({things_to_create_the_plot}) . - shiny vs plotly
- plotOutput() vs plotlyOutput()
- renderPlot() vs renderPlotly()
- ggplot() vs ggplotly()
That’s all that I can give you for an intro to Shiny for now. If you’d like to learn by yourself, you might wanna check this Shiny gallery from RStudio. There are plenty of beautiful Shiny projects that could inspire us for sure. Stay tuned because I will be posting more about Shiny.
Cheers,