Skip to content

fix: Hide/show nav keyboard nav bug#1275

Merged
elnelson575 merged 20 commits intomainfrom
fix/hidden-navs
Jan 20, 2026
Merged

fix: Hide/show nav keyboard nav bug#1275
elnelson575 merged 20 commits intomainfrom
fix/hidden-navs

Conversation

@elnelson575
Copy link
Contributor

@elnelson575 elnelson575 commented Jan 6, 2026

Issue

When there is a nav_panel_hidden() item in a navset_pill_list(), once the spot where it would be if it was visible is hit with keyboard navigation (ex. if you have Tab 1, Hidden Tab 2, Tab 3, after you press an arrow or tab after Tab 1), extra space is added in the <ul> well and the content of the hidden tab is shown, while the nav panel loses focus.

Problem:
This essentially breaks the nav for users navigating via the keyboard, because they both lose focus unexpectedly and end up in content that they shouldn't.

Cause:
nav_panel_hidden() creates tabs with title=NULL and removes the padding via CSS. The liTag() function renders the <li> elements, but they are marked hidden.
However, Bootstrap 5's keyboard navigation uses the .nav-link class to find navigable tabs and hidden tabs still have this class, even though they are hidden.

Status:
Adding the disabled attribute and a CSS class to hide based on that does work to eliminate the initial issue (keyboard users can no longer stumble upon it, there is no awkward gap.
It does not affect the behavior if nav_select() or nav_show()-- the tab content will be shown.

  • Tested BS5
  • Tested BS4
  • Tested BS3 (minor update needed, then retested all with the update)

BS5:
https://github.com/user-attachments/assets/dd0057c0-5ea8-4788-a3d5-3c608f97a268

BS4:
https://github.com/user-attachments/assets/cf0b0afd-a8eb-4376-9a8a-955ea647560c

BS3:
https://github.com/user-attachments/assets/f4009a29-ec4b-48c4-a797-20a12420c5b4

App Code Commented portion is BS5 only, so tested only in that version.
library(shiny)
library(bslib)

ui <- page_fluid(
  title = "All navset_*() Layouts Demo",
  theme = bs_theme(version = 4),
  h2("navset_tab()"),
  p("Horizontal tabs with content below"),
  navset_tab(
    id = "navset_tab",
    nav_panel("Tab 1", "Content for tab 1"),
    nav_panel("Tab 2", "Content for tab 2"),
    nav_panel("Tab 3", "Content for tab 3"),
    nav_panel_hidden("tab_hidden", "Hidden tab content")
  ),
  actionButton("show_tab_hidden", "Show Hidden Tab"),

  hr(),

  h2("navset_pill()"),
  p("Pill-style navigation"),
  navset_pill(
    id = "navset_pill",
    nav_panel("Pill 1", "Content for pill 1"),
    nav_panel("Pill 2", "Content for pill 2"),
    nav_panel("Pill 3", "Content for pill 3"),
    nav_panel_hidden("pill_hidden", "Hidden pill content")
  ),
  actionButton("show_pill_hidden", "Show Hidden Pill"),

  hr(),

  # navset_underline() requires Bootstrap 5
  # h2("navset_underline()"),
  # p("Underlined navigation style"),
  # navset_underline(
  #   id = "navset_underline",
  #   nav_panel("Option 1", "Content for option 1"),
  #   nav_panel("Option 2", "Content for option 2"),
  #   nav_panel("Option 3", "Content for option 3"),
  #   nav_panel_hidden("underline_hidden", "Hidden underline content")
  # ),
  # actionButton("show_underline_hidden", "Show Hidden Underline"),

  hr(),

  # navset_card_tab() requires Bootstrap 5 (uses card())
  # h2("navset_card_tab()"),
  # p("Tabs within a card header"),
  # navset_card_tab(
  #   id = "navset_card_tab",
  #   title = "Card with Tabs",
  #   selected = "Card Tab 1",
  #   nav_panel(
  #     "Card Tab 1",
  #     "This is the content for Card Tab 1",
  #     tags$ul(
  #       tags$li("Item 1"),
  #       tags$li("Item 2"),
  #       tags$li("Item 3")
  #     )
  #   ),
  #   nav_panel("Card Tab 2", "This is the content for Card Tab 2"),
  #   nav_panel("Card Tab 3", "This is the content for Card Tab 3"),
  #   nav_panel_hidden("card_tab_hidden", "Hidden card tab content")
  # ),
  # actionButton("show_card_tab_hidden", "Show Hidden Card Tab"),

  hr(),

  # navset_card_pill() requires Bootstrap 5 (uses card())
  # h2("navset_card_pill()"),
  # p("Pills within a card (above placement)"),
  # navset_card_pill(
  #   id = "navset_card_pill_above",
  #   title = "Card with Pills",
  #   placement = "above",
  #   selected = "Card Pill 1",
  #   nav_panel(
  #     "Card Pill 1",
  #     "This is the content for Card Pill 1",
  #     tags$ul(
  #       tags$li("Feature A"),
  #       tags$li("Feature B"),
  #       tags$li("Feature C")
  #     )
  #   ),
  #   nav_panel("Card Pill 2", "This is the content for Card Pill 2"),
  #   nav_panel("Card Pill 3", "This is the content for Card Pill 3"),
  #   nav_panel_hidden(
  #     "card_pill_above_hidden",
  #     "Hidden card pill content (above)"
  #   )
  # ),
  # actionButton("show_card_pill_above_hidden", "Show Hidden Card Pill (Above)"),
  #
  # hr(),
  # hr(),
  # hr(),
  #
  # h2("navset_card_pill() with below placement"),
  # p("Pills within a card footer (below placement)"),
  # navset_card_pill(
  #   id = "navset_card_pill_below",
  #   title = "Card with Pills Below",
  #   placement = "below",
  #   selected = "Card Pill 1",
  #   nav_panel(
  #     "Card Pill 1",
  #     "This is the content for Card Pill 1 (below placement)",
  #     tags$ul(
  #       tags$li("Option 1"),
  #       tags$li("Option 2"),
  #       tags$li("Option 3")
  #     )
  #   ),
  #   nav_panel("Card Pill 2", "This is the content for Card Pill 2"),
  #   nav_panel("Card Pill 3", "This is the content for Card Pill 3"),
  #   nav_panel_hidden(
  #     "card_pill_below_hidden",
  #     "Hidden card pill content (below)"
  #   )
  # ),
  # actionButton("show_card_pill_below_hidden", "Show Hidden Card Pill (Below)"),

  hr(),

  # navset_card_underline() requires Bootstrap 5
  # h2("navset_card_underline()"),
  # p("Underlined navigation within a card"),
  # navset_card_underline(
  #   id = "navset_card_underline",
  #   title = "Card with Underline",
  #   selected = "Underline 1",
  #   nav_panel(
  #     "Underline 1",
  #     "This is the content for Underline 1",
  #     tags$ul(
  #       tags$li("Detail A"),
  #       tags$li("Detail B"),
  #       tags$li("Detail C")
  #     )
  #   ),
  #   nav_panel("Underline 2", "This is the content for Underline 2"),
  #   nav_panel("Underline 3", "This is the content for Underline 3"),
  #   nav_panel_hidden("card_underline_hidden", "Hidden card underline content")
  # ),
  # actionButton("show_card_underline_hidden", "Show Hidden Card Underline"),

  hr(),

  h2("navset_pill_list()"),
  p("Vertical pill list navigation"),
  navset_pill_list(
    id = "navset_pill_list",
    nav_panel("List Item 1", "Content for list item 1"),
    nav_panel("List Item 2", "Content for list item 2"),
    nav_panel("List Item 3", "Content for list item 3"),
    nav_panel_hidden("pill_list_hidden", "Hidden pill list content")
  ),
  actionButton("show_pill_list_hidden", "Show Hidden Pill List"),

  hr(),

  h2("navset_bar()"),
  p("Navigation bar at the top"),
  navset_bar(
    id = "navset_bar",
    title = "Nav Bar Title",
    nav_panel("Bar 1", "Content for nav bar 1"),
    nav_panel("Bar 2", "Content for nav bar 2"),
    nav_panel("Bar 3", "Content for nav bar 3"),
    nav_menu(
      "More Options",
      nav_panel("Menu Item 1", "Menu content 1"),
      nav_panel("Menu Item 2", "Menu content 2")
    ),
    nav_panel_hidden("bar_hidden", "Hidden nav bar content")
  ),
  actionButton("show_bar_hidden", "Show Hidden Nav Bar"),

  hr(),

  h2("navset_hidden()"),
  p("Hidden navigation (programmatically controlled)"),
  navset_hidden(
    id = "hidden_navset",
    nav_panel_hidden("hidden1", "This is hidden panel 1"),
    nav_panel_hidden("hidden2", "This is hidden panel 2"),
    nav_panel_hidden("hidden3", "This is hidden panel 3")
  ),
  div(
    class = "btn-group",
    actionButton("show_hidden1", "Show Panel 1"),
    actionButton("show_hidden2", "Show Panel 2"),
    actionButton("show_hidden3", "Show Panel 3")
  )
)

server <- function(input, output, session) {
  # Control hidden panels in navset_tab
  observeEvent(input$show_tab_hidden, {
    nav_select("navset_tab", "tab_hidden")
  })

  # Control hidden panels in navset_pill
  observeEvent(input$show_pill_hidden, {
    nav_select("navset_pill", "pill_hidden")
  })

  # navset_underline() requires Bootstrap 5
  # # Control hidden panels in navset_underline
  # observeEvent(input$show_underline_hidden, {
  #   nav_select("navset_underline", "underline_hidden")
  # })

  # navset_card_tab() requires Bootstrap 5 (uses card())
  # # Control hidden panels in navset_card_tab
  # observeEvent(input$show_card_tab_hidden, {
  #   nav_select("navset_card_tab", "card_tab_hidden")
  # })

  # navset_card_pill() requires Bootstrap 5 (uses card())
  # # Control hidden panels in navset_card_pill (above)
  # observeEvent(input$show_card_pill_above_hidden, {
  #   nav_select("navset_card_pill_above", "card_pill_above_hidden")
  # })
  #
  # # Control hidden panels in navset_card_pill (below)
  # observeEvent(input$show_card_pill_below_hidden, {
  #   nav_select("navset_card_pill_below", "card_pill_below_hidden")
  # })

  # navset_card_underline() requires Bootstrap 5
  # # Control hidden panels in navset_card_underline
  # observeEvent(input$show_card_underline_hidden, {
  #   nav_select("navset_card_underline", "card_underline_hidden")
  # })

  # Control hidden panels in navset_pill_list
  observeEvent(input$show_pill_list_hidden, {
    nav_select("navset_pill_list", "pill_list_hidden")
  })

  # Control hidden panels in navset_bar
  observeEvent(input$show_bar_hidden, {
    nav_select("navset_bar", "bar_hidden")
  })

  # Control hidden navset
  observeEvent(input$show_hidden1, {
    nav_select("hidden_navset", "hidden1")
  })

  observeEvent(input$show_hidden2, {
    nav_select("hidden_navset", "hidden2")
  })

  observeEvent(input$show_hidden3, {
    nav_select("hidden_navset", "hidden3")
  })
}

shinyApp(ui, server)


To do:
Check if this is a problem elsewhere (ex. vanilla shiny) and/or in pyshiny as well.

@elnelson575 elnelson575 self-assigned this Jan 14, 2026
Removed unnecessary comment about Bootstrap documentation.
@elnelson575 elnelson575 marked this pull request as ready for review January 14, 2026 16:14
@elnelson575 elnelson575 requested a review from gadenbuie January 14, 2026 16:14
Copy link
Member

@gadenbuie gadenbuie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, just a few more details to wrap up

elnelson575 and others added 6 commits January 14, 2026 15:16
Copy link
Member

@gadenbuie gadenbuie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks!

Co-authored-by: Garrick Aden-Buie <garrick@adenbuie.com>
@elnelson575 elnelson575 merged commit b86ca0e into main Jan 20, 2026
12 checks passed
@elnelson575 elnelson575 deleted the fix/hidden-navs branch January 20, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nav_panel_hidden() visible and keyboard navigable to in navset_pill_list()

2 participants