1

I am trying to fix some accessibility issues on one of my Shiny apps. The problem is that Shiny's tabPanel default behavior is that once a tab has been visited its tabindex becomes -1, making it inaccessible for users accessing through keyboard navigation (before being clicked a tab would be tabindex = 0).

The example below shows how keyboard navigation doesn't work for the tabs. I am using Shiny 1.7.2 and R 4.0.3.

I have tried using the library bslib and its page_navbar/nav_panel functions but no luck. I also tried to add HTML to the tabPanel calls and explore if I could do anything through CSS but had no luck. I have investigated the code of these functions to see if I could modify them, but I am not sure how. Maybe it can be done with JS, but I know only the very basics of it. I also tried using htmltools::tagAppendatrribute as suggested in this response, but it doesn't modify the behavior after clicking.

Any pointers or solutions would be great.

library(shiny)

# Define UI 
ui <- navbarPage(
    title = "Test accessibility issue",
    tabPanel(title = "Home", icon = icon("house"), value = "home",
             p("Test")),
    tabPanel(title = "Trends", icon = icon("list-ul"), value = "trends",
             p("Test")),
    tabPanel(title = "Geography", icon = icon("globe"), value = "geo",
             p("Test"))
)

# Define server logic 
server <- function(input, output) {

}

# Run the application 
shinyApp(ui = ui, server = server)
jvilla
  • 109
  • 2
  • 12

1 Answers1

1

What you can do is to use a MutationObserver and listen to changes of the attribute tabindex. If the attribute is set to "-1", you can edit it to e.g. "0". In the following example this is applied to all elements, but you can of course change this.

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        var attributeValue = $(mutation.target).prop(mutation.attributeName);
        if (mutation.attributeName === "tabindex" && attributeValue == "-1") {
            $(mutation.target).prop(mutation.attributeName, "0");
        }
    });
});

$("*").each(function() {
    observer.observe(this, {
        attributes: true
    });
});

enter image description here

library(shiny)

js <- "
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        var attributeValue = $(mutation.target).prop(mutation.attributeName);
        if (mutation.attributeName === 'tabindex' && attributeValue == '-1') {
            $(mutation.target).prop(mutation.attributeName, '0');
        }
    });
});

$('*').each(function() {
    observer.observe(this, {
        attributes: true
    });
});
"

# Define UI
ui <- shinyUI(
    navbarPage(
        header = tags$script(HTML(js)),
        title = "Test accessibility issue",
        tabPanel(
            title = "Home",
            icon = icon("house"),
            value = "home",
            p("Test")
        ),
        tabPanel(
            title = "Trends",
            icon = icon("list-ul"),
            value = "trends",
            p("Test")
        ),
        tabPanel(
            title = "Geography",
            icon = icon("globe"),
            value = "geo",
            p("Test")
        )
    )
)

# Define server logic
server <- function(input, output) {
    
}

# Run the application
shinyApp(ui = ui, server = server)
Jan
  • 2,245
  • 1
  • 2
  • 16
  • Thank so much! That worked like a treat. I was trying to use the JS function document.getElementsByClassName to do something like https://stackoverflow.com/questions/3772438/can-i-dynamically-set-tabindex-in-javascript but this works really well. – jvilla Aug 08 '23 at 10:21