Join the Shiny Community every month at Shiny Gatherings

shiny.emptystate – How To Add an Empty State Component to R Shiny

What happens when there’s no data to show to the user? This can occur in Shiny apps, where users must upload their own data. Well, one solution is to show an empty screen (an empty state). But this negatively impacts the user experience. A better approach would be to have a dedicated empty state screen, and today you’ll implement a Shiny empty state screen with Appsilon’s latest open-source package.

Do you love Shiny, but your apps are slow, dull, or lacking functionality? Explore the open-source Rhinoverse and take your apps to the next level.

If we’re talking about dashboards, there are numerous places where empty state screens come in handy. For example, you could use them to instruct the user to upload a dataset before proceeding with calculations. Or when there’s no data to display at all, potentially due to too many data filters being applied.

So without much ado, let’s dive straight in and leave your users in a better state!

Want to measure the performance of your new empty state changes? Try Appsilon’s shiny.benchmark package.

Table of contents:

Building a Dashboard Logic for Shiny Empty State

Before actually writing the R Shiny app, we must first discuss the logic it will implement. The app will have two screens:

  1. Empty state screen – Instructs the user to upload the dataset, and provides an action button to do so.
  2. Dashboard screen – Renders a chart (combination of a scatter and line chart) that shows the uploaded datasets and results of a fitted machine learning model.

Sounds easy enough, and in this section, we’ll deal with the dataset logic and the ways to fit a machine learning model.

To start, let’s create a dataset. It will have two columns – x and y, where x is a list of numbers between 1 and 100, and y is identical to x, but with added variation of a number between -3 and +3:


x <- seq(1, 100)
y <- to_vec(for (num in x) num + runif(n = 1, min = -3, max = 3))
df <- data.frame(
  x = x,
  y = y


Image 1 – First 6 rows of the random dataset

That’s the dataset we’ll upload later to the dashboard, so it’s a good idea to save it locally. The following code snippet dumps it into a CSV file:

write.csv(df, "data.csv", row.names = FALSE)

Awesome! Now, let’s fit a simple linear regression model. The variable y is dependent, and the variable x is independent. R’s lm() function allows us to easily model that relationship:

model <- lm(df$y ~ df$x)

Here’s the summary of our model:

Image 2 – Summary of a linear regression model

We’ll later extract the model coefficients, but first, let’s calculate the predictions and assign them to new dataframe columns. Put simply, we’re leveraging the patterns the model learned to make predictions on the input variable.

Wait, what is Linear Regression? Here’s our complete guide to simple and multiple linear regression in R.

In the case of linear regression, this just means solving a line equation with the coefficients from the previous image:

predictions <- predict(model, data.frame(df$x))
df$y_hat <- predictions

Here’s what the dataset looks like now:

Image 3 – Random dataset with added predictions

Predictions of a simple linear regression model will always be a straight line. To demonstrate, we’ll plot the original data points and predictions.

The get_model_subtitle() function is responsible for extracting model coefficients from the summary and formatting them as a formula string.

Below, there’s a call to ggplot() in which original data is rendered as a scatter plot (blue), and predictions are rendered as a black line:

get_model_subtitle <- function(model) {
  return(paste0(round(summary(model)$coefficients[1], 3), " + ", round(summary(model)$coefficients[2], 3), "x"))

ggplot(df, aes(x = x, y = y)) +
  geom_point(color = "#0099F9", size = 5, alpha = 0.75) +
  geom_line(aes(x = x, y = y_hat), linewidth = 2) +
    title = "X and Y Relationship",
    subtitle = paste("Formula:", get_model_subtitle(model))
  ) +
  theme_minimal() +
  theme(plot.title = element_text(size = 20, face = "bold"))

Image 4 – Chart showcasing actual data (blue) and predictions (black)

That’s the chart we want to display in R Shiny, but only after the user uploads the dataset. In other words, only after the Shiny empty state is passed.

Empty State in Shiny – Dashboard in Action

We now have everything we need to write a dashboard in R Shiny. Well, everything except the package itself. You can install it with the following command:


The code snippet you’ll see below is simple to follow and understand, but let’s dissect it chunk by chunk:

  • get_model_subtitle() – The function you saw in the previous section, it’s responsible for extracting coefficients from a linear regression model and formatting them as a string equation.
  • empty_state_content – HTML content that’ll be rendered while the state is empty. In our case, it includes a heading and an action button responsible for uploading a CSV file.
  • ui – The user interface of our app once we get passed the empty Shiny state screen. If you’re using shiny.emptystate package, you must include a call to use_empty_state().
  • server() – Shiny function responsible for handling application logic. It must include an instance of EmptyStateManager class in which you define by the id element which element of your dashboard should be covered with the empty state content.
    • The server() function here also contains a call to show() and hide() methods of the empty state manager to, well, show or hide the empty state screen depending on a condition.
    • Finally, the server() function is responsible for applying a machine learning model, calculating predictions, and displaying the chart. You might want to extract this logic when building more complex dashboards.

If you prefer code over words, here’s everything you need to start using Appsilon’s shiny.emptystate package:


# Helper function for formatting the subtitle - model formula
get_model_subtitle <- function(model) {
  return(paste0(round(summary(model)$coefficients[1], 3), " + ", round(summary(model)$coefficients[2], 3), "x"))

# Contents of the empty state - Just a heading and an upload button
empty_state_content <- div(
  h3("Please upload a CSV file with columns \"x\" and \"y\""),
    inputId = "upload_btn",
    label = "Choose CSV File",
#We don't recommend this method, but for this tutorial the simple button below looks better than the default fileInput
    onclick = "document.querySelector('#upload').click();"

ui <- fluidPage(
    id = "chart_container",
    plotOutput(outputId = "chart")
  shinyjs::hidden(fileInput(inputId = "upload", label = "upload"))

server <- function(input, output) {
  # Initialize and show empty state
  empty_state_manager <- EmptyStateManager$new(
    id = "chart_container",
    html_content = empty_state_content
  # Handle dataset upload
  dataset <- reactiveVal()
  uploaded_dataset <- reactive({
  observeEvent(uploaded_dataset(), {
    if (nrow(uploaded_dataset()) > 0) {
    } else {
  # Handle chart output
  output$chart <- renderPlot({
    # ML model
    model <- lm(dataset()$y ~ dataset()$x)
    predictions <- predict(model, data.frame(dataset()$x))
    ggplot(dataset(), aes(x = x, y = y)) +
      geom_point(color = "#0099F9", size = 5, alpha = 0.75) +
      geom_line(aes(x = x, y = predictions), linewidth = 2) +
        title = "X and Y Relationship",
        subtitle = paste("Formula:", get_model_subtitle(model))
      ) +
      theme_minimal() +
      theme(plot.title = element_text(size = 20, face = "bold"))

# Connect all to a Shiny app
shinyApp(ui = ui, server = server)

Let’s run the app to check if everything works:

Image 5 – Shiny app demonstrating shiny.emptystate package

We’ve saved the dataset to a CSV file in the previous section, and used it here. As you can see, the app successfully goes from a Shiny empty state to a dashboard screen, which is just what we’ve wanted.

Let’s make a brief recap next.


R Shiny was designed to be easy for developers, but that doesn’t mean it lacks advanced functionality. This article is a perfect example of a logic you don’t want (nor need) to implement from scratch. It can be included in any app to improve the user experience and instruct the user on what to do.

Long story short, whenever you need to implement an empty state screen in your Shiny apps, look no further than shiny.emptystate. The link contains more examples that might be easier to grasp for newcomers to Shiny. If you get stuck, don’t hesitate to leave a comment below this article, or ping us via Twitter – @appsilon.

Does R Shiny sound like a promising career? Here’s how to get started.