Chapter 7 Appendix: Accessibility in R Shiny

R shiny is an increasingly powerful tool for creating interactive data dashboards and websites in R. As noted in Jamie Owen’s blog, allowing data scientists with little experience in website design and development to create powerful and visually appealing apps is a major benefit to R Shiny, but this lack of required experience, makes it easy to create inaccessible products 5. Common issues result from built-in User Interface (UI) functions rendering hypertext markup language (HTML) that cannot be keyboard navigable or parsed by screen readers. Additionally, some default color schemes do not meet suggested contrast ratios. However, you can leverage R’s main strength, its flexibility, to correct the default, inaccessible behavior.

This guide provides a quick overview of how to design, develop, and assess accessibility in R Shiny app. The first section, Accessibility Standards, will provide a high-level look at the current accessibility guidelines The second section, Designing Accessible Shiny Apps, highlights some tips for presenting information. The third section, Developing Accessible Shiny Apps, explores some strategies for increasing the accessibility of your Shiny app. The final section, Assessing Accessibility, explains how to use a variety of tools for testing the accessibility of a finished product.

7.1 Accessibility Standards

Web accessibility describes the ability of people with disabilities to interact with and interpret the content of a web page. Here, disability includes visual, auditory, physical, speech, cognitive, language, learning, and neurological disabilities. Many people with disabilities use assistive technology (e.g., screen readers) to enhance their ability to interact with web content. Therefore, accessible webpages are interpretable by both humans and machines. This also means that a webpage may appear accessible, but the underlying code is uninterpretable by a screen reader.

For web developers who do not have disabilities, it is difficult to know how users with disabilities will interact with and interpret the content of a webpage or if screen readers will parse the content as expected. To aid in the development of accessible websites, the World Wide Web Consortium (W3C) created the Web Content Accessibility Guidelines (WCAG). The WCAG seek to “make content more accessible to a wider range of people with disabilities, including accommodations for blindness and low vision, deafness and hearing loss, limited movement, speech disabilities, photosensitivity, and combinations of these…” 6. Such accommodation also improves the experience of users without disabilities.

The current version of WCAG, 2.1, consists of several success criteria across 13 guidelines organized under 4 principles. Each success criterium defines 3 levels of conformance—in order of lowest to highest: A, AA, and AAA. Table 1 shows the 13 guidelines and the related principles. The full guidelines can be found on the W3C website.

Title II of the Americans with Disabilities Act (ADA) requires state and local governments to comply with all A and AA success criteria with some limited exceptions.

Table 7.1: Table 1; Summary of Web Content Accessibility Guidelines (WCAG) version 2.1.
Guideline Criteria
Perceivable: web content is made available to the senses—sight, hearing, and/or touch
1.1—Provide text alternatives for any non-text content 1.1.1 Non-text content (A)
1.2—Provide alternatives for time-based media 1.2.1 Audio-only and Video-only Prerecorded (A); 1.2.2 Captions Prerecorded (A); 1.2.3 Audio Description or Media Alternative Prerecorded (A): 1.2.4 Captions Live (AA); 1.2.5 Audio Description Prerecorded (AA); 1.2.6 Sign Language Prerecorded (AAA); 1.2.7 Extended Audio Description Prerecorded (AAA); 1.2.8 Media Alternative Prerecorded (AAA); 1.2.9 Audio-only Live (AAA)
1.3—Create content that can be presented in different ways without losing information or structure 1.3.1 Info and Relationships (A); 1.3.2 Meaningful Sequence (A); 1.3.3 Sensory Characteristics (A); 1.3.4 Orientation (AA); 1.3.5 Identify Input Purpose (AA); 1.3.6 Identify Purpose (AAA)
1.4—Make it easier for users to see and hear content including separating foreground from background 1.4.1 Use of Color (A); 1.4.2 Audio Control (A); 1.4.3 Contrast Minimum (A); 1.4.4 Resize Text (AA); 1.4.5 Images of Text (AA); 1.4.6 Contrast Enhanced (AAA); 1.4.7 Low or No Background Audio (AAA); 1.4.8 Visual Presentation (AAA); 1.4.9 Images of Text No Exception (AAA); 1.4.10 Reflow (AA); 1.4.11 Non-text Contrast (AA); 1.4.12 Text Spacing (AA); 1.4.13 Content on Hover or Focus (AA)
Operable: inferface forms, controls, and navigation are operable
2.1—Make all functionality available from a keyboard 2.1.1 Keyboard (A); 2.1.2 No Keyboard Trap (A); 2.1.3 Keyboard No Exception (AAA); 2.1.4 Character Key Shortcuts (A)
2.2—Provide users enough time to read and use content 2.2.1 Timing Adjustable (A); 2.2.2 Pause, Stop Hide (A); 2.2.3 No Timing (AAA); 2.2.4 Interruptions (AAA); 2.2.5 Re-authenticating (AAA); 2.2.6 Timeouts (AAA)
2.3—Do not design content in a way that is known to cause seizures 2.3.1 Three Flashes or Below Threshold (A); 2.3.2 Three Flashes (AAA); 2.3.3 Animations from Interactions (AAA)
2.4—Provide ways to help users navigate, find content, and determine where they are 2.4.1 Bypass Blocks (A); 2.4.2 Page Titled (A); 2.4.3 Focus Order (A); 2.4.4 link Purpose In Context (A); 2.4.5 Multiple Ways (AA); 2.4.6 Headings and Labels (AA); 2.4.7 Focus Visible (AA); 2.4.8 Location (AAA); 2.4.9 Link Purpose Link Only (AAA); 2.4.10 Section Headings (AAA); 2.4.11 Focus Not Obscured Minimum (AA); 2.4.12 Focus Not Obscured Enhanced (AAA); 2.4.13 Focus Appearance (AAA)
2.5—Make it easier for users to operate functionality through various inputs beyond keyboard 2.5.1 Pointer Gestures (A); 2.5.2 Pointer Cancellation (A); 2.5.3 Label in Name (A); 2.5.4 Motion Actuation (A); 2.5.5 Target Size (AAA); 2.5.6 Concurrent Input Mechanisms (AAA); 2.5.7 Dragging Movements (AA); 2.5.8 Target Size Minimum (AA)
Understandable: information and the operation of user interface must be understandable
3.1—Make text content readable and understandable 3.1.1 Language of Page (A); 3.1.2 Language of Parts (AA); 3.1.3 Unusual Words (AAA); 3.1.4 Abbreviations (AAA); 3.1.5 Reading Level (AAA); 3.1.6 Pronunciation (AAA)
3.2—Make web pages appear and operate in predictable ways 3.2.1 On Focus (A); 3.2.2 On Input (A); 3.2.3 Consistent Navigation (AA); 3.2.4 Consistent Identification (AA); 3.2.5 Change on Request (AAA); 3.2.6 Consistent Help (A)
3.3—Help users avoid and correct mistakes 3.3.1 Error Identification (A); 3.3.2 Labels or Instructions (A); 3.3.3 Error Suggestion (AA); 3.3.4 Error Prevention (AA); 3.3.5 Help (AAA); 3.3.6 Error Prevention All (AAA); 3.3.7 Redundant Entry (A); 3.3.8 Accessible Authentication Minimum (AA); 3.3.9 Accessible Authentication Enhanced (AAA)
Robust: content must be robust enough that it can be interpreted reliably by a wide variety of user agents, including assistive technologies
4.1—Maximize compatibility with current and future user agents, including assistive technologies 4.1.2 Name, Role, Value (A); 4.1.3 Status Messages (AA)

7.2 Designing Accessible Shiny Apps

The first step to creating an accessible Shiny app is considering accessibility at the beginning of the design process. Early decisions—including what information to present and how to present it, layout, and color schemes—have a large impact on the accessibility of an app for both disabled and non-disabled users. For a deep-dive on crafting and communicating accessible written information, see the Guidelines for Communicating Epidemiology and Public Health. In short, engage audiences with personalized content to aid understanding, reduce the information to a few pieces of memorable key points, and meet audience reading level and comfort with data visualization.

As part of meeting the WCAG principle of perceivability, one aspect of designing accessible websites is maximizing content readability by assistive technology. Since screen readers easily parse plain text, more text-focused apps are more accessible than those with non-text elements. Obviously, some balance with visual elements is needed to engage non-disabled users, but, generally, more textual elements will improve accessibility while also facilitating comprehension of visual elements. This does not mean that text must be minimalist. For example, the value_box() function from the bslib package allows for creating appealing text boxes, and the text can still be parsed by screen readers 7.

After deciding what content your app will share, consider how your app will display that content. The default Shiny layout creates a single column layout where elements are left-justified and arranged in rows according to the order specified in the UI function. This default arrangement is easily understood by screen readers and people, but, like plain text, is not always visually appealing. The bslib package, again, offers a way to balance accessibility with visual variety using the layout_columns() function. layout_columns() divides the webpage into a twelve-column layout, and elements can take up one to twelve columns of horizontal space. Dividing the page into multiple columns allows you to arrange multiple elements in the same row provided that the total row width does not exceed twelve. Shiny will create a new row for remaining elements after all twelve columns are filled. Row height is determined by the elements in a row or can be specified. The layout_columns() framework allows for more visually appealing designs that preserve readability by screen readers, particularly by defining element order and limiting elements outside of a consistent structure (e.g., callout boxes, floating elements, etc.). Another way to increase accessibility in the design stage is to use page titles and headings to break up text as recommended in WCAG guideline 2.4. Page titles orient users, including those using screen readers, to the webpage. Headings provide similar orientation at the page level and help users skim content.

When designing other elements (e.g, data visualizations, banners, text boxes, etc.), carefully consider the use of color. Many users’ ability to interpret content is affected by color including those with colorblindness or limited vision. There are many types of colorblindness (some more common than others), but a key trait shared by most is a limited ability to distinguish between two or more colors. This does not mean that a colorblind user will, for example, be unable to detect red (although some may not), but that they may have difficulty distinguishing between nearby red and green pixels. This problem commonly presents in data visualizations where data are color coded. In this scenario, it can be difficult to tell which data are red and which are green. While red-green colorblindness is the most common, the same issue exists for other colors 8. To accommodate different color vision, consider color schemes that avoid red-green combinations such as a blue-purple-red scheme. Color also affects non-colorblind users’ ability to interact with apps. Primarily, the contrast between foreground and background is critical to ensure readability. The current WCAG success measures (see WCAG guideline 1.4) recommend at least a 4.5:1 contrast ratio between foreground and background. 9 In addition to ensuring colors are colorblind-friendly and have sufficient contrast, consider adding a color-free way for interpreting the content such as using symbols, texture, and text to supplement information provided by color.

7.3 Developing Accessible Shiny Apps

Once you have a general design and layout in mind, there are several ways to increase accessibility while building the Shiny app. Shiny UI functions generally create accessible apps, but there are several elements to add and default behaviors to alter to improve accessibility.

If your design calls for images, WCAG guideline 1.1 suggests adding descriptive alternative (alt) text enables access to those using screen readers. For tips on writing alt text, visit Harvard’s Digital Accessibility page. The following code shows how to add alt text to your app.

ui <- fluidPage(
  tags$img(
    src = "yourimage.jpg",
    alt = "text describing your image"
           )
)

Just as alt text describes images, link text describes where an external link directs a user. WCAG guideline 2.4 recommends adding link text to describe the purpose of the link. Link text is also more visually appealing than a URL and improves search engine optimization (SEO). The following HTML code shows how to add descriptive text to a link.

<a href = "url here"> link text here </a>

As stated in the previous section, titles and headings break up text and help orient users. To add a simple title element, use the titlePanel() function at the beginning of your UI. The titlePanel() function takes two strings as arguments: one for the full title and an optional argument for a short title to display in the browser tab. The following code shows a more complicated example of a title banner that adds a background, logo (with alt text), and a subtitle. An easy way to add headers to your app is to use R Markdown documents in your UI to render text rather than adding character strings to your UI. R Markdown documents can also offer more flexible options for changing font, colors, and adding other static elements. The heading system in R Markdown uses a # symbol to indicate first-level headings, ## to indicated sub-headings, and ### to indicate sub-sub-headings. The resulting HTML generated from rendering a markdown document preserves this organization and can then be parsed by screen readers. To add a markdown document to your UI, use the includeMarkdown() function as exemplified below.

ui <- fluidPage(
  includeMarkdown("path to markdown.Rmd")
)

When your app has text elements, screen readers interpret them better when it knows what language the text is, especially if the user’s screen reader is also attempting to translate the content. To facilitate this translation, WCAG guideline 3.1 recommends adding a “lang attribute” to your HTML that specifies what language the content is in. The following code shows how to add a lang attribute (here, English) to your app.

ui <- fluidPage(
  tags$html(lang = "en"),
  #other app content...
)

So far, this guide has discussed adding elements to a Shiny app’s UI to improve accessibility, but some default behavior of UI functions does not meet WCAG requirements and must be overridden with cascading stye sheets (CSS) or JavaScript (JS). CSS controls the appearance of app elements including colors and sizes. Custom CSS can alter the default appearance of UI elements to match branding, enhance visual appeal, and improve accessibility (see above discussion on color). To change an element’s appearance, first identify its “selector” (i.e., name) and the “property” (e.g., background-color, padding, text-decoration, etc.). The selector can either be the HTML element (e.g., h1), the HTML class (e.g., .checkbox), or the HTML id. The easiest way to find this information is to run your app prototype in the browser and use the “inspect” feature to locate the HTML corresponding to the relevant element. The following code uses custom CSS change the appearance of an actionButton().

#using HTML element as selector
"button {
  border: 2px solid #FFFFF;
  border-radius: 5px;
}"

#using HTML class as selector
".btn-default {
  background-color: #F2F2F2;
  color: white;
  font-size: 18px;
}"

#using HTML ID as selector
"#myButton {
  font-weight: bold;
  text-align: center;
}"

You can incorporate custom CSS in your app directly in the UI function (called inline CSS) or by referencing a separate CSS script. Adding inline CSS is the better option when only making minor changes to a few elements. If you are making several changes throughout the UI, consider referencing a separate CSS file (.css). The following code examples demonstrate both approaches.

#inline CSS
ui <- fluidPage(
  tags$head(
    tags$style(HTML(
      "button {
        background-color: #F2F2F2;
        color: white;
        font-size: 18px;
      }
      "
    ))
  ),
  #other app content...
)

#file-based CSS
#store your CSS (without wrapping quotation marks) in a .css file stored in a folder called www
ui <- fluidPage(
  tags$head(
    tags$link(rel = "stylesheet", type = "text/css", href = "custom.css")
  ),
#other app content...
)

CSS, however, can only alter the appearance of elements and cannot change their behavior. For example, the navlistPanel() function creates a navigation menu for your app, and custom CSS can change the default text, highlight, and hover colors, but the menu does not meet WCAG criteria for keyboard operability. This criteria requires clickable elements to also operate using only the keyboard. Keyboard operability requires an element to have a positive value for its tab index (tabindex in HTML), but navlistPanel() only renders the selected page to have a positive index and sets all other to a negative value. Fixing this behavior requires overriding using custom JS. Under the hood, Shiny uses JS to link changes in the server function to changes in the HTML. Like custom CSS, custom JS can be added either inline or by referencing a separate JS file (.js). The following code demonstrates changing the tab index of navlistPanel() using both inline and referenced JS code.

#inline
ui <- fluidPage(
  tags$head(
    tags$script(HTML(
      "function fixTabindex() {
                const links = document.querySelectorAll('ul[data-tabsetid] a.nav-link[role=\"tab\"]');
                links.forEach(link => {
                  if (link.getAttribute('tabindex') !== '0') {
                    link.setAttribute('tabindex', '0');
                  }
                });
              }
              
              // Run once when page loads
              $(document).ready(function() {
                setTimeout(fixTabindex, 500);
              });
              
              // Run after any click on the navigation
              $(document).on('click', 'ul[data-tabsetid] a.nav-link', function() {
                setTimeout(fixTabindex, 100);
              });"
    ))
  ),
  #other app content...
)

#file-based
#store your JS (without wrapping quotation marks) in a .js file stored in a folder called www
ui <- fluidPage(
  tags$head(
    tags$script(src = "custom.js")
  ),
  #other app content...
)

7.4 Assessing Accessibility

After designing and developing a Shiny app, the final step to optimized accessibility is to test compliance with existing guidance and regulations. The best way to test accessibility is to receive feedback from disabled users or other experts either through a paid service or crowdsourcing, but there are more preliminary, immediate, and cost-effective tools.

The shinya11y package is a quick way to assess many accessibility guidelines while still in a development environment 10. Running an app with the use_tota11y() function opens a floating viewer that checks for several success criteria and shows how a screen reader may interpret the app content. The viewer also suggests HTML changes to improve compliance.

Other tools work best once your app is published with an associated URL or running in a browser. The W3C offers two tools for assessing accessibility: the web accessibility evaluation tool (WAVE) and the markup validation service. WAVE is a browser extension that generates an accessibility report for your app, and the markup validation service is an HTML validator that detects potential accessibility issues in your app’s HTML. The Social Security Administration’s accessible name and description inspector (ANDI) is an online tool similar to shinya11y that assesses conformance to ADA accessibility regulations and highlights elements that may be inaccessible. Finally, Google Chrome’s Lighthouse tool generates a graded report for accessibility as well as performance speed, compliance with best practices, and SEO. Use of any of the above tools, or implementation of the above considerations, does not guarantee the ability of all users to interact with your Shiny app. However, implementing these strategies and assessing accessibility using a combination of tools will increase the accessibility of your app and increase its utility for a wider variety of potential users.


  1. Owen, J (2022). Accessibility in R applications: {shiny}. Jumping Rivers. https://www.jumpingrivers.com/blog/accessible-shiny-standards-wcag/.↩︎

  2. Kirkpatrick, A., O’Connor, J., Campbell, A., & Cooper, M. (2025). Web content accessibility guidelines (WCAG) 2.1. W3C. https://www.w3.org/TR/WCAG21/.↩︎

  3. Sievert C, Cheng J, Aden-Buie G (2025). bslib: Custom ‘Bootstrap’ ‘Sass’ Themes for ‘shiny’ and ‘rmarkdown’. R package version 0.9.0, https://github.com/rstudio/bslib, https://rstudio.github.io/bslib/.↩︎

  4. National Eye Institute (2023). Types of color vision deficiency. https://www.nei.nih.gov/learn-about-eye-health/eye-conditions-and-diseases/color-blindness/types-color-vision-deficiency.↩︎

  5. Black text on a white background has a contrast ratio of 21:1, while yellow text on a white background has a contrast ratio of 1.15:1.↩︎

  6. Henderson E, Scales J, Yates J (2025). shinya11y: Accessibility (a11y) Tooling for ‘Shiny’. R package version 0.0.0.9000, commit 4f17db8f0c06e7332258e6c499e3c4af36fd4a17, https://github.com/ewenme/shinya11y.↩︎