The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Animated Tabs with glasstabs

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) {}

if (interactive()) 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

Only the selected tab sits in the regular Tab-key order. Left and Right move through a horizontal strip; Up and Down do the same in a vertical rail. Home and End reach the first and last available tabs. Hidden and disabled tabs are left out of that path.

Each tab names its panel through aria-controls, and each panel points back with aria-labelledby. Those relationships stay in place when tabs are added or removed from the server.

Narrow screens and touch

Scroll mode works well for most apps with several tabs:

glassTabsUI(
  "reports",
  glassTabPanel("summary", "Summary", selected = TRUE, summary_ui),
  glassTabPanel("activity", "Recent activity", activity_ui),
  glassTabPanel("quality", "Data quality", quality_ui),
  glassTabPanel("settings", "Team settings", settings_ui),
  overflow = "scroll"
)

The strip accepts touch scrolling and brings the selected tab into view. Use overflow = "multiline" when two or more rows suit the layout. In a horizontal layout, menu mode keeps the same tab content behind a compact native chooser:

glassTabsUI(
  "compact_nav",
  glassTabPanel("queue", "Queue", selected = TRUE, queue_ui),
  glassTabPanel("review", "In review", review_ui),
  glassTabPanel("complete", "Complete", complete_ui),
  overflow = "menu"
)

Swipe navigation is deliberately opt-in:

glassTabsUI(
  "story",
  glassTabPanel("today", "Today", selected = TRUE, today_ui),
  glassTabPanel("week", "This week", week_ui),
  swipe = TRUE
)

Swipes that begin on an input, link, button, Shiny output, HTML widget, or horizontally scrolling element are ignored. A component can opt out directly with data-gt-no-swipe as well.

The widget follows prefers-reduced-motion automatically. In that mode the panel changes immediately and the transfer trace and badge pulse stay quiet.

Indicator styles

Use indicator to choose how the active tab is drawn:

glassTabsUI("nav",
  glassTabPanel("overview", "Overview", selected = TRUE, p("Overview")),
  glassTabPanel("data", "Data", p("Data")),
  glassTabPanel("settings", "Settings", p("Settings")),
  indicator = "solid"
)

The same tabs can be rendered in all three styles without changing their content or server logic:

make_tabs <- function(id, indicator) {
  glassTabsUI(
    id,
    glassTabPanel("overview", "Overview", selected = TRUE, p("Overview")),
    glassTabPanel("data", "Data", p("Data")),
    indicator = indicator,
    compact = TRUE
  )
}

tagList(
  make_tabs("glass_tabs", "glass"),
  make_tabs("solid_tabs", "solid"),
  make_tabs("underline_tabs", "underline")
)

Vertical orientation

Set orientation = "vertical" to stack the tabs in a left-hand rail beside the content pane. In vertical mode, Up/Down arrow keys move between tabs and indicator = "underline" becomes a side bar adjacent to the content.

glassTabsUI("side",
  glassTabPanel("inbox", "Inbox", selected = TRUE, p("Inbox")),
  glassTabPanel("sent", "Sent", p("Sent")),
  glassTabPanel("archive", "Archive", p("Archive")),
  orientation = "vertical",
  indicator = "underline"
)

Use tab_align = "left", "center", or "right" to place the tab group in the available navigation area. This works in horizontal and vertical layouts. Use text_align when the labels and icons inside the buttons should have a different alignment:

glassTabsUI("aligned",
  glassTabPanel("drafts", "Drafts", selected = TRUE, p("Draft queue")),
  glassTabPanel("review", "Review", p("Review queue")),
  glassTabPanel("done", "Done", p("Completed queue")),
  orientation = "vertical",
  tab_align = "right",
  text_align = "left",
  indicator = "solid"
)

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", ...)

# Auto - follows Bootstrap 5 / bslib data-bs-theme light/dark mode
glassTabsUI("nav", theme = "auto", ...)

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 nine 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
focus_ring Keyboard focus indicator
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"]]))
}

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.