Single-Select Dropdown with glasstabs

Overview

glassSelect() is an animated single-select dropdown for Shiny.

It behaves like a standard Shiny input: input$<inputId> stores the selected value as a single character string, or NULL when nothing is selected.

It supports:

Basic usage

library(shiny)
library(glasstabs)

fruits <- c(
  Apple  = "apple",
  Banana = "banana",
  Cherry = "cherry",
  Mango  = "mango"
)

ui <- fluidPage(
  useGlassTabs(),
  glassSelect("pick", fruits),
  verbatimTextOutput("out")
)

server <- function(input, output, session) {
  output$out <- renderPrint(input$pick)
}

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

Initial selection

# No selection (default)
glassSelect("f", fruits)

# Pre-select one value
glassSelect("f", fruits, selected = "banana")

Optional label

glassSelect("f", fruits, label = "Choose a fruit")

Selection styles

glassSelect() supports three selection marker styles:

glassSelect("f1", fruits, check_style = "checkbox")
glassSelect("f2", fruits, check_style = "check-only")
glassSelect("f3", fruits, check_style = "filled")

Corner shape

By default the trigger and dropdown use the signature rounded glass corners. Pass shape = "square" for crisp, selectize-style corners so the widget sits flush next to native selectizeInput() controls without looking out of place:

# Rounded (default)
glassSelect("region_rounded", fruits, shape = "rounded")

# Square - matches selectizeInput()
glassSelect("region_square", fruits, shape = "square")

The shape can also be switched at runtime from the server with updateGlassSelect(session, "region_square", shape = "square"). See the corner shape side by side with native selectize in runGlassExample("square-corners").

Width, grouped choices, and disabled options

For parity with native selectizeInput(), glassSelect() accepts a width, grouped choices as a named list, and disabled state:

# Fixed / fluid width
glassSelect("region_w", fruits, width = "240px")

# Grouped choices (selectInput()-style named list)
glassSelect("food", list(
  Fruit = c(Apple = "apple", Banana = "banana"),
  Veg   = c(Carrot = "carrot", Pea = "pea")
))

# Disable the whole widget, or specific options
glassSelect("region_d", fruits, disabled = TRUE)
glassSelect("region_dc", fruits, disabled_choices = "banana")

disabled and disabled_choices can also be toggled at runtime with updateGlassSelect().

Searchable and clearable

Search is enabled by default. You can also enable a clear control.

glassSelect(
  "f",
  fruits,
  searchable = TRUE,
  clearable = TRUE
)

To hide the search box:

glassSelect(
  "f",
  fruits,
  searchable = FALSE
)

Server-side search for large choice sets

For hundreds or thousands of choices, set server = TRUE in the UI and register glassSelectServer() in the server function. The browser renders only an initial slice of choices and asks Shiny for matching choices as the user types.

many_choices <- stats::setNames(
  sprintf("value-%04d", 1:2000),
  sprintf("Choice %04d", 1:2000)
)

ui <- fluidPage(
  useGlassTabs(),
  glassSelect(
    "pick",
    many_choices,
    selected = "value-1500",
    clearable = TRUE,
    server = TRUE,
    server_limit = 30
  ),
  verbatimTextOutput("out")
)

server <- function(input, output, session) {
  glassSelectServer("pick", many_choices, session = session, limit = 30)
  output$out <- renderPrint(input$pick)
}

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

This pattern also works when the control is created dynamically with renderUI(). Keep the glassSelectServer() call in the server function for the same inputId.

Explicit “All” option

You can prepend an explicit “All” option:

glassSelect(
  "f",
  fruits,
  include_all = TRUE,
  all_choice_label = "All fruits",
  all_choice_value = "__all__"
)

This is useful when you want the UI to distinguish between: - no selection - a deliberate “All” selection

Server-side access

Read the current value directly from input$<inputId>:

server <- function(input, output, session) {
  observe({
  message("Selected: ", if (is.null(input$pick)) "NULL" else input$pick)
})
}

Reactive helper

If you want a small convenience wrapper, use glassSelectValue():

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

  observe({
    print(pick())
  })
}

Updating choices and selection from the server

updateGlassSelect() can update: - available choices - current selection - selection marker style

observeEvent(input$filled, {
  updateGlassSelect(
    session,
    "pick",
    check_style = "filled"
  )
})

It follows Shiny-style update semantics:

ui <- fluidPage(
  useGlassTabs(),
  actionButton("subset", "Keep first 2 fruits"),
  actionButton("banana", "Select banana"),
  actionButton("clear", "Clear"),
  actionButton("filled", "Use filled style"),
  glassSelect("pick", fruits, clearable = TRUE),
  verbatimTextOutput("out")
)

server <- function(input, output, session) {
  output$out <- renderPrint(input$pick)

  observeEvent(input$subset, {
    updateGlassSelect(
      session,
      "pick",
      choices = fruits[1:2]
    )
  })

  observeEvent(input$banana, {
    updateGlassSelect(
      session,
      "pick",
      selected = "banana"
    )
  })

  observeEvent(input$clear, {
    updateGlassSelect(
      session,
      "pick",
      selected = character(0)
    )
  })
}

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

When choices is updated without selected, the widget keeps the current value if it still exists in the new set of choices.

Theming

Built-in presets

glassSelect("f", fruits, theme = "dark")
glassSelect("f", fruits, theme = "light")

Custom theme with glass_select_theme()

glassSelect(
  "f",
  fruits,
  theme = glass_select_theme(
    mode = "dark",
    accent_color = "#f59e0b"
  )
)

glassSelect(
  "f",
  fruits,
  theme = glass_select_theme(
    bg_color = "#1a0a2e",
    border_color = "#a855f7",
    text_color = "#ede9fe",
    accent_color = "#a855f7"
  )
)


#other themes
glassSelect(
  "f",
  fruits,
  theme = glass_select_theme(
    mode = "light",
    accent_color = "#2563eb"
  )
)

#more themes
glassSelect(
  "f",
  fruits,
  theme = glass_select_theme(
    mode = "light",
    bg_color = "#ffffff",
    border_color = "rgba(0,0,0,0.15)",
    text_color = "#111111",
    accent_color = "#111111",
    label_color = "#111111"
  )
)

Standalone usage

glassSelect() can be used without the tab widget. You only need useGlassTabs() to load the package CSS and JavaScript assets:

ui <- fluidPage(
  useGlassTabs(),
  glassSelect("region", c(North = "North", South = "South", East = "East")),
  verbatimTextOutput("out")
)

server <- function(input, output, session) {
  output$out <- renderPrint(input$region)
}

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