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.
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 |
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):
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 from the server with
updateGlassMultiSelect(session, "filters_square", shape = "square").
The runGlassExample("square-corners") app shows the square
shape next to native selectize.
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().
All three interactive chrome elements can be toggled independently.
Defaults are all TRUE:
Read the selection directly from input$<inputId>
and the style from input$<inputId>_style:
If you want a small convenience wrapper, use
glassMultiSelectValue():
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.
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: