Accessibility Web Development - How to Make R Shiny Apps Accessible

By:
Dario Radečić
July 19, 2022

It's crucial you address R Shiny accessibility early on. Why? Because you want to make sure your data products can be used by all users - inclusivity matters. In a nutshell, that's what web accessibility development means, but you'll learn more about it in the context of R Shiny apps today. To kick things off, we'll start with the theory behind R Shiny accessibility and web accessibility in general. There are some, somewhat strict rules you need to follow if you want to provide the most inclusive user experience. <blockquote>Want to see how users use your R Shiny dashboard? <a href="https://appsilon.com/monitoring-r-shiny-user-adoption/" target="_blank" rel="noopener">Here are three tools for monitoring user adoption</a>.</blockquote> Table of contents: <ul><li><a href="#theory">Accessibility Theory Explained</a></li><li><a href="#a11y-shiny">A11Y and Shiny - Get Started with R Shiny accessibility</a></li><li><a href="#summary">Summary of R Shiny Accessibility</a></li></ul> <hr /> <h2 id="theory">Web Accessibility Theory Explained</h2> As mentioned earlier, <i>accessibility</i> means your data products (e.g., dashboards) can be used equally by all users. To get started, you should look no further than the <a href="https://www.a11yproject.com/" target="_blank" rel="noopener">A11Y project</a>. The term "A11Y" is an abbreviation of the word "accessibility," because there are 11 letters between A and Y. The entire project breaks down accessibility into a checklist anyone can follow. Let's go over a couple of their points. <h3>Don't rely on color to explain the data</h3> Some people are color-blind, so a stacked bar chart with each category represented by a different color doesn't always translate. Also, your charts can be printed in a black and white color scheme, making the coloring super confusing. The <code>ggpattern</code> R package can help. To demonstrate, we'll copy a stacked bar chart source code from the <a href="https://r-graph-gallery.com/48-grouped-barplot-with-ggplot2.html" target="_blank" rel="noopener">ggplot2 example gallery</a> and compare the two libraries. The code snippet below creates a traditional stacked bar chart, where each colored segment of a bar represents one category: <pre><code class="language-r">library(ggplot2) <br>specie &lt;- c(rep("sorgho", 3), rep("poacee", 3), rep("banana", 3), rep("triticum", 3)) condition &lt;- rep(c("normal", "stress", "Nitrogen"), 4) value &lt;- abs(rnorm(12, 0, 15)) data &lt;- data.frame(specie, condition, value) <br>ggplot(data, aes(fill = condition, y = value, x = specie)) +    geom_bar(position = "stack", stat = "identity")</code></pre> <img class="size-full wp-image-13660" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3b5519e83c01aface20_eec43e96_1-3.webp" alt="Image 1 - ggplot2 stacked bar chart" width="2156" height="1624" /> Image 1 - ggplot2 stacked bar chart Just imagine if you were color-blind - it would be either extremely difficult or impossible to distinguish between the categories. That's why adding patterns is helpful. The following code snippet adds a distinct pattern to each category: <pre><code class="language-r">library(ggpattern) <br>ggplot(data, aes(fill = condition, y = value, x = specie)) +    geom_col_pattern(        aes(pattern = condition, fill = condition, pattern_fill = condition),        colour = "black",        pattern_colour = "white",        pattern_density = 0.15,        pattern_key_scale_factor = 1.3    )</code></pre> <img class="size-full wp-image-13662" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3b57a1cb06c0305f3bd_74c8bd05_2-3.webp" alt="Image 2 - ggpattern stacked bar chart" width="2156" height="1624" /> Image 2 - ggpattern stacked bar chart It doesn't matter if you can't tell the difference between colors, or if the chart gets printed in shades of gray - everyone can spot a pattern and identify the individual categories. Want to expand your bar charts skills in ggplot2? <a href="https://appsilon.com/ggplot2-bar-charts/" target="_blank" rel="noopener">Check out our guide to bar charts</a> and try to make them more accessible. <h3>Don't use very bright or low-contrast colors</h3> Your background and foreground colors should be different enough. But what classifies as <i>enough</i>? That can be tricky to decide. Luckily, free online tools such as <a href="https://contrastchecker.com/" target="_blank" rel="noopener">contrastchecker.com</a> allow you to verify if your colors pass the tests. There are six tests on the website currently. Don't worry about what they mean, just remember that green is good, and red is bad. Here's an example. A white background color with black text is clear and easy for everyone to see: <img class="size-full wp-image-13664" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3b688e64fe97f77a73f_f82c8209_3-3.webp" alt="Image 3 - Contrast checker - white and black" width="1928" height="1264" /> Image 3 - Contrast checker - white and black Black and white are the exact opposite colors, so the human eye has little difficulty identifying the characters. But what about yellow letters on a white background? Let's see: <img class="size-full wp-image-13666" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3b734291e95149af3fe_63fadac5_4-3.webp" alt="Image 4 - Contrast checker - white and yellow" width="1860" height="1504" /> Image 4 - Contrast checker - white and yellow Suddenly, things went south. Reading the yellow text on a white background causes your eyes to strain. It's difficult even with typical color perception. For that reason, all of the six tests have failed. <h3>Don't overwhelm the user with the information</h3> If you're a statistician or a data scientist, it's easy to think everyone will understand your dashboards just because you do. It's a common fallacy. But the reality is most users are not following your internal logic. <b>Don't include dozens of charts on a single dashboard page!</b> It's a bad design practice that will leave your users confused, concerned, and likely disoriented. Make one chart a focus of the dashboard and add a couple of helper charts around it. That's enough. If you can't convey a message with a handful of data visualizations, consider simplifying your message or re-evaluate your approach. Also, you should use consistent language and color. If charts have identical X-axis, you shouldn't name it differently across plots. Likewise, using red and blue in one chart and green and yellow on the other is just confusing, especially if the chart types are identical. Be consistent! <h3>Translate the data into clear language</h3> Every data visualization can be simplified. But you don't have to remove elements to simplify things - you can also add to simplify. Even if the chart looks simple enough to you, you can simplify it further by providing additional information. The <code>ggplot2</code> R package allows you to easily add titles, and subtitles, but that's only the starting point. Let's take a look at the following visualization. It's sort of a progress bar, and it shows how many users have registered to the company's newsletter as of 2022/05/15 vs. how many registrations the company needs: <pre><code class="language-r">library(ggplot2) <br>registered_users &lt;- 559 target_users &lt;- 1500 <br>ggplot() +    geom_col(aes("", target_users), alpha = 0.5, color = "black") +    geom_col(aes("", registered_users), fill = "#0199f8") +    coord_flip() +    ggtitle("Number of user registrations vs. the target") </code></pre> <img class="size-full wp-image-13668" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3b7f870288447681016_3b55fd7c_5-3.webp" alt="Image 5 - A bad example of a progress bar" width="2412" height="2096" /> Image 5 - A bad example of a progress bar There are many things wrong with this visualization. For starters, it's near impossible to read how many users have registered so far. We also don't know up to which date the chart shows data, nor what the data really represents. We know these are registrations, but for what? Always provide additional context! It makes the data interpretation clearer and more accessible. Here's an example: <pre><code class="language-r">library(ggplot2) <br>registered_users &lt;- 559 target_users &lt;- 1500 <br>format_text &lt;- function(registered, target) {    pct &lt;- round((registered / target) * 100, 2)    paste0(registered, "/", target, " users registered - ", pct, "%") } <br>ggplot() +    geom_col(aes("", target_users), alpha = 0.5, color = "black") +    geom_col(aes("", registered_users), fill = "#0199f8") +    geom_text(        aes("", y = 65, label = format_text(registered_users, target_users)),        hjust = 0,        fontface = "bold",        size = 10,        color = "white"    ) +    coord_flip() +    theme_minimal() +    theme(        axis.title = element_blank(),        axis.text = element_blank(),        axis.ticks = element_blank(),        panel.grid.major = element_blank(),        panel.grid.minor = element_blank(),        panel.background = element_blank()    ) +    labs(        title = "Number of user registrations vs. the target",        subtitle = paste0("As of 2022/05/15, ", registered_users, " users have registered for our company's weekly newsletter. Our target is ", target_users, " and we plan to achieve it by the end of the year.")    ) </code></pre> <img class="size-full wp-image-13670" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b2ac5571d14fbf782e76c2_6-3.webp" alt="Image 6 - A good example of a progress bar" width="2238" height="1866" /> Image 6 - A good example of a progress bar <h3>Other A11Y guidelines</h3> We'll only cover a handful of A11Y guidelines. But there's an <a href="https://www.a11yproject.com/checklist/" target="_blank" rel="noopener">exhaustive checklist</a> you can find and follow on their website, and we encourage you to do so. In general, here are guidelines you should remember about R Shiny accessibility. We gathered them from an <a href="https://www.youtube.com/watch?v=l_U3hQ6mm60" target="_blank" rel="noopener">RStudio Meetup</a> on data visualization accessibility presented by two advocates and contributors to the R and Shiny community, Maya Gans and Mara Averick. We recommend you watch the video, you'll even catch some of these points put into action in the presentations: <ul><li><b>Don't rely on color to explain the data.</b> Use chart patterns as we explained earlier.</li><li><b>Don't use very bright or low-contrast colors.</b> It just looks bad and in some cases makes it impossible to read the text.</li><li><b>Don't hide important data behind interactions.</b> Hover events aren't possible on mobile, and most users will access your dashboard from a smartphone.</li><li><b>Don't overwhelm users with the information.</b> Showing 500 charts on a single dashboard page is just awful.</li><li><b>Use accessibility tools when designing.</b> Google Lighthouse and the A11Y project checklist are good places to start.</li><li><b>Use labels and legends.</b> Otherwise, how will the user know what the data represents?</li><li><b>Translate the data into clear language.</b> Every chart can be simplified. Make sure you have your target user in mind.</li><li><b>Provide context and explain the visualization.</b> Annotate individual data points on a chart (e.g., all-time high, all-time low, dates).</li><li><b>Focus on accessibility during user interviews.</b> Create narratives of people who will use the app. Person X in HR will use the dashboard differently than Person Y, the CFO.</li></ul> Now you know the theory, so next, we'll see how accessibility translates to R Shiny. <h2 id="a11y-shiny">A11Y and Shiny - Get Started with R Shiny accessibility</h2> Here's the good news - you don't need to lift a finger regarding R Shiny accessibility, as someone already did all the heavy lifting for you. The <a href="https://github.com/ewenme/shinya11y" target="_blank" rel="noopener">shinya11y</a> package is a huge time saver, and comes will every A11Y guideline and checklist. The best part? It integrates seamlessly into your Shiny apps. To start, let's install the package: <pre><code class="language-r">devtools::install_github("ewenme/shinya11y")</code></pre> You can launch the demo Shiny app from the R console to get the gist of it: <pre><code class="language-r">shinya11y::demo_tota11y()</code></pre> <img class="size-full wp-image-13672" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3b97d51e4abfae78116_8e0c5c93_7-2.webp" alt="Image 7 - Demo shinya11y app" width="2312" height="2044" /> Image 7 - Demo shinya11y app The button on the bottom left corner of the app allows you to toggle various checklists. You can, for example, see if your app breaks one or more A11Y rules for accessibility. If a highlighted portion is red, you know there's something you'll need to address. But the best part about the package is that you can easily integrate it into your own R Shiny apps. For demonstration, we'll reuse an R Shiny application from our <a href="https://appsilon.com/monitoring-r-shiny-user-adoption/" target="_blank" rel="noopener">Tools for Monitoring User Adoption article</a>. The dashboard applies a clustering algorithm to the Iris dataset and lets you change the columns you want to see on a scatter plot: <pre><code class="language-r">library(shiny) <br>ui &lt;- fluidPage(    headerPanel("Iris k-means clustering"),    sidebarLayout(        sidebarPanel(            selectInput(                    inputId = "xcol",                    label = "X Variable",                    choices = names(iris)                ),            selectInput(                    inputId = "ycol",                    label = "Y Variable",                    choices = names(iris),                    selected = names(iris)[[2]]                ),            numericInput(                inputId = "clusters",                label = "Cluster count",                value = 3,                min = 1,                max = 9            )        ),        mainPanel(            plotOutput("plot1")        )    ) ) <br>server &lt;- function(input, output, session) {    selectedData &lt;- reactive({        iris[, c(input$xcol, input$ycol)]    })    clusters &lt;- reactive({        kmeans(selectedData(), input$clusters)    })    output$plot1 &lt;- renderPlot({        palette(c(            "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",            "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"        ))                par(mar = c(5.1, 4.1, 0, 1))        plot(selectedData(),                col = clusters()$cluster,                pch = 20, cex = 3        )        points(clusters()$centers, pch = 4, cex = 4, lwd = 4)    }) } <br>shinyApp(ui = ui, server = server)</code></pre> <img class="size-full wp-image-13674" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3ba109e5d60f59c390d_00f78eed_8-2.webp" alt="Image 8 - Clustering R Shiny application" width="2260" height="1920" /> Image 8 - Clustering R Shiny application To add <code>shinya11y</code>, you'll need to modify two things: <ol><li>Import the <code>shinya11y</code> library.</li><li>Add a call to <code>use_tota11y()</code> at the top of Shiny UI.</li></ol> Here's what the modified app looks like in code: <pre><code class="language-r">library(shiny) library(shinya11y) <br>ui &lt;- fluidPage(    use_tota11y(),    headerPanel("Iris k-means clustering"),    sidebarLayout(        sidebarPanel(            selectInput(                inputId = "xcol",                label = "X Variable",                choices = names(iris)                ),            selectInput(                inputId = "ycol",                label = "Y Variable",                choices = names(iris),                selected = names(iris)[[2]]                ),            numericInput(                inputId = "clusters",                label = "Cluster count",                value = 3,                min = 1,                max = 9            )        ),        mainPanel(            plotOutput("plot1")        )    ) ) <br>server &lt;- function(input, output, session) {    selectedData &lt;- reactive({        iris[, c(input$xcol, input$ycol)]    })    clusters &lt;- reactive({        kmeans(selectedData(), input$clusters)    })    output$plot1 &lt;- renderPlot({        palette(c(            "#E41A1C", "#377EB8", "#4DAF4A", "#984EA3",            "#FF7F00", "#FFFF33", "#A65628", "#F781BF", "#999999"        ))                par(mar = c(5.1, 4.1, 0, 1))        plot(selectedData(),                col = clusters()$cluster,                pch = 20, cex = 3        )        points(clusters()$centers, pch = 4, cex = 4, lwd = 4)    }) } <br>shinyApp(ui = ui, server = server)</code></pre> And here's what it looks like when launched: <img class="size-full wp-image-13676" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3bb7a1cb06c0305f936_019cb606_9-1.webp" alt="Image 9 - Clustering R Shiny application with accessibility tools" width="2260" height="1920" /> Image 9 - Clustering R Shiny application with accessibility tools It really is that simple. You can now add these two lines to any existing Shiny app and see if there's anything that needs improvement. <hr /> <h2 id="summary">Summary of Web Accessibility Development for R Shiny Accessibility</h2> If you're just starting out, accessibility might be tough to wrap your head around. It's easy to neglect some rules and guidelines if you don't experience the same challenges others face. But if you go the extra mile, you'll ensure that your Shiny app, data visualization, or presentation, will be inclusive. We believe building tech solutions should be no different than any other engineering project; it should be inviting, accessible to all, and provide high-quality service to every user. Now it's time for you to shine. For a homework assignment, pick up any of your R Shiny dashboards and plug in the <code>shinya11y</code> package. See what needs addressing and make sure to do so. If you're ready, share your results with us on Twitter - <a href="https://twitter.com/appsilon" target="_blank" rel="noopener">@appsilon</a>. We'd love to see how accessible Shiny can become. <blockquote>Ready to take your data visualization skills to the next level? <a href="https://appsilon.com/key-data-visualization-principles/" target="_blank" rel="noopener">Here are 5 key data visualization principles you must know</a>.</blockquote> <h2>Additional Resources for Accessible Color and Design</h2> Additional resources to help you create accessible visualizations for color Blindness: <ul><li><a href="https://mariechatfield.com/simple-pdf-viewer/" target="_blank" rel="noopener">Vision Deficiency Viewer</a></li><li><a href="https://cran.r-project.org/web/packages/colorBlindness/vignettes/colorBlindness.html" target="_blank" rel="noopener">Color Blindness guide and package</a></li><li><a href="https://jfly.uni-koeln.de/color/" target="_blank" rel="noopener">Color Universal Design</a></li><li><a href="https://contrast-finder.tanaguru.com/" target="_blank" rel="noopener">Contrast Finder</a></li><li><a href="https://www.color-blindness.com/coblis-color-blindness-simulator/" target="_blank" rel="noopener">Color Blindness Simulator</a></li><li><a href="https://colorbrewer2.org/#type=sequential&amp;scheme=BuGn&amp;n=3" target="_blank" rel="noopener">Cartography Color Guide</a></li><li><a href="https://colorspace.r-forge.r-project.org/articles/color_vision_deficiency.html" target="_blank" rel="noopener">Color Vision Deficiency Emulator</a></li></ul>

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!

Is Your Software GxP Compliant?

Download a checklist designed for clinical managers in data departments to make sure that software meets requirements for FDA and EMA submissions.
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
shiny
shiny dashboards
r
r community
tutorials