Site icon R-bloggers

Accessibility in R applications: {shiny}

[This article was first published on The Jumping Rivers Blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Web applications that are Web Content Accessibility Guidelines (WCAG) compliant are becoming an increasingly prominent part of my role as a data scientist as the importance of ensuring that data products are available to all takes a more central focus. This is particularly true in the case of building solutions for public sector organisations in the UK as they are under a legal obligation to meet certain accessibility requirements.

{shiny} has, for some time now, been a leading route that statisticians, analysts and data scientists might take to provide a web based application as a graphical user interface to data manipulation, graphical and statistical tooling that may otherwise only be easily accessible to R programmers.

At Jumping Rivers we were recently tasked with taking a prototype product, which we initially helped to develop in {shiny}, to a public facing production environment for a public sector client. This blog post highlights some of the thoughts that arose throughout the scoping stage of that project when assessing {shiny} as a suitable candidate for the final solution.


Do you require help building a Shiny app? Would you like someone to take over the maintenance burden? If so, check out our Shiny and Dash services.


Accessibility and {shiny}

The good

The great thing about {shiny} is that it allows data practitioners a relatively simple, quick approach to providing an intuitive user interface to their R code via a web application. So effective is {shiny} at this job that it can be done with little to no traditional web development knowledge on the part of the developer. {shiny} and associated packages provide collections of R functions that return HTML, CSS and JavaScript which is then shipped to a browser. The variety of packages giving trivial access to styled front end components and widgets is already large and constantly growing. What this means is that R programmers can achieve a huge amount in the way of building complex, visually attractive web applications without needing to care very much about the underlying generated content that is interpreted by the browser.

Need a tidy menu to drive navigation around your application? {shinydashboard} is a fine choice. If you want an attractive table to display data, that also facilitates download, sorting, pagination plus numerous other “bolt-ons” then many R users will point you in the direction of {DT}. {plotly} has you covered for interactive charts, again allowing you to do things like download snapshots from your plots for use elsewhere.

This sort of technology is absolutely fantastic for prototyping products. The feedback loop through iteration from initial idea, manipulation and modelling code, rough design and layout to usable and deployable application can be phenomenally fast. That is not to say that shiny is completely inappropriate beyond the prototyping stage, just that, certainly in my opinion, this is absolutely one of its biggest strengths.

The bad

If it is good that shiny allows data and statistics experts to create web applications without any real knowledge of front end technology then it is almost certainly also bad that shiny allows data and statistics experts to create web applications without any real knowledge of front end technology. To my mind, its big strength is also a weakness. Much or all of the browser interpreted content is generated for you. This is particularly prominent when considering WCAG compliance.

Let’s take a look at how some of this problem is manifested:

Consider the following snippet of code which I suspect is something reflective of a large number of shiny applications given the popularity of the package.

library("shinydashboard")
ui = shinydashboardPlus::dashboardPage(
  skin = "purple",
  header = dashboardHeader(title = "My App"),
  sidebar = dashboardSidebar(
    sidebarMenu(
      id = "sidebarMenu",
      menuItem("Home page", tabName = "Home")
    )
  ),
  body = dashboardBody(
    id = "dashboardBody",
    tabItems(
      tabItem(tabName = "Home", "Hello Dashboard")
    )
  )
)

# Note that I would typically namespace all function calls
# and encourage others to do the same, however for the
# purpose of a blog post, loading the packages via `library`
# may make it a little easier to read on small screen formats.

If we were to start a shiny app in the usual sort of way

server = function(input, output, session){
  # empty server function
  # not important for discussion but necessary
  # to launch an application.
}
shiny::shinyApp(ui, server)

this gives the not entirely unattractive UI below (subjective I know).

It does so by generating the following markup as the output of the R code which is shipped off to the browser to be rendered.

<body data-scrollToTop="0" class="hold-transition skin-purple" data-skin="purple" style="min-height: 611px;">
  <div class="wrapper">
    <header class="main-header">
      <span class="logo">My App</span>
      <nav class="navbar navbar-static-top" role="navigation">
        <span style="display:none;">
          <i class="fa fa-bars" role="presentation" aria-label="bars icon"></i>
        </span>
        <a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
          <span class="sr-only">Toggle navigation</span>
        </a>
        <div class="navbar-custom-menu">
          <ul class="nav navbar-nav"></ul>
        </div>
      </nav>
    </header>
    <aside id="sidebarCollapsed" class="main-sidebar" data-collapsed="false">
      <section id="sidebarItemExpanded" class="sidebar">
        <ul class="sidebar-menu">
          <li>
            <a href="#shiny-tab-Home" data-toggle="tab" data-value="Home">
              <span>Home page</span>
            </a>
          </li>
          <div id="sidebarMenu" class="sidebarMenuSelectedTabItem" data-value="null"></div>
        </ul>
      </section>
    </aside>
    <div class="content-wrapper">
      <section class="content" id="dashboardBody">
        <div class="tab-content">
          <div role="tabpanel" class="tab-pane" id="shiny-tab-Home">Hello Dashboard</div>
        </div>
      </section>
    </div>
  </div>
</body>

So what’s the problem? Well, even though our application has almost zero content to it, it would fail even the most basic of accessibility tests. Chrome based browsers have a tool, Lighthouse, accessible from the developer console in the browser, which can provide a report on accessibility for a web page. This is by no means a comprehensive WCAG compliance assessment, but seems like a reasonable first hurdle to get over.

A Lighthouse report, whilst reminding us that

Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged

gives the following on our “app”:

The ugly

On the assumption I have the above app and want to stick with {shiny} and {shinydashboard} what can I do to solve these flagged issues?

Document does not have a <title> element

<html> element does not have a [lang] attribute

Lists do not only contain <li> elements and script supporting elements

[aria-*] attributes do not match their roles

So we are now at a state where even though we start to patch functions generating the UI code, things are happening outside of my direct control which make it extremely difficult to force this package to comply with WCAG. And we are completely ignoring all the things that Lighthouse doesn’t pick up on. To name some:

So scrap {shiny}?

Is {shiny} a terrible solution when wanting to build an accessible web app then? Well not necessarily, at the end of the day, all {shiny} does is wrap front end content in R functions. You can still write R functions that will generate WCAG compliant HTML. But… and I think it is quite a big but, making a {shiny} application WCAG compliant requires a bit more thought and attention, and almost certainly means not using all your favourite libraries. It was ignored in the previous section but {DT} and {plotly}, both mentioned as great packages for common {shiny} app components, also do not give WCAG compliant markup. {plotly} in particular is very problematic in this arena, still one of my favourite plotting solutions for R, but not amenable to an accessible application. In short, you will have to roll your own a bit more.

There are tools to help you assess your application. Lighthouse in the browser was used in the above discussion, there are other tools like Koa11y for generating reports which I find give more info and there is a {shinya11y} R package which aims to help specifically with {shiny}. Having said that none of these tools are perfect.

How does this story end?

In summary, it is entirely possible to create fully accessible {shiny} applications, however I think there is a lot of work to be done by developers of packages for {shiny} to ease the burden somewhat as at present a lot of my favourite packages leave me with too much hacking to do to solve the problem. For the particular project referenced in the opening remarks, the requirement to be WCAG compliant plus some additional constraints meant that an alternative solution based on {plumber} and a separate front end was developed. In my initial report I remarked to the client that a {shiny} solution could be developed and I maintain that view now, however I am a little bit happy that we opted for an alternative. I do love {shiny} and will continue to use it a lot, but it is not the only solution we have available to us and until it becomes a little easier to create accessible applications with some complexity to them I can’t strongly recommend it for every application.


For updates and revisions to this article, see the original post

To leave a comment for the author, please follow the link and comment on their blog: The Jumping Rivers Blog.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.