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)
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!
I ran into this issue today and had to fix it with a little JS, so I had ChatGPT whip up a reprex:
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):
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!