Skip to content

Two A11y-related issues with pickerInputs #745

@BajczA475

Description

@BajczA475

I ran into this issue today and had to fix it with a little JS, so I had ChatGPT whip up a reprex:

library(shiny)
library(shinyWidgets)

ui <- fluidPage(
  tags$h3("Reprex: pickerInput Accessibility Issues"),
  
  tags$p("Inspect this app in a browser using WAVE (https://wave.webaim.org/) or similar accessibility checker."),
  
  pickerInput(
    inputId = "collector_county",
    label = "County",
    choices = c("Hennepin", "Ramsey", "Dakota", "Anoka"),
    selected = NULL,
    options = list(
      `liveSearch` = TRUE,
      `mobile` = TRUE
    )
  ),
  
  tags$hr(),
  
  tags$p("Known issues with pickerInput:"),
  tags$ol(
    tags$li("The native <select> element is hidden using CSS that includes transparent text (e.g., color: rgba(..., 0)), which triggers a contrast error."),
    tags$li("The visible <button> element has a title attribute that duplicates its visible text (e.g., 'No selection'), which causes redundancy warnings in accessibility tools.")
  ),
  
  tags$p("These issues do not impact visual usability but trigger accessibility errors, which could impact conformance or trigger audits.")
)

server <- function(input, output, session) {}

shinyApp(ui, server)
Image Image

As this reprex shows, the default pickerInput will get flagged by WAVE for two issues. With the US adopting WCAG 2.1AA by April of next year, having to resolve these compliance flags is a hassle that will limit use if they aren't resolved.

These two functions are drop-in fixes that ChatGPT made for me to put into the UI. I confirmed they do solve the issue (at least for me):

suppressNativeSelectAccessibility = function(inputId) {
  tags$script(HTML(sprintf("
    $(document).ready(function() {
      var el = document.getElementById('%s');
      if (el) {
        el.setAttribute('aria-hidden', 'true');
        el.setAttribute('tabindex', '-1');
        el.style.visibility = 'hidden';
        el.style.position = 'absolute';
        el.style.width = '0';
        el.style.height = '0';
        el.style.overflow = 'hidden';
        el.style.pointerEvents = 'none';
        el.style.color = 'inherit';  // prevent contrast warnings
        el.style.backgroundColor = 'inherit';
      }
    });
  ", inputId)))
}

stripPickerDuplicateTitles = function(inputId) {
  tags$script(HTML(sprintf("
    $(document).ready(function() {
      // Bootstrap-select renders a <button> after setup
      // Use a short delay to wait for rendering
      setTimeout(function() {
        var button = document.querySelector('button[data-id=\"%s\"]');
        if (button) {
          var labelText = button.innerText.trim();
          var titleText = button.getAttribute('title')?.trim();

          if (titleText && labelText === titleText) {
            button.removeAttribute('title');
          }
        }
      }, 100);
    });
  ", inputId)))
}

I include them in case they help provide ideas for permanent solutions.

Thanks for your work on shinyWidgets--I prefer pickerInputs to selectInputs by a mile!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions