Disclaimer:

The examples and code snippets in this tutorial are adapted from the official Shiny tutorial by Posit (formerly RStudio). Full credit goes to the original authors — this walkthrough simply restructures and supplements their content for a smoother, section-wise learning experience.

1. Introduction to Shiny

What is Shiny?

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.

Packages required

  • library(shiny)
  • library(bslib)

Every Shiny app has three main parts:

  • UI (User Interface): Describes how the app looks
  • Server: Contains instructions to build outputs based on inputs
  • Run the App: shinyApp(ui = ui, server = server)

Shiny App Structure

This is the simplest Shiny app structure. It loads the required library and sets up an empty user interface with no inputs or outputs.

Code

# ---- UI Section ----
# This defines what the app will look like: the layout and input/output elements
library(shiny)

# ui object
ui <- fluidPage( )

# server()
server <- function(input, output){ }

# shinyApp()
shinyApp(ui = ui, server = server)

Output

The app runs successfully but displays a blank page since no elements are included in the UI.

2. User Interface

Shiny apps are built with a clear separation between UI (user interface) and server (logic). This section focuses on designing the UI using layouts like page_sidebar(), and organizing content using sidebar(), card(), and value_box(). These components help create clean, readable, and interactive interfaces for your app users.

card

We now add a card() element to the main panel. Cards help group and visually organize content within the interface.

ui <- page_sidebar(
   title = "title panel",
   sidebar = sidebar("sidebar"),
   card("Card content")
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

The card appears in the main panel, giving a more polished look. Cards are useful for grouping content like text, outputs, or plots.

value_box

Now let’s introduce a value_box() to display key metrics or indicators. These are often used in dashboards to highlight summary stats.

ui <- page_sidebar(
  title = "title panel",
  sidebar = sidebar("Sidebar"),
  value_box(
    title = "Value box",
    value = 100,
    showcase = bsicons::bs_icon("bar-chart"),
    theme = "teal"
  ),
  card("Card"),
  card("Another card")
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

This version displays a sidebar, a value box (with an icon and value), and two cards. It’s a great starting point for dashboards and summary displays.

3. Control Widgets

Widgets are the heart of interactivity in Shiny apps. They allow users to take action — click buttons, check boxes, select values, choose dates, and more. This section explores different types of input widgets that can be added to our user interface to make apps dynamic and user-friendly following the official Shiny tutorial.

Buttons

This example shows how to add an action button and a submit button, which can later be linked to events in the server.

ui <- page_fluid(
  title = "title panel",
  card(
      card_header("Buttons"),
      actionButton("action", "Action"),
      submitButton("Submit")
    )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

Single checkbox

A single checkbox input allows users to toggle between TRUE and FALSE. By default, the checkbox is checked.

ui <- page_fluid(
  title = "title panel",
  card(
      card_header("Single checkbox"),
      checkboxInput("checkbox", "Choice A", value = TRUE)
    )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

Checkbox group

Can use a checkbox group to allow users to select multiple options from a list. Here, “Choice 1” is selected by default.

ui <- page_fluid(
  title = "title panel",
  card(
      card_header("Checkbox group"),
      checkboxGroupInput(
        "checkGroup",
        "Select all that apply",
        choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3),
        selected = 1)
      )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

Radio buttons

Radio buttons let users select only one option from a set. “Choice 1” is pre-selected. This is useful when only one value should be active at a time.

ui <- page_fluid(
  title = "title panel",
card(
      card_header("Radio buttons"),
      radioButtons(
        "radio",
        "Select option",
        choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3),
        selected = 1
      )
    )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

Select box

A dropdown (select box) widget helps conserve space while still offering a list of options.

ui <- page_fluid(
  title = "title panel",
card(
      card_header("Select box"),
      style = "overflow: visible;", 
      selectInput(
      inputId = "select",
      label = "Select option",
      choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3),
      selected = 1),
      
    verbatimTextOutput("selected_val")
  )
)

# Define server logic ----
server <- function(input, output) {
   output$selected_val <- renderText({
    paste("You selected:", input$select)
  }) 
}

# Run the app ----
shinyApp(ui = ui, server = server)

Sliders

Sliders are great for numeric input, either as a single value or a range.
One slider lets the user pick a number, and the other sets a range. These can be used for filtering or setting thresholds.

ui <- page_fluid(
  title = "title panel",
  card(
      card_header("Sliders"),
      sliderInput(
        "slider1",
        "Set value",
        min = 0,
        max = 100,
        value = 50
      ),
      sliderInput(
        "slider2",
        "Set value range",
        min = 0,
        max = 100,
        value = c(25, 75)
      )
    )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

Date range

This widget allows users to pick a start and end date from a calendar popup.
Once a date range is selected, it becomes available in input$dates.

ui <- page_fluid(
  title = "title panel",
  card(
      card_header("Date range input"),
      dateRangeInput("dates", "Select dates")
    )
)

# Define server logic ----
server <- function(input, output) {
  
}

# Run the app ----
shinyApp(ui = ui, server = server)

4. Run the first ShinyApp

This is our first fully functional Shiny app! It connects a slider input to a reactive plot, showing how user actions instantly update outputs.

Outputs in Shiny:

To display something in our app—like a plot, table, or map, we need two things:

  • In the UI, add an *Output() function to create space for the output (e.g., plotOutput() for a plot).
  • In the server(), use a matching render*() function to generate the output (e.g., renderPlot() to draw the plot).

For example:

  • To display a table, use DTOutput() in the UI and renderDT() in the server.
  • To show a plot, use plotOutput() in the UI and renderPlot() in the server.
  • For a Leaflet map, use leafletOutput() in the UI and renderLeaflet() in the server.

This simple pattern works for most Shiny outputs—just match the Output with the render!

Code

# ---- UI Section ----
# This defines what the app will look like: the layout and input/output elements
ui <- fluidPage(
  
  # Title displayed at the top
  titlePanel("My First Shiny App!"),
  
  # Create a layout with a sidebar and a main area
  sidebarLayout(
    
    # Sidebar: contains input elements like sliders, textboxes, etc.
    sidebarPanel(
      sliderInput("num",            # Input ID
                  "Choose a number:", # Label displayed
                  min = 10,          # Minimum value of slider
                  max = 100,         # Maximum value of slider
                  value = 50)        # Default value when the app loads
    ),
    
    # Main panel: contains outputs like plots, text, tables
    mainPanel(
      plotOutput("hist")  # Output ID for the plot
    )
  )
)

# ---- Server Section ----
# This defines the logic behind the app — how inputs affect outputs
server <- function(input, output) {
  
  # Create a plot that reacts to user input
  output$hist <- renderPlot({
    
    # Generate a histogram of random numbers based on slider value
    hist(rnorm(input$num),  # input$num accesses the slider value
         main = paste("Histogram of", input$num, "Random Numbers"), # Title
         col = "steelblue", # Bar color
         border = "white")  # Border color
  })
}

# ---- Run the App ----
# This function launches the app with the defined UI and server
# shinyApp(ui = ui, server = server)

Output

The app displays a slider in the sidebar. As we move the slider, the histogram in the main panel updates in real time based on the number of random values selected.

Wrapping Up

We’ve taken our first steps with Shiny — and that’s something to feel good about!
From sliders to sidebars, we’re now familiar with the basic building blocks of interactive R apps.

But guess what?

The fun is not over — it’s just getting started!
This is only the beginning, and there’s so much more we can do to make our apps shine.
Get ready for part two:

👉 Adding the Shine to Shiny