actionLink() and
downloadLink(). Checkbox and radio rows are at least 44px
tall; the marks stay about 18px. Slider thumbs keep their size and use a
44px hit area. Harbor notes also checks the send button, edit button,
composer, sidebar toggle, sources button, history row, scroll-to-bottom,
the thinking disclosure, source-pill hit area, and the sources close
button.html and
body use overflow-x: clip.0.5, Apple’s midpoint between
Ultra Clear and Tinted (previously 0.45). Apps that do not
set intensity look slightly more tinted, with a stronger
rim, lip, and edge and a narrower, more even blur range. Colored edge
tokens (--glass-refract-a, --glass-refract-b)
and the backdrop brightness and contrast boosts scale with
intensity.specular = TRUE still paints the highlights.
specular = FALSE hides them. iOS 27 does not tilt glass.
Pointer tracking is not available in 0.5.0.refraction = FALSE turns the lens off. Reduced
transparency, increased contrast, forced colors, and flatten mode do the
same.morph = FALSE
disables that. Click targets stay on the original controls.press = TRUE). Reduced motion skips the
spring.minimize_tabs = TRUE). Reduced
motion keeps them full size. Navbar compact-on-scroll is still
nav_morph.icon_style = "clear"). Light mode uses
#1d1d1f so the glyph stays above the contrast floor. Dark
mode uses near-white. Primary buttons keep their own ink.
"color" leaves author colors. "all" also
restyles icons inside cards.material = "clear" is deprecated. Pass
intensity instead. Existing calls still work: when
intensity is omitted they map to 0.12, warn,
and stay clearer than the midpoint.
update_glass_theme(material = "clear") and
setMaterial("clear") do the same.floating_sidebar = TRUE) and off by default.vignette("migrating") lists the visual and API changes
and shows how to approximate the 0.4.0 look.shinyglass (also
shinyglass-package) is the usage guide: a minimal app, how
to add glass to an existing bslib or fluidPage() app, key
options, the 0.5.0 migration, and common pitfalls with their fixes.
vignette("shinyglass") is that same guide and is what
browseVignettes("shinyglass") lists. Help pages state
types, defaults, and minimal examples, and they cross-link by topic.
Argument errors name the argument and the value to pass.glass_theme(), glass_page(), and
update_glass_theme() take refraction,
morph, press, scroll_edge,
minimize_tabs, icon_style, and
floating_sidebar. Tokens include --glass-ior,
--glass-refraction-scale, --glass-dispersion,
--glass-diffusion, --glass-specular-top,
--glass-specular-bottom, --glass-edge,
--glass-press-scale, --glass-scroll-edge-fade,
--glass-minimize-scale, and
--glass-icon-clear.scroll_edge = TRUE). This is the 0.4.0
scroll-edge toolbar, now a theme argument.glass_page_react()
and glass_theme_dependencies() attach
inst/js/shinyglass-react.js, an ES module (html dependency
shinyglass-react) that registers
useGlassTheme, GlassPage,
GlassSidebar, GlassSurface,
GlassCard, GlassButton, and the small layout
helpers on window.shinyglass and as named exports. The
module uses window.shinyreact.React (it does not ship a
second React or an npm package). Paint stays on the existing
--glass-* tokens via .glass-page,
.glass-sidebar, .glass-button, and
.glass-surface. useGlassTheme() follows mode,
resolved preset, scene, material, and intensity, and reports
reducedTransparency, increasedContrast, and
forcedColors, plus refraction, morph, press, scroll edge,
tab minimize, icon style, and floating sidebar.
GlassIntensitySlider runs from Ultra Clear to Tinted
(default 0.5). GlassSidebar floats when
floating is set or the theme opts in. See
vignette("shinyreact").glass_page_react() passes
glass_theme() as theme so Liquid Glass CSS is
not stripped (an unnamed theme hits shinyreact’s no-Bootstrap page and
drops the compiled stylesheet). glass_theme_dependencies()
is the same asset list for page_react_html(extra_deps = ).
Opt-in .glass-surface / .glass-muted follow
the live light/dark packs. Document class rules do not cross a shadow
root; --glass-* custom properties do, and
window.shinyglass.adoptShadow() injects a
.glass-surface rule into a root the client created.
Example:
system.file("examples/shinyreact-glass", package = "shinyglass").inst/js/glass-ios27.js (not copied from the reference
projects). The approach follows public
backdrop-filter: url() lens work: Surdeddd/liquidglassjs,
nikdelvin/liquid-glass, and archisvaze/liquid-glass.inst/examples/compat-glass.R with
Playwright coverage in inst/scripts/browser/compat.spec.js.
It checks common inputs, DT, reactable, plotly, leaflet, ggplot2,
echarts4r, shinyWidgets, modals, tooltips, modules, dynamic UI, dark
mode, scroll, and print under the iOS 27 defaults and with refraction
fallback.inst/examples/shinychat-glass) joins the
five-app demo set with basics, dashboard, inputs, and Olympics. plotly +
gt stays a local example (inst/examples/plotly-gt-demo.R)
and stays in the deploy catalog, outside that five-app set. Harbor notes
uses the dusk wallpaper, streams a local script (thinking, a tool card,
and a citation) with no API key, and uses
ellmer::chat_google_gemini() (Gemini Flash) when
GEMINI_API_KEY or GOOGLE_API_KEY is set. Each
visitor gets 8 messages of at most 400 characters, shared with the phone
preview. Quota and other API errors fall back to the local script. Live
Harbor notes replies stay specific to shinyglass
(glass_theme(), glass_page(), the intensity
slider, the physics, and shinychat). Deploy key chat
(shinyglass-chat). When GEMINI_API_KEY is
present in the deploy environment, the chat bundle gets a temporary
.Renviron. shinyapps.io does not accept rsconnect
envVars. The value is not logged. Hosted app: Harbor
notes.page_chat() and chat_ui()
pick up the glass pack: full-window header and history sidebar, message
bubbles, a floating composer, thinking panels, tool rows, citation
chips, artifact previews, toolbars, and the slash-command menu. Chat
replies use a neutral glass bubble, including while the reply is
streaming. User bubbles stay the tinted pill. A scrolled transcript
fades under the header and the composer instead of clipping on the bar.
On a coarse pointer the send button, edit button, and composer are at
least 44px, and the footer keeps the bottom safe-area inset. The typed
“/” in the composer stays visible when the slash menu opens. Refraction,
morph, press, and the scroll edge stay on. Example:
inst/examples/shinychat-glass (local echo, no API key).
Harbor notes shows a Live (Gemini) or Demo replies chip, lists citations
from the scripted reply (and from Gemini when grounding is present), and
keeps the theme and the conversation in the browser.offcanvas() panels use the glass material,
scale in from the trigger, and show a scroll edge on the header.
Bootstrap still owns the slide.?frame=0, coarse pointer) on a wide fine-pointer screen. A
phone never gets the control.page_sidebar() or
layout_sidebar() opens as a full-height overlay. The
collapse toggle stays above the panel and closes it, and a tap outside
the panel closes it too. The open panel no longer wears a white band
across the top in dark mode.actionLink() /
downloadLink(). Those links keep their text size. Nav tabs
do not scale when pressed.glass_page().plot_surface = "clear", so the text does not
sit on the wallpaper.verbatimTextOutput() inside a card uses the card
surface, so a live values panel matches the cards next to it.--glass-* values the intensity script writes. Reduce
Transparency uses opaque fills and no backdrop blur (the same idea as
flatten mode). Increase Contrast and forced colors are watched live. The
page sets data-glass-contrast,
data-glass-forced-colors, and
data-glass-reduced-transparency on
<html>, and matching CSS rules use
!important.bslib::input_dark_mode() switches the glass pack when
it writes data-bs-theme.materialSwitch() gets a 24px hit target so
the knob stays inside glass cards and accepts clicks.glass_theme(plot_surface = "opaque") densifies plot,
plotly, gt, and DT hosts (~94% panel fill, readable ink) while chrome
stays Liquid Glass. Default "clear" keeps wallpaper
show-through. Toggle live with glass_plot_surface_input(),
update_glass_theme(plot_surface = ),
window.shinyglass.setPlotSurface(), or
.glass-plot-surface-opaque. theme_glass(),
plotly_glass(), and gt_theme_glass() accept
surface = "opaque". dt_options_glass() is an
optional DT helper.aurora, harbor, and
grove join tahoe / dusk /
mesh. glass_scenes() /
glass_scene_input() list and switch them. User
wallpaper= photos are blurred and washed
(--glass-wallpaper-wash) so body ink stays above the
contrast floor (4.5:1 text / 3:1 large chrome).glass_flatten(), glass_theme(flatten = ),
update_glass_theme(),
window.shinyglass.setFlatten() /
.enterFlatten() / .exitFlatten(), and
?glass_flatten=1. @media print does the same
automatically.--glass-* tokens:
glass_css_tokens(), glass_token_pack(),
glass_add_tokens(), glass_theme(tokens = ),
and update_glass_theme(tokens = ) /
window.shinyglass.setTokens().0.45). Dark
bslib sidebars keep rim/lip/side-stroke edges instead of a shadow-only
look.body.glass-scroll-edge)
when content sits under floating nav. Contrast-only: stays on under
prefers-reduced-motion;
prefers-reduced-transparency still forces Tinted.page_navbar / navsets,
nested cards and value boxes, sidebars, accordion, tooltips/popovers,
and modals. Focus-visible rings and overlay stacking (tooltip 1090,
popover 1085, menus 1080) apply to every glass_theme()
app.zIndex = 1080) unless the app
sets those options.plot_surface, scenes, flatten, persist, and JS
knobs on glass_theme().glass_theme() no longer errors when scene
is the default name vector (R 4.4+ && length check)
or a subset of scene names.tokens= stay distinct from the compiled Sass pack
(empty overrides no longer inject glass_bg as a public CSS
token).glass_theme(persist = TRUE) stores
preset, intensity, accent, material, and scene in
localStorage per app path.theme_glass(), plotly_glass(), and
gt_theme_glass() match plot and table ink to the resolved
light/dark pack. glass_plot_colors() exposes the
palette.glass_accent_input() wells (blue / purple /
pink / orange / green / teal). Named colors like
primary = "purple" are accepted.glass_page() / observe_glass() assemble
theme + toggle + intensity + accent in one call. Persistence defaults to
on.tahoe, dusk,
mesh) and optional photo wallpaper =.
update_glass_theme() can change material,
ambient_motion, and scene live.glass_theme(ambient_motion = FALSE) disables decorative
ambient animation independently of pointer highlights and navbar
motion.prefers-contrast: more) and Windows
forced-colors surfaces. Reduced-motion and reduced-transparency follow
the OS live.rings.jpg.theme_glass() uses grid-safe colors (ggplot2 cannot
paint CSS rgba()).[data-glass-preset].
Switching preset no longer requires recompiling Sass or reloading the
page. Apps that overrode Sass-only $glass-* tokens should
target --glass-* CSS variables (or
[data-glass-preset="dark"]) instead.glass_theme() always builds on the Bootstrap 5 base
theme (not Bootswatch darkly for dark). Dark appearance
comes from the CSS variable pack so light↔︎dark can toggle live. Visuals
should match 0.1.x closely; if you relied on darkly-specific chrome
outside glass surfaces, re-check dark mode.window.__shinyglassDisableTint
still works, but the supported API is
glass_theme(tint = FALSE) /
update_glass_theme(tint = …).Light / Dark / Auto is an iOS-style segmented control: theme-aware glass thumb (changes in light vs dark), body ink on every chip, and a delegated click so Auto applies even when host pages rewrite inline handlers.
Theme toggle applies the preset once (no inline handler + server echo storm), so plots no longer flicker when switching Light / Dark.
Default actionButton() / .btn-link ink
follows theme tokens (--glass-body-color /
--glass-accent) instead of compiled
$primary.
[glass_resolved_preset()] reports the resolved light/dark appearance so plots do not stay light-inked when the user picks Auto.
Dark pack no longer re-locks default / outline / download labels
to compiled $primary. Download links and ghost focus rings
follow --glass-accent. Accent
<select id="accent"> applies setPrimary
immediately.
[glass_intensity_slider()] / [observe_glass_intensity()]: iOS
27-style Ultra Clear → Tinted control.
glass_theme(intensity = ) sets the starting value
(0–1, default 0.45);
[update_glass_theme()] and window.shinyglass.setIntensity()
update it live.
[glass_preset_input()] / [observe_glass_preset_input()]:
client-first Light/Dark/Auto select (applies setPreset
immediately; server sync is optional). Prefer this over a bare
selectInput() + update_glass_theme() alone on
hosts that rewrite custom messages.
Theme delivery is dual-channel (shinyglass + legacy
glassPreset) with a self-healing
oncustommessage proxy so preset/intensity/showModal survive
host message rewrites (e.g. shinyapps.io).
Demo app intensity-slider-demo.R; live shinyglass-demo
and shinyglass-dashboard
ship glass_intensity_slider() in the UI.
iOS 27 material polish: stronger darkened edges, brighter specular highlights, intensity-scaled fill/blur for clearer layer separation and readability.
Dark-mode nav tab ink keeps WCAG UI 3:1 on glass (inactive tabs).
Closer iOS 27 Liquid Glass: defined borders (not shadow-only),
chromatic edge refraction cues, lighter dark-mode glass, thicker
elevated chrome, ambient specular drift + pointer override, stronger
diffusion blur defaults (blur = 36,
saturation = 200).
Runtime light/dark switching via
document.documentElement.dataset.glassPreset without a full
reload.
glass_theme(preset = "auto") follows
prefers-color-scheme and updates when the OS theme
changes.
[update_glass_theme()] to change preset,
tint, and primary (live
accent via --glass-accent / --bs-primary) from
the server. Recolors primary buttons, Shiny checkboxInput()
/ radios / switches, ion.rangeSlider bars, value chips, and active
nav/list items without a Sass recompile.
[glass_theme_toggle()] / [observe_glass_theme_toggle()] drop-in Light / Dark / Auto buttons.
Behavior knobs on [glass_theme()]: tint,
specular, and nav_morph (all default
TRUE, matching 0.1.x JS behavior).
Client helpers: window.shinyglass.setPreset() /
.getPreset() / .setTint() /
.setPrimary() / .getPrimary().
Liquid Glass material fidelity (Tahoe / iOS 26 inspired): multi-layer surfaces (illumination fill, dual-edge rim/lip, ambient + pointer specular), chrome vs content hierarchy (less glass-on-glass), softer wallpaper orbs, larger concentric radii, capsule-leaning controls.
New glass_theme(material = "regular"|"clear"). Clear
is more transparent for media-rich layouts. Defaults:
blur = 36, radius = "1.5rem",
saturation = 200.
Value boxes are tinted glass (theme-color wash + body ink), not
solid .bg-* slabs. Plots and DT/gt hosts inside cards drop
the extra glass frame so page orbs show through
(renderPlot(bg = "transparent") in bundled demos).
DataTables / Bootstrap pagination chips: style the visible
.page-link surface (DT uses
<li.paginate_button><a.page-link>). Fixes solid
black page numbers and light-grey ellipsis chips in dark mode on the
dashboard demo / README screenshots.
Overlay chrome (datepicker popup, Shiny notification types,
generic accordion, Bootstrap tooltip/popover/toast,
.btn-close, file Browse chip) now uses the glass menu pack
so dark mode is not leftover Bootstrap light hex. Datepicker is hoisted
to document.body (Shiny 1.14 bsDatepicker) so
card/navbar backdrop-filter no longer clips the calendar.
Navbar overflow is visible so nav_menu dropdowns escape the
glass bar. Kitchen-sink example: chrome-kitchen-sink.R
(page_navbar, nav_menu,
showModal(), all four showNotification()
types).
Visual QA harness:
inst/scripts/audit-glass-contrast.R (+ .js)
runs a dual-theme computed-style audit (low contrast, solid-black chips,
Bootstrap light greys in dark, dark ink on accent, DT pagination
structure) across demo / dashboard / inputs-gallery / chrome
kitchen-sink. Opens datepicker, navbar menu, notifications, accordion,
tooltip/popover, and server modal before sampling. Checklist and
definition of done in inst/scripts/VISUAL-QA.md.
Selectize fields: force body/menu ink on inputs and multi-select
tags (selectize default near-black item text failed dark mode); opaque
--glass-menu-bg fills in dark; dropdown options use menu
colors.
Ecosystem chrome: plotly modebar + frame,
gt tables, waiter / shinybusy
overlays, shinyalert/SweetAlert2 popups, denser
shinydashboardPlus controlbar/dropdowns/user menu,
light rhandsontable / echarts/highcharter hosts.
Examples: plotly-gt-demo.R,
shinydashboardPlus-glass-demo.R.
plotly + gt stays a local example
(plotly-gt-demo.R). README light/dark screenshots for
plotly+gt. The hosted copy is archived.
CI workflow visual-qa (testthat + dual-theme
contrast audit) and committed
inst/scripts/audit-baseline.json.
Navbar brand / page title ink uses
--glass-body-color (no more white brand on light glass).
Plotly hosts no longer clip x-axis titles.
Active-on-accent contrast: lower Bootstrap
$min-contrast-ratio to 3 so
color-contrast() picks white on brand blues/purples (white
on #007AFF is ~4.02:1 and failed the old 4.5 gate). Solid
.bg-* / .text-bg-* fills (including
bslib::value_box(theme=)) get luminance-based ink via
glass-on() / --glass-on-*, not body color on
solid primary. Safety nets remain for checks, radios, switches,
pagination, and list-group active.
Accent rules target Shiny markup
(.shiny-input-checkbox) as well as Bootstrap
.form-check-input.
Plot / image outputs get a softer glass frame (lighter nesting inside cards).
prefers-reduced-motion: reduce disables nav morph,
specular, tint sampling, and decorative transitions (JS + CSS).
Demo and bslib dashboard examples: theme toggle helper; dashboard
accent select calls
update_glass_theme(primary = …).
Vignettes cover migration, primary updates, toggle helper, and reduced motion.
Unit tests cover dual CSS packs, head script knobs, active-color
vars, and update_glass_theme() messaging.
shiny-glass.js no longer crashes when the script loads
in <head> before document.body
exists.bslib::sidebar() / page_sidebar()
layout no longer collapses to a full-width stack. Glass floating-sidebar
rules now win over bslib’s later
position/width cascade, and open sidebars
reserve space in main so value boxes and cards stay fully visible beside
the chrome (not under it).layout_sidebar() no longer double-floats.
Inner sidebars keep glass styling but use bslib’s in-flow grid so
content is not pushed into a large empty gutter beside a second absolute
panel.shinydashboard and
bs4Dash) gets stronger glass overlay: translucent
header/sidebar, soft-tinted value/info boxes, and clearer menu active
states, without breaking AdminLTE layout.glass_theme() returns a bslib theme with Liquid
Glass styling for shiny apps:
translucent surfaces, backdrop blur, soft depth, and system
typography.fluidPage(), navbarPage(),
bslib::page_sidebar(), and other page functions that accept
a bslib theme. Pass the theme via theme = glass_theme(), or
for teal apps
via options(teal.bs_theme = glass_theme()).