knitr::opts_chunk$set(eval = FALSE)
glassMultiSelect() is an animated multi-select dropdown for Shiny.
It can be used inside a glassTabsUI() layout or completely on its own
in any Shiny page.
The widget behaves like a standard Shiny input:
input$<inputId> stores the selected values, and
input$<inputId>_style stores the active indicator style. The
input$<inputId>_open value reports whether the dropdown is currently open.
It also supports server-side updates with
updateGlassMultiSelect().
library(shiny) library(glasstabs) fruits <- c(Apple = "apple", Banana = "banana", Cherry = "cherry", Mango = "mango", Peach = "peach") ui <- fluidPage( useGlassTabs(), glassMultiSelect("pick", fruits), verbatimTextOutput("out") ) server <- function(input, output, session) { output$out <- renderPrint(input$pick) } if (interactive()) shinyApp(ui, server)
| Input | Type | Value |
|-------------------------|------------------|---------------------------|
| input$<inputId> | character vector | Currently selected values |
| input$<inputId>_style | character | Active checkbox style |
| input$<inputId>_open | logical | Whether the dropdown is open |
# All selected (default) glassMultiSelect("f", fruits) # Subset pre-selected glassMultiSelect("f", fruits, selected = c("apple", "cherry")) # Nothing pre-selected glassMultiSelect("f", fruits, selected = character(0))
Three indicator styles are available via check_style:
# Bordered box + animated tick (default) glassMultiSelect("f", fruits, check_style = "checkbox") # Tick only — minimal, no box border glassMultiSelect("f", fruits, check_style = "check-only") # Solid coloured box — each option gets its own hue glassMultiSelect("f", fruits, check_style = "filled")
By default, a style switcher row is shown inside the dropdown so the
user can change the style at runtime. Hide it with
show_style_switcher = FALSE:
glassMultiSelect("f", fruits, check_style = "check-only", show_style_switcher = FALSE # lock the style silently )
When check_style = "filled", hues are auto-assigned evenly around the
colour wheel. Override them with a named integer vector of HSL hue
angles (0-360):
glassMultiSelect("f", fruits, check_style = "filled", hues = c(apple = 0, banana = 50, cherry = 340, mango = 30, peach = 20) )
By default the trigger and dropdown use the signature rounded glass corners.
Pass shape = "square" for crisp, selectize-style corners so the widget
blends into layouts built around native selectizeInput() controls:
# Rounded (default) glassMultiSelect("filters_rounded", fruits, shape = "rounded") # Square - matches selectizeInput() glassMultiSelect("filters_square", fruits, shape = "square")
The shape can also be switched at runtime with
updateGlassMultiSelect(session, "filters_square", shape = "square"). This
complete layout places it beside a native multiple select:
fluidRow( column( 6, selectizeInput( "native_filters", "Native", choices = fruits, multiple = TRUE ) ), column( 6, glassMultiSelect( "glass_filters", fruits, label = "glasstabs", shape = "square", width = "100%" ) ) )
Like native selectizeInput(), glassMultiSelect() accepts a width, grouped
choices as a named list, and disabled state:
# Fixed / fluid width glassMultiSelect("filters_w", fruits, width = "100%") # Grouped choices (selectInput()-style named list) glassMultiSelect("food", list( Fruit = c(Apple = "apple", Banana = "banana"), Veg = c(Carrot = "carrot", Pea = "pea") )) # Disable the whole widget, or specific options glassMultiSelect("filters_d", fruits, disabled = TRUE) glassMultiSelect("filters_dc", fruits, disabled_choices = "banana")
disabled_choices are skipped by "Select all", and both can be toggled at
runtime with updateGlassMultiSelect().
For dynamic layouts, modals, sidebars, or tab changes, you can explicitly close an open dropdown from the server:
observeEvent(input$change_layout, { closeGlassMultiSelect(session, "pick") })
Use closeAllGlassSelects(session) when a layout change should close any open
glasstabs select dropdown.
All three interactive chrome elements can be toggled independently.
Defaults are all TRUE:
glassMultiSelect("f", fruits, show_style_switcher = FALSE, # hide the Check / Box / Fill row show_select_all = FALSE, # hide the "Select all" row show_clear_all = FALSE # hide the "Clear all" footer link )
glassFilterTags()Place a glassFilterTags() container anywhere on the page to show the
active selections as removable tag pills. The JS engine keeps them in
sync automatically - clicking x on a tag deselects that option.
ui <- fluidPage( useGlassTabs(), glassMultiSelect("cat", fruits, show_style_switcher = FALSE), glassFilterTags("cat") # can be placed anywhere, even in a different tab pane )
You can place multiple glassFilterTags("cat") containers for the same
filter — all will stay in sync.
Read the selection directly from input$<inputId> and the style from
input$<inputId>_style:
server <- function(input, output, session) { # Direct access observe({ message("Selected: ", paste(input$pick, collapse = ", ")) message("Style: ", input$pick_style) }) }
If you want a small convenience wrapper, use
glassMultiSelectValue():
server <- function(input, output, session) { ms <- glassMultiSelectValue(input, "pick") observe({ message("Selected: ", paste(ms$selected(), collapse = ", ")) message("Style: ", ms$style()) }) }
For large filters, set server = TRUE in the UI and register
glassMultiSelectServer() in the server function. The control keeps the
selected values on the client while rendering only the current page of
matching choices.
many_choices <- stats::setNames( sprintf("value-%04d", 1:2000), sprintf("Choice %04d", 1:2000) ) ui <- fluidPage( useGlassTabs(), glassMultiSelect( "pick", many_choices, server = TRUE, server_limit = 30, show_style_switcher = FALSE ), verbatimTextOutput("out") ) server <- function(input, output, session) { glassMultiSelectServer("pick", many_choices, session = session, limit = 30) output$out <- renderPrint(length(input$pick)) } if (interactive()) shinyApp(ui, server)
This pattern is also suitable for controls inserted by renderUI().
Register glassMultiSelectServer() once for the same inputId, and the
newly rendered control will receive search results through Shiny.
updateGlassMultiSelect() can update:
- available choices
- current selection
- active style
It follows Shiny-style update semantics:
choices = NULL leaves choices unchangedselected = NULL leaves selection unchangedselected = character(0) clears the selectionui <- fluidPage( useGlassTabs(), actionButton("subset", "Keep first 3 fruits"), actionButton("pick2", "Select 2 fruits"), actionButton("clear", "Clear"), actionButton("fill", "Switch to filled style"), glassMultiSelect("pick", fruits), verbatimTextOutput("out") ) server <- function(input, output, session) { output$out <- renderPrint(input$pick) observeEvent(input$subset, { updateGlassMultiSelect( session, "pick", choices = fruits[1:3] ) }) observeEvent(input$pick2, { updateGlassMultiSelect( session, "pick", selected = c("apple", "cherry") ) }) observeEvent(input$clear, { updateGlassMultiSelect( session, "pick", selected = character(0) ) }) observeEvent(input$fill, { updateGlassMultiSelect( session, "pick", check_style = "filled" ) }) } if (interactive()) shinyApp(ui, server)
When choices is updated without selected, the widget keeps the
intersection of the current selection and the new choice set.
glassMultiSelect("f", fruits, theme = "dark") # default glassMultiSelect("f", fruits, theme = "light") # white panel, dark text glassMultiSelect("f", fruits, theme = "auto") # follows Bootstrap/bslib color mode
Use theme = "auto" inside Bootstrap 5 / bslib apps to follow
data-bs-theme light and dark modes without server code.
glass_select_theme()Supply only the colors you want to change:
# One field — accent colour only glassMultiSelect("f", fruits, theme = glass_select_theme(accent_color = "#f59e0b") ) # Two fields glassMultiSelect("f", fruits, theme = glass_select_theme( text_color = "#1e293b", accent_color = "#2563eb" ) ) # All four fields glassMultiSelect("f", fruits, theme = glass_select_theme( bg_color = "#1a0a2e", border_color = "#a855f7", text_color = "#ede9fe", accent_color = "#a855f7" ) )
| Argument | What it controls |
|----------------|-------------------------------------------------------|
| bg_color | Dropdown panel and trigger button background |
| border_color | Border colour |
| text_color | Main text and label colour |
| accent_color | Tick marks, badge, checked highlights, Clear all link |
glassMultiSelect() is fully independent of the tab widget. You only
need useGlassTabs() for the CSS and JS:
library(shiny) library(glasstabs) sales <- data.frame( region = c("North","North","South","South","East","West"), product = c("Alpha","Beta","Alpha","Gamma","Beta","Alpha"), revenue = c(42000, 31000, 27000, 55000, 38000, 61000) ) ui <- fluidPage( useGlassTabs(), glassMultiSelect("region", c(North="North", South="South", East="East", West="West"), show_style_switcher = FALSE), glassMultiSelect("product", c(Alpha="Alpha", Beta="Beta", Gamma="Gamma"), show_style_switcher = FALSE, check_style = "check-only"), glassFilterTags("region"), glassFilterTags("product"), tableOutput("tbl") ) server <- function(input, output, session) { filtered <- reactive({ sales[sales$region %in% (input$region %||% unique(sales$region)) & sales$product %in% (input$product %||% unique(sales$product)), ] }) output$tbl <- renderTable(filtered()) } if (interactive()) shinyApp(ui, server)
Each glassMultiSelect() is scoped to its own inputId and works
independently. When one dropdown is open it automatically floats above
all others:
ui <- fluidPage( useGlassTabs(), glassMultiSelect("a", c(X = "x", Y = "y", Z = "z")), glassMultiSelect("b", c(P = "p", Q = "q", R = "r"), check_style = "filled", show_style_switcher = FALSE) )
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.