rguides

Building UI Components in Shiny

Shiny provides a rich set of UI components that let you create interactive web applications with R. In this tutorial, you’ll learn how to use text inputs, sliders, dropdowns, buttons, and layout functions to build professional-looking Shiny apps.

What you’ll learn

This tutorial covers the key concepts and practical techniques for working with Building UI Components in Shiny. By the end, you will know how to apply the core functions in real data analysis workflows.

Text and numeric inputs

The most common input widgets let users enter text or numbers.

library(shiny)

ui <- fluidPage(
  textInput("name", "Enter your name:"),
  numericInput("age", "Enter your age:", value = 25, min = 0, max = 150)
)

server <- function(input, output) {}

shinyApp(ui, server)

The textInput() function creates a text field where users can type strings. The numericInput() function restricts input to numbers and lets you set minimum and maximum values.

Sliders

Sliders are perfect for selecting values within a range.

ui <- fluidPage(
  sliderInput("confidence", "Confidence Level:",
              min = 0, max = 100, value = 95),
  sliderInput("range", "Select Range:",
              min = -100, max = 100, value = c(-50, 50))
)

server <- function(input, output) {}

shinyApp(ui, server)

You can create single-value sliders or range sliders by providing a two-element vector for the default value.

Use selectInput() for dropdown menus and radioButtons() for visible option lists.

ui <- fluidPage(
  selectInput("country", "Select Country:",
              choices = c("USA", "UK", "Canada", "Australia")),
  radioButtons("color", "Favorite Color:",
               choices = c("Red", "Green", "Blue"))
)

server <- function(input, output) {}

shinyApp(ui, server)

The selectInput() function creates a dropdown, while radioButtons() displays all options at once.

Checkboxes

Checkbox inputs let users select multiple options or toggle a single boolean.

ui <- fluidPage(
  checkboxInput("subscribe", "Subscribe to newsletter", value = FALSE),
  checkboxGroupInput("features", "Select Features:",
                    choices = c("Charts", "Tables", "Maps"))
)

server <- function(input, output) {}

shinyApp(ui, server)

Use checkboxInput() for a single toggle and checkboxGroupInput() for multiple selections.

Action buttons

Action buttons trigger specific events in your app.

ui <- fluidPage(
  actionButton("clickme", "Click Me!"),
  actionLink("gotolink", "Go to Documentation")
)

server <- function(input, output) {
  observeEvent(input$clickme, {
    message("Button was clicked!")
  })
}

shinyApp(ui, server)

The observeEvent() function listens for button clicks and executes code in response.

Date and file inputs

Shiny supports date selection and file uploads.

ui <- fluidPage(
  dateInput("startdate", "Start Date:"),
  dateRangeInput("daterange", "Date Range:"),
  fileInput("upload", "Upload File:", accept = c(".csv", ".xlsx"))
)

server <- function(input, output) {}

shinyApp(ui, server)

Use dateInput() for single dates, dateRangeInput() for ranges, and fileInput() for file uploads.

Layout functions

Organize your UI with layout functions.

ui <- fluidPage(
  titlePanel("My Shiny App"),
  
  fluidRow(
    column(4, 
           textInput("name", "Name:")),
    column(4,
           numericInput("age", "Age:", 25)),
    column(4,
           actionButton("submit", "Submit"))
  ),
  
  fluidRow(
    column(12,
           plotOutput("myplot"))
  )
)

server <- function(input, output) {}

shinyApp(ui, server)

The fluidPage() function creates a responsive page layout. Use fluidRow() to create rows and column() to specify width (1-12).

Putting it all together

Here’s a complete example combining several UI components:

library(shiny)

ui <- fluidPage(
  titlePanel("Data Entry Form"),
  
  fluidRow(
    column(6,
           textInput("username", "Username:"),
           passwordInput("password", "Password:")),
    column(6,
           selectInput("role", "Role:",
                      choices = c("Admin", "User", "Guest")))
  ),
  
  fluidRow(
    column(12,
           sliderInput("priority", "Priority:",
                      min = 1, max = 10, value = 5))
  ),
  
  fluidRow(
    column(12,
           actionButton("submit", "Submit", class = "btn-primary"),
           actionButton("reset", "Reset", class = "btn-secondary"))
  )
)

server <- function(input, output) {}

shinyApp(ui, server)

Layout system

Shiny uses Bootstrap’s grid system for layout. fluidRow() creates a row; column(width, ...) creates columns within it (widths are on a 1-12 scale). sidebarLayout() creates the classic sidebar + main panel pattern. navbarPage() creates a multi-tab interface with a navigation bar. tabsetPanel() creates tabs within a single page.

Input widgets

The core input widgets: sliderInput() for numeric ranges, selectInput() for dropdown menus, checkboxGroupInput() for multi-select, dateRangeInput() for date pickers, fileInput() for file upload, textInput() for text fields, numericInput() for numbers. Each widget has an inputId that is read in the server as input$inputId.

Output elements

plotOutput() displays plots; tableOutput() renders data frames as HTML tables; DT::dataTableOutput() renders interactive DT tables; textOutput() shows text; uiOutput() renders dynamic UI generated in the server. Match each output function to its render function: renderPlot(), renderTable(), renderDT(), renderText(), renderUI().

Dynamic UI

renderUI() generates UI elements in the server based on reactive inputs. This enables dynamic dropdowns that update based on a filter selection, conditionally showing input widgets, or generating a variable number of panels. req() inside renderUI() prevents the server from running before the inputs it depends on are available, it silently stops execution and waits for valid inputs.

Styling with bslib

bslib::bs_theme() customizes Bootstrap themes for Shiny apps. Pass the theme to shinyApp(ui = fluidPage(theme = bs_theme(...))). bslib::value_box() creates styled KPI cards. The thematic package extends the Bootstrap theme to ggplot2 and base R plots, keeping all visuals consistent with the Shiny app’s color scheme.

Input widgets overview

Shiny provides a comprehensive set of input widgets. Each widget has an inputId that uniquely identifies it, and a label displayed next to the control. The server accesses the current value with input$inputId.

Numeric inputs: numericInput(inputId, label, value, min, max, step) creates a text field that only accepts numbers. sliderInput(inputId, label, min, max, value) creates a slider. Range sliders use value = c(low, high) to set both endpoints. sliderInput with animate = TRUE adds a play button for animated transitions.

Text inputs: textInput() for single-line text, textAreaInput() for multi-line text. Both have placeholder for hint text and width for sizing. passwordInput() masks input with dots.

Selection inputs: selectInput() and selectizeInput() for dropdowns (selectize uses a searchable JavaScript control). radioButtons() shows all options at once. checkboxGroupInput() allows multiple selections. checkboxInput() is a single checkbox returning TRUE/FALSE.

Dynamic UI generation

renderUI() and uiOutput() create server-generated UI. The server returns a UI element (or NULL to hide): output$dynamic_panel <- renderUI({ if (input$show_options) { sliderInput(...) } else { NULL } }). In the UI, uiOutput("dynamic_panel") places the element.

updateSelectInput(), updateSliderInput(), updateTextInput() modify existing inputs without re-rendering them. updateSelectInput(session, "choices_input", choices = new_choices, selected = new_selected) replaces the choices in a dropdown and sets the selected value — useful for cascading dropdowns where the options in one widget depend on the selection in another.

insertUI() and removeUI() add and remove arbitrary UI elements dynamically. insertUI(selector = "#container", where = "beforeEnd", ui = tags$div(id = "item_1", "Content")) inserts a div inside #container. removeUI(selector = "#item_1") removes it. This powers dynamic form generation where the number of inputs varies.

Layout systems

fluidRow() and column(width, ...) implement Bootstrap’s 12-column grid. Columns in a row sum to 12: two equal columns are column(6, ...) each; a 3-9 split uses column(3, ...) and column(9, ...). Columns stack vertically on small screens by default.

sidebarLayout() with sidebarPanel() and mainPanel() is the standard layout for filter-plus-output dashboards. navbarPage() creates a multi-tab application with a top navigation bar. tabsetPanel() creates tabs within a panel.

bslib::page_sidebar() and bslib::layout_columns() are the modern replacement for fluidRow()/column(), using CSS grid rather than Bootstrap’s float-based grid. They handle responsive layouts more reliably on small screens.

HTML and custom CSS

tags$div(), tags$span(), tags$h3() generate HTML elements in R. All standard HTML tags are available through htmltools::tags. tags$div(class = "myclass", id = "myid", "Content") produces <div class="myclass" id="myid">Content</div>.

tags$style(HTML(".myclass { color: red; }")) injects CSS. For larger style blocks, use includeCSS("www/styles.css") to load a CSS file from the www/ directory. Similarly, includeScript("www/script.js") loads JavaScript.

shinyjs::useShinyjs() in the UI enables the shinyjs package, which provides functions like shinyjs::hide(), shinyjs::show(), shinyjs::toggleClass(), and shinyjs::runjs() for JavaScript execution from the server side.

Notification and modal dialogs

showNotification() displays a toast-style message at the corner of the screen. Options: type = "message" (blue), "warning" (yellow), "error" (red). duration = NULL keeps the notification until dismissed; a numeric value auto-dismisses after that many seconds.

showModal(modalDialog(title = "Confirm", "Are you sure?", footer = tagList(modalButton("Cancel"), actionButton("confirm", "Confirm")))) opens a modal dialog. removeModal() closes it. Modal dialogs are the correct pattern for confirmation prompts and detail views.

showModal() paired with observeEvent(input$confirm, { removeModal(); do_action() }) creates a confirm-then-act pattern without navigating away from the current view.

Inputs as the entry point

Shiny inputs are the UI elements that accept user interaction. Sliders, dropdowns, checkboxes, date pickers, text boxes, and file upload widgets all create reactive values in the server’s input object. Every input widget has an id argument that becomes the accessor name in the server: a slider with id = “n_bins” is read in the server as input$n_bins. The ID must be unique within an application.

Input widgets have default values that determine what the user sees before interacting. Setting sensible defaults reduces the number of required interactions before the user sees useful output. For inputs that control computationally expensive operations, consider adding a submit button with actionButton and wrapping the reactive expression in eventReactive so computation only happens when the user explicitly requests it rather than on every change.

Layout building blocks

Shiny’s layout functions compose hierarchically. fluidPage creates a full-width responsive page. Inside it, fluidRow divides the page into rows, and column inside a row allocates width in units out of twelve. Panels, tabs, and sidebars build on this grid. The sidebarLayout pattern — a narrow sidebar for inputs and a wider main panel for outputs — is the most common layout for data exploration apps.

shinydashboard and bslib provide more sophisticated layout options. bslib implements Bootstrap components including cards, value boxes, and navsets with theming support. For applications that need a dashboard aesthetic with KPI metrics, navigation, and multiple panels, these packages produce polished layouts without custom CSS. The choice between them depends on the Bootstrap version and design system you want to target.

Dynamic and conditional UI

Static UI is defined once in the ui object and never changes. Dynamic UI uses uiOutput and renderUI to generate UI elements from server code. This enables conditional inputs that appear only when relevant, input lists that change based on data, and dropdowns populated from data rather than hardcoded in the UI definition.

Dynamic UI is powerful but has performance implications. renderUI re-creates and re-renders the entire UI element every time the reactive expression it depends on changes. For complex UI elements, this can cause visible flicker. Use updateSelectInput, updateSliderInput, and related update functions to modify existing inputs without recreating them — these partial updates are faster and do not cause layout reflow.

Summary

Shiny’s UI components make it easy to build interactive web applications with R. Key takeaways:

  • Text inputs: textInput(), passwordInput() for text entry
  • Numeric inputs: numericInput() for number entry with validation
  • Sliders: sliderInput() for selecting values in a range
  • Selection inputs: selectInput(), radioButtons(), checkboxGroupInput()
  • Buttons: actionButton(), actionLink() for triggering events
  • Special inputs: dateInput(), dateRangeInput(), fileInput()
  • Layout: fluidPage(), fluidRow(), column() for organizing components

In the next tutorial, we’ll cover deploying your Shiny app to the web so others can use it.

Next steps

Now that you understand building ui components in shiny, explore these related topics to deepen your knowledge and apply these techniques in more complex scenarios.