Skip to contents

Tabs often begin with three short labels and grow with the app. This article keeps that larger version comfortable on a phone, with a keyboard, and under system accessibility settings.

A complete responsive app

library(shiny)
library(glasstabs)

ui <- fluidPage(
  useGlassTabs(),
  glassTabsUI(
    "reports",
    glassTabPanel("summary", "Summary", selected = TRUE,
      h3("Summary"), p("A quick view of the current reporting period.")
    ),
    glassTabPanel("activity", "Recent activity",
      h3("Recent activity"), tableOutput("activity_table")
    ),
    glassTabPanel("quality", "Data quality",
      h3("Data quality"), plotOutput("quality_plot")
    ),
    glassTabPanel("settings", "Team settings",
      h3("Team settings"), textInput("team_name", "Team name")
    ),
    overflow = "scroll",
    theme = "auto"
  )
)

server <- function(input, output, session) {
  output$activity_table <- renderTable(head(mtcars))
  output$quality_plot <- renderPlot(plot(mtcars$wt, mtcars$mpg))
}

shinyApp(ui, server)

On a narrow screen, the labels remain in a touch-scrollable row. Keyboard focus follows the selected tab, and a server update brings its destination back into view.

Choosing the overflow style

"scroll" is a natural fit for most tab bars. "multiline" is useful when every label should be visible at once. In a horizontal layout, "menu" keeps the header compact and uses a native select control. Vertical tabs already have a dedicated rail, so compact menu mode is not available there.

glassTabsUI(
  "wrapped",
  glassTabPanel("one", "First report", selected = TRUE, p("First")),
  glassTabPanel("two", "Second report", p("Second")),
  glassTabPanel("three", "Third report", p("Third")),
  overflow = "multiline"
)

glassTabsUI(
  "menu",
  glassTabPanel("one", "First report", selected = TRUE, p("First")),
  glassTabPanel("two", "Second report", p("Second")),
  overflow = "menu"
)

Touch swipes without surprises

swipe = TRUE adds left and right gestures to ordinary panel content. Inputs, links, buttons, Shiny outputs, HTML widgets, and horizontal scrollers keep their own touch behavior.

glassTabsUI(
  "story",
  glassTabPanel("opening", "Opening", selected = TRUE, p("The first part.")),
  glassTabPanel("middle", "Middle", p("The middle part.")),
  glassTabPanel("ending", "Ending", p("The final part.")),
  swipe = TRUE
)

Add data-gt-no-swipe to any custom region that should always keep its own gesture:

tags$div(`data-gt-no-swipe` = "", custom_touch_component)

Motion and contrast

No R setting is needed for reduced motion. The browser preference removes the transfer trace and badge pulse, shortens CSS transitions, and shows the next panel without an animation delay.

Windows forced-colors mode uses system colors and visible focus outlines. When backdrop blur is unsupported, the controls use solid surfaces instead of losing contrast. Custom themes can set focus_ring in glass_tab_theme() and glass_select_theme(); the colour follows select dropdowns when they move to the document body, so search focus remains visible.

Experimental fillable page

glassPage() keeps a small full-page app pleasantly short:

ui <- glassPage(
  title = "Operations",
  glassTabsUI(
    "main",
    glassTabPanel("overview", "Overview", selected = TRUE, overview_ui),
    glassTabPanel("alerts", "Alerts", alerts_ui),
    overflow = "scroll",
    theme = "auto"
  )
)

It is an experimental wrapper around bslib::page_fillable(). Plain fluidPage(), page_fillable(), and dashboard layouts remain first-class homes for every glasstabs widget.