Skip to contents

Overview

glassTabsUI() provides an animated glass-morphism tab navigation bar for Shiny. The active tab is tracked by a sliding glass halo with spring easing, and a luminous transfer trace sweeps across intermediate tabs during navigation.

Basic usage

Three functions work together:

library(shiny)
library(glasstabs)

ui <- fluidPage(
  useGlassTabs(),
  glassTabsUI("nav",
    glassTabPanel("overview", "Overview", selected = TRUE,
      h3("Overview"),
      p("This pane is shown first.")
    ),
    glassTabPanel("details", "Details",
      h3("Details"),
      p("Switch to this tab using the button above.")
    ),
    glassTabPanel("settings", "Settings",
      h3("Settings"),
      p("A third tab.")
    )
  )
)

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

shinyApp(ui, server)

Reading the active tab in the server

The active tab value is pushed to Shiny automatically on every click via Shiny.setInputValue. Access it as input[["<id>-active_tab"]]:

server <- function(input, output, session) {
  observe({
    req(input[["nav-active_tab"]])
    message("Active tab: ", input[["nav-active_tab"]])
  })

  # Or use the convenience wrapper
  active <- glassTabsServer("nav")
  observe(message("Active: ", active()))
}

Keyboard navigation

Arrow keys move between tabs when focus is inside the widget — no extra code needed.

Placing a filter widget beside the tabs

Pass any UI element to extra_ui to place it to the right of the tab bar. The most common use is a glassMultiSelect() filter:

choices <- c(Alpha = "alpha", Beta = "beta", Gamma = "gamma")

ui <- fluidPage(
  useGlassTabs(),
  glassTabsUI("nav",
    extra_ui = glassMultiSelect("cat", choices, show_style_switcher = FALSE),
    glassTabPanel("a", "Tab A", selected = TRUE,
      p("Content A"),
      glassFilterTags("cat")   # tag pills synced to the filter
    ),
    glassTabPanel("b", "Tab B",
      p("Content B"),
      glassFilterTags("cat")
    )
  )
)

Theming

Built-in presets

# Dark (default)
glassTabsUI("nav", theme = "dark", ...)

# Light — suits white page backgrounds and bs4Dash cards
glassTabsUI("nav", theme = "light", ...)

Custom theme with glass_tab_theme()

Supply only the values you want to change — everything else falls back to the dark preset:

# Change only the halo colour
glassTabsUI("nav",
  theme = glass_tab_theme(
    halo_bg         = "rgba(251,191,36,0.18)",
    halo_border     = "rgba(251,191,36,0.40)",
    tab_active_text = "#fef3c7"
  ),
  glassTabPanel("a", "Tab", selected = TRUE, p("Content"))
)

All eight handles available in glass_tab_theme():

Argument What it controls
tab_text Inactive tab label colour
tab_active_text Active tab label colour
halo_bg Sliding glass halo fill
halo_border Sliding glass halo border
content_bg Tab content panel background
content_border Tab content panel border
card_bg Inner card background
card_text Inner card text colour

bs4Dash integration

Set wrap = FALSE when embedding inside a bs4Card or bs4Box — the card body already provides a constrained container. Pair with theme = "light" to match the card’s white background:

library(bs4Dash)
library(glasstabs)

ui <- bs4DashPage(
  header  = bs4DashNavbar(title = "My App"),
  sidebar = bs4DashSidebar(disable = TRUE),
  body    = bs4DashBody(
    useGlassTabs(),
    bs4Card(
      title = "Analysis", width = 12,
      glassTabsUI("dash",
        wrap     = FALSE,
        theme    = "light",
        extra_ui = glassMultiSelect("f", choices, theme = "light",
                                    show_style_switcher = FALSE),
        glassTabPanel("a", "Overview", selected = TRUE, p("Overview content")),
        glassTabPanel("b", "Details",  p("Detail content"))
      )
    )
  )
)

Multiple tab widgets on one page

Each glassTabsUI() instance is scoped to its own id, so multiple widgets work fully independently:

ui <- fluidPage(
  useGlassTabs(),    # only needed once per page
  glassTabsUI("widget1",
    glassTabPanel("a", "One-A", selected = TRUE, p("Widget 1, pane A")),
    glassTabPanel("b", "One-B", p("Widget 1, pane B"))
  ),
  glassTabsUI("widget2",
    glassTabPanel("x", "Two-X", selected = TRUE, p("Widget 2, pane X")),
    glassTabPanel("y", "Two-Y", p("Widget 2, pane Y"))
  )
)

server <- function(input, output, session) {
  observe(message("Widget 1 active: ", input[["widget1-active_tab"]]))
  observe(message("Widget 2 active: ", input[["widget2-active_tab"]]))
}