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.

glasstabs Cheatsheet

One rule

Call useGlassTabs() once somewhere in your UI before using any widget.

ui <- fluidPage(
  useGlassTabs(),
  # widgets go here
)

glassPage() is the experimental exception. It is a thin wrapper around bslib::page_fillable() and loads the dependency itself.

ui <- glassPage(
  title = "Review",
  lang = "en",
  theme = bslib::bs_theme(version = 5),
  h2("Review queue")
)

Tabs: basic pattern

ui <- fluidPage(
  useGlassTabs(),
  glassTabsUI(
    "main",
    glassTabPanel("overview", "Overview", selected = TRUE, h3("Overview")),
    glassTabPanel("details", "Details", h3("Details"))
  )
)

server <- function(input, output, session) {
  active_tab <- glassTabsServer("main")
}

Tabs: inside a Shiny module

The key rule: use ns() in the UI, use the bare id in the server.

# UI function
my_ui <- function(id) {
  ns <- NS(id)
  tagList(
    useGlassTabs(),
    glassTabsUI(
      ns("tabs"),                          # ns() wraps the id in the UI
      glassTabPanel("summary", "Summary", selected = TRUE, h3("Summary")),
      glassTabPanel("detail",  "Detail",  h3("Detail"))
    )
  )
}

# Server function
my_server <- function(id) {
  moduleServer(id, function(input, output, session) {
    active <- glassTabsServer("tabs")    # bare id — no ns() here

    observeEvent(active(), {
      message("Active tab: ", active())
    })
  })
}

# App
ui     <- fluidPage(my_ui("explorer"))
server <- function(input, output, session) my_server("explorer")
if (interactive()) shinyApp(ui, server)

Tabs: dynamic values and selected

# Build panels from data
tab_defs <- list(
  list(value = "revenue", label = "Revenue"),
  list(value = "orders",  label = "Orders"),
  list(value = "returns", label = "Returns")
)

panels <- lapply(tab_defs, function(t) {
  glassTabPanel(t$value, t$label, h3(t$label))
})

ui <- fluidPage(
  useGlassTabs(),
  do.call(glassTabsUI, c(list("metrics", selected = "orders"), panels))
)

server <- function(input, output, session) {
  active <- glassTabsServer("metrics")
}

Note: tab value strings must be unique — glassTabsUI() will error on duplicates. Values are plain strings; they do not need to match labels.

Tabs: server actions

# Switch active tab
updateGlassTabsUI(session, "main", "details")

# Hide or show a tab
hideGlassTab(session, "main", "admin")
showGlassTab(session, "main", "admin")

# Append or remove a tab at runtime
appendGlassTab(
  session, "main",
  glassTabPanel("compare", "Compare", h3("Compare")),
  select = TRUE
)
removeGlassTab(session, "main", "compare")

Tabs: common options

glassTabsUI(
  "main",
  glassTabPanel("a", "A", selected = TRUE, p("A")),
  glassTabPanel("b", "B", p("B")),
  selected = "a",
  wrap = TRUE,
  compact = FALSE,      # set TRUE inside dashboard cards for tighter layout
  content_min_height = NULL,  # default: 120px (60px when compact = TRUE); "0" for no minimum
  extra_ui = tags$div("Right side UI"),
  theme = "light"
)

Tabs: compact mode (dashboard cards)

Use compact = TRUE when embedding glasstabs inside a tight layout such as a bs4Dash::bs4Card(). It reduces margins, padding, and font size so the widget does not overflow the card.

bs4Card(
  title = "Explorer", width = 12,
  glassTabsUI(
    "tabs",
    glassTabPanel("a", "A", selected = TRUE, p("Content")),
    glassTabPanel("b", "B", p("More content")),
    compact = TRUE      # reduced spacing for card context
  )
)

Tabs: connected styling

The active tab and its content are visually linked automatically: the active tab carries a subtle inner light on its content-facing edge (bottom for horizontal tabs, the rail side for vertical, RTL-aware) in the halo color, and the content box’s top edge picks up the same color to bridge the gap. The underline indicator keeps its own connection cue instead. No parameters needed - to restyle the link, theme halo_border via glass_tab_theme().

Tabs: attached style and slide transition

Use style = "attached" for a single unified card where the tab bar docks onto the content box, and transition = "slide" for directional pane movement that follows tab order.

glassTabsUI(
  "main",
  glassTabPanel("a", "A", selected = TRUE, p("A")),
  glassTabPanel("b", "B", p("B")),
  style = "attached",      # tab bar shares one border with the content box
  transition = "slide"     # panes slide in the direction of travel
)

Multi-select: basic pattern

choices <- c(Revenue = "revenue", Orders = "orders", Returns = "returns")

ui <- fluidPage(
  useGlassTabs(),
  glassMultiSelect("metric", choices),
  glassFilterTags("metric")
)

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

Multi-select: update from server

updateGlassMultiSelect(
  session,
  "metric",
  selected = c("revenue", "orders"),
  check_style = "filled",
  notify = "changed"
)

# Clear selection
updateGlassMultiSelect(session, "metric", selected = character(0))

Multi-select: useful arguments

glassMultiSelect(
  "metric",
  choices,
  selected = unname(choices),
  label = "Metrics",
  placeholder = "Choose metrics",
  all_label = "All metrics",
  check_style = "checkbox",
  show_style_switcher = TRUE,
  show_select_all = TRUE,
  show_clear_all = TRUE,
  searchable = "auto",
  search_threshold = 12,
  selection_display = "summary",
  selection_max_items = 2,
  dropdown_max_height = "18rem",
  width = "240px",
  tongue = TRUE,
  label_bold = TRUE,
  label_size = "14px",
  search_focus_ring = FALSE,
  overflow_tooltip = "auto",
  fill_colors = "single",
  theme = "dark",
  no_matches_text = "No matches"
)
# Let an empty UI selection mean every choice in filtering code.
metric <- glassMultiSelectValue(
  input,
  "metric",
  choices = choices,
  empty_behavior = "all"
)
metric$resolved()

Single-select: basic pattern

regions <- c("All Regions" = "all", North = "north", South = "south")

ui <- fluidPage(
  useGlassTabs(),
  glassSelect("region", regions, selected = "all")
)

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

Single-select: update from server

updateGlassSelect(session, "region", selected = "south")

# Clear value
updateGlassSelect(session, "region", selected = character(0))

Single-select: useful arguments

glassSelect(
  "region",
  regions,
  selected = "all",
  label = "Region",
  placeholder = "Pick a region",
  searchable = "auto",
  search_threshold = 6,
  clearable = TRUE,
  include_all = FALSE,
  check_style = "checkbox",
  width = "240px",
  tongue = TRUE,
  label_bold = TRUE,
  label_size = "14px",
  search_focus_ring = FALSE,
  overflow_tooltip = "auto",
  theme = "light",
  no_matches_text = "No matches"
)

For glassSelect(), the overflow tooltip appears for an ellipsized trigger. Inside either open dropdown, it appears for the truncated option currently hovered or reached with the keyboard; the multi-select trigger does not combine selected labels into a tooltip. On touch, wrapped option text is the full-text fallback. Use overflow_tooltip = "never" to disable visual tips.

Tabs: inside bs4Dash

glassTabsUI() works inside bs4Dash::tabItem(). Place useGlassTabs() in the dashboardBody() (or anywhere before the first widget — once is enough).

library(shiny)
library(bs4Dash)

ui <- bs4DashPage(
  header  = bs4DashNavbar(),
  sidebar = bs4DashSidebar(
    bs4SidebarMenu(
      bs4SidebarMenuItem("Explorer", tabName = "explorer", icon = icon("table"))
    )
  ),
  body = bs4DashBody(
    useGlassTabs(),                     # place once, anywhere in the body
    bs4TabItems(
      bs4TabItem(
        tabName = "explorer",
        bs4Card(
          title = "Data explorer",
          width = 12,
          glassTabsUI(
            "s3_tabs",
            glassTabPanel("buckets", "Buckets", selected = TRUE,
              p("List of S3 buckets here.")
            ),
            glassTabPanel("objects", "Objects",
              p("Object browser here.")
            ),
            glassTabPanel("preview", "Preview",
              p("File preview here.")
            )
          )
        )
      )
    )
  )
)

server <- function(input, output, session) {
  active <- glassTabsServer("s3_tabs")
}

if (interactive()) shinyApp(ui, server)

useGlassTabs() only needs to appear once. Inside a module that renders into a bs4Dash layout, call it in your module’s UI function.

Theme helpers

# Tabs
glassTabsUI(
  "main",
  glassTabPanel("a", "A", selected = TRUE, p("A")),
  theme = glass_tab_theme(
    halo_bg = "rgba(251,191,36,0.15)",
    tab_active_text = "#fef3c7"
  )
)

# Select widgets
glassMultiSelect(
  "metric", choices,
  theme = glass_select_theme(
    mode = "dark",
    accent_color = "#38bdf8"
  )
)

Input values at a glance

Widget Server value
glassTabsUI("main", ...) input[["main-active_tab"]] or glassTabsServer("main")()
glassMultiSelect("metric", ...) input$metric or glassMultiSelectValue(input, "metric")$selected()
resolved multi-select value glassMultiSelectValue(..., empty_behavior = "all")$resolved()
multi-select style input$metric_style or glassMultiSelectValue(input, "metric")$style()
select open state input$metric_open / input$region_open
glassSelect("region", ...) input$region or glassSelectValue(input, "region")()

conditionalPanel integration

Use glassTabCondition() to avoid constructing the input key manually:

# Instead of: condition = "input['main-active_tab'] === 'details'"
conditionalPanel(
  condition = glassTabCondition("main", "details"),
  p("Only visible on the Details tab.")
)

# Inside a module — pass the same id as glassTabsUI():
# glassTabsUI(ns("tabs"), ...)
conditionalPanel(
  condition = glassTabCondition(NS("mymod")("tabs"), "details"),
  p("Only visible on the Details tab.")
)

View the changelog

glasstabs_news()      # prints changelog to the console

Common gotchas

That is the whole working model: load the dependency once, compose widgets in the UI, and read their values through ordinary Shiny inputs.

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.