library(shiny)
library(shinyglass)
ui <- glass_page(
title = "Liquid Glass",
persist = TRUE,
scene = "tahoe",
plotOutput("p")
)glass_page() is fluidPage() plus
[glass_theme()], the Light / Dark / Auto toggle, intensity slider, and
accent wells. Pair it with [observe_glass()] in the server. For a bare
page, still pass theme = glass_theme() to
fluidPage() / navbarPage() /
bslib::page_sidebar().
preset |
Behavior |
|---|---|
"light" |
Light glass pack (default) |
"dark" |
Dark glass pack |
"auto" |
Follows prefers-color-scheme; updates when the OS theme
changes |
Light and dark surface tokens ship as dual CSS custom-property packs
on [data-glass-preset]. Switching preset updates
document.documentElement.dataset.glassPreset — no Sass
recompile and no full page reload.
ui <- fluidPage(
theme = glass_theme(preset = "auto"),
glass_theme_toggle(selected = "auto"),
selectInput("accent", "Accent", c(
Blue = "#007AFF", Purple = "#AF52DE", Orange = "#FF9500"
))
)
server <- function(input, output, session) {
observe_glass_theme_toggle(input, session)
observeEvent(input$accent, {
update_glass_theme(session, primary = input$accent)
}, ignoreInit = TRUE)
}update_glass_theme() accepts:
| Argument | Effect |
|---|---|
preset |
"light" / "dark" /
"auto" |
tint |
Content-aware ambient color from plots/images |
primary |
Live accent via CSS variables (--bs-primary, …) |
primary updates Bootstrap accent CSS variables so
buttons, checked controls, and other accent surfaces follow without a
reload. A few Sass-baked one-offs may still need a full reload to pick
up a new color.
ui <- fluidPage(
theme = glass_theme(preset = "auto"),
glass_theme_toggle(), # Light / Dark / Auto buttons
# ...
)
server <- function(input, output, session) {
observe_glass_theme_toggle(input, session)
}Buttons call window.shinyglass.setPreset() immediately
and also fire Shiny inputs (glass_toggle_light, …) so the
server can stay in sync.
glass_theme(
preset = "auto",
primary = "#AF52DE",
blur = 36,
saturation = 200,
radius = "1.5rem",
material = "regular", # or "clear" over media-rich content
intensity = 0.45, # 0 Ultra Clear → 1 Tinted (iOS 27)
tint = TRUE, # sample plot/image colors into glass surfaces
specular = TRUE, # pointer specular highlight
nav_morph = TRUE # compact navbar while scrolling down
)material = "regular" is the adaptive Tahoe-style fill
(default). Use "clear" when chrome sits over rich media and
labels stay bold.
Use [glass_intensity_slider()] for a live Ultra Clear → Tinted control (same idea as iOS 27 Settings → Appearance → Liquid Glass):
All three JS behaviors default to TRUE (same as 0.1.x).
They respect prefers-reduced-motion: reduce where relevant.
Advanced escape hatch:
window.__shinyglassDisableTint = true still disables
tint.
Breaking in 0.2.0: light/dark surfaces are dual CSS
packs, not a Bootswatch darkly rebuild. Prefer CSS custom
properties over Sass $glass-* tokens when customizing:
:root {
/* shared knobs also set by glass_theme(blur=..., radius=...) */
--glass-blur: 36px;
--glass-radius: 1.25rem;
--bs-primary: #007AFF; /* also updated by update_glass_theme(primary=) */
--glass-primary: #007AFF;
}
:root[data-glass-preset="dark"] {
/* override a dark-pack value if needed */
--glass-bg: rgba(255, 255, 255, 0.1);
}Apps that overrode Sass-only $glass-* tokens should
target --glass-* or [data-glass-preset="dark"]
instead.
glass_theme(persist = TRUE) (and
glass_page(), which defaults to on) writes preset,
intensity, accent, material, and scene to localStorage for
this app path so a refresh restores the last look.
glass_theme(scene = "tahoe") (or "dusk" /
"mesh") swaps the page gradient and orbs.
wallpaper = "https://..." paints a frosted photo behind the
glass. Change scene live with
update_glass_theme(session, scene = "dusk").
output$p <- renderPlot({
ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) +
ggplot2::geom_point() +
theme_glass(input = input)
}, bg = "transparent")[theme_glass()] is a ggplot2 theme with transparent panels and
light/dark ink. [plotly_glass()] and [gt_theme_glass()] do the same for
those packages. [glass_plot_colors()] returns ink,
grid, fill, and paper.
When the user prefers reduced motion
(prefers-reduced-motion: reduce):
This is automatic and responds to OS preference changes while the app is open. Reduced-transparency changes also update the surfaces without changing the requested slider value.
To disable only the idle decorative animation, use
glass_theme(ambient_motion = FALSE). To disable all
optional effects, use:
This retains glass surfaces and blur; it is not a zero-paint-cost mode.
Intensity is page-wide. The first explicit slider value
in each newly inserted group sets that intensity; peer sliders
synchronize visually. A restricted-range slider displays the nearest
endpoint when the global value is outside its range. Bounds must satisfy
0 <= min < max <= 1, step must be
finite and positive, and an explicit value must be within
the slider’s range.
Bootstrap’s color-contrast() often picks
black ink for system blue #007AFF.
glass_theme() forces light ink on primary fills so checked
checkboxes, radios, switches, and active pagination retain light ink.
White on #007AFF is approximately 4.02:1: it meets the 3:1
large-text threshold, but not the 4.5:1 ordinary-text threshold. Choose
and test an appropriate darker accent when ordinary white labels must
meet that threshold. The QA guide’s --text-aa audit checks
size-aware text thresholds; translucent backgrounds, gradients, and
focus visibility still need visual review.