javascripthtmlrshinytabindex

How to modify the tabindex in R Shiny?


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)

Solution

  • 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)