NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #1268 most downloaded on pub.dev
iOS 26/27-style liquid glass widgets for Flutter. Built on a custom fragment shader for real blur, liquid interactions, specular highlights, and chromatic aberration.
Last release 2 days ago
06 Oct 2026
Ships on a steady schedule
a new release about every 8 days
Nearly every release is documented
notes for 60 of the last 60 stable releases
1 version withdrawn
withdrawn after publishing
10 months old
177 releases · first in 2025
One column per month.
`GlassBarItem.spacer()` multi-capsule rendering (#245, PR #390): A spacer now splits a pinned bar's run of shared items into separate capsules, as Too
GlassBarItem.spacer() multi-capsule rendering (#245, PR #390): A spacer now splits a pinned bar's run of shared items into separate capsules, as ToolbarSpacer(.fixed) does natively, where it used to assert. Across a transition each capsule follows its items, matched by id and otherwise by position, rather than one capsule morphing into another. A capsule the destination adds beside one buds out of it through a liquid bridge, as on iOS 27, and one it drops merges back in.
Thanks to @JakeThomson for the contribution (#390).
External slide-to-select on GlassMenu (discussion #369, PR #384): Added glideTo(Offset), endGlide(), and cancelGlide() to GlassMenuController. Enables external gesture recognizers (such as a long-press on an outer card) to drive menu item highlighting, selection haptics, and activation without needing direct touch ownership.
Thanks to @F1orian for the contribution (#384).
Dynamic sheet appearance with expandedDarkColor (PR #385): Added optional expandedDarkColor parameter to GlassModalSheet and GlassModalSheet.show so presented full-height sheets and morph droplets dynamically follow system light/dark appearance switches while open.
Thanks to @DFelten for the contribution (#385).
Accessibility semantic label overrides (PR #387): Added optional semanticLabel parameters across GlassPicker, GlassMenu, GlassPopover, GlassTextField, GlassChip, GlassPasswordField, GlassSearchBar, and GlassSearchBarConfig to allow custom screen reader announcements.
Thanks to @Ahmadre for the contribution (#387).
Tinted GlassBarItem dropped the bar's buttonSettings (fixes #372, PR #389): A GlassBarItem with a tintColor built its capsule's settings from scratch, so under LiquidGlassSettings.ios27Light it lost the outline, rim light and shadow the untinted capsules beside it kept. The tint now replaces only the body colour of the bar's button settings, hoisted and in-route.
Thanks to @JakeThomson for the fix (#389).
LiquidGlassSettings.ios27Dark over bright backdrops (fixes #373, PR #388): From mid-grey up, ios27Dark lifted the backdrop and drew a light rim where the native dark material darkens it and keeps a dark outline, and over a white page it stayed white where native greys to about 184. The new bodyShade term pulls a bright backdrop down by its luminance squared, outline included; ios27Dark sets it to 0.28 and raises rimShade to 1.
Thanks to @JakeThomson for the fix (#388).
Screen reader, dynamic type, and Reduce Motion pass (PR #387): Exposed missing button semantics and dismiss actions on menu/popover barriers and plain triggers; prevented chip delete buttons from merging into name text; allowed GlassPicker to grow with large system fonts; and honored Reduce Motion across toasts, page controls, and searchable tabs.
Thanks to @Ahmadre for the contribution (#387).
Controlled GlassSwitch state synchronization (PR #383): Fixed visual state desynchronization when widget.value is unchanged or rejected by the parent. Reconciles the position controller to authoritative widget.value after gestures and settles thickness bloom correctly.
Thanks to @sbezhuk for the fix (#383).
One screen-reader node per control (issue #381): Every interactive control now surfaces as a single semantics node carrying its label, role, state, tap and focus. Previously GlassFocusRegion split its focus semantics into a second node — unlabelled on switches, icon buttons, tabs, steppers and button groups (an extra TalkBack stop), and a repeat of the visible text on segments, chips and menu rows (read twice by VoiceOver). The drag detectors on GlassSegmentedControl and GlassSwitch, and the inner detector on GlassListTile, no longer add unlabelled scroll/tap nodes either.
GlassButton.label, GlassSegment.semanticLabel, GlassMenuItem.title, …) now replaces the control's visible text instead of being announced alongside it, as GlassButton.label documents.GlassMenuItem.subtitle is announced as the row's value; GlassListTile.onLongPress is reachable by screen readers.GlassChip's delete button defaults to "Remove ‹label›" instead of being unnamed.canRequestFocus: false is now honoured; it was silently overridden, so such controls still took keyboard focus.Thanks to @cosmeao for the detailed report and reproduction (#381).
fix(backdrop-group): keep glass under any opacity out of the group; r…
fix(backdrop-group): keep glass under any opacity out of the group; r…
Vertical bars on iPhone Duo (#348, PR #365): Under a GlassNavigationShell, the pinned chrome, GlassTabBar.bottom and GlassToolbar now move into the 84pt strip iOS 27.1 reserves on the outer display and on the inner display in landscape, laid out to the native strip's measured geometry. The title stays in a row at the top of the content, and GlassBarItem.axisBehavior decides which items go vertical. Where the strip runs short, the chrome overflows into a ••• menu and the tab bar collapses to its selected tab. The strip is read from MediaQuery.viewPadding and published as GlassVerticalBar; GlassNavigationShell.verticalBarBehavior and verticalBarCompression mirror UIKit's preferredVerticalBarBehavior and verticalBarCompressionBehavior. Nothing changes on any other device.
GlassNavigationShell(
verticalBarBehavior: GlassVerticalBarBehavior.automatic,
verticalBarCompression: GlassVerticalBarCompression.automatic,
child: child!,
)
Thanks to @JakeThomson for the contribution (#365).
Plain cross-fade for pinned chrome after committed back-swipe (PR #360): Added swipeCommitTransition to GlassNavigationShell (GlassSwipeCommitTransition.crossFade vs GlassSwipeCommitTransition.effect) to perform a plain cross-fade without capsule swell, scale, or blur after an interactive back-swipe commits.
Thanks to @DFelten for the contribution (#360).
Search and large titles in iPhone Duo's strip (#366, PR #370): GlassTabBar.searchable now joins the strip's capsule with search as its last slot, and opens its field in the row at the top of the content. A GlassLargeTitle moves into that row at 28pt, drawn by the GlassAppBar.pinned sharing its controller, and scrolls away with the content; its searchBar becomes a magnifier at the bottom of the strip, which opens the field along the bottom of the content and hides the bar, as searchable does natively. The row's insets are physical, as natively, so in a right-to-left app the title ends against the strip.
Thanks to @JakeThomson for the contribution (#370).
Sheets, popovers and Reduce Transparency in iPhone Duo's strip (#367, PR #371): A GlassModalSheet now takes the strip's place where it covers it, as natively: on the outer display its GlassAppBar.pinned stacks down a strip of its own, and the sheet rises to 8pt from the top with its margins kept. On the inner display it is a 653pt card with a horizontal bar, placed by the new placement (GlassSheetPlacement, mirroring presentationPlacement(_:)); .trailing docks it over the strip and moves its bar into it. A GlassMenu or GlassPopover opened from the strip opens towards the content, centred on its item. Under Reduce Transparency the strip and the title row turn opaque behind a hairline. A pinned bar inside a presented route no longer registers with the shell, since the presentation is its container.
Thanks to @JakeThomson for the contribution (#371).
GlassAdaptiveScope.frostStep (also on GlassAdaptiveScopeConfig): opt-in step through
premium without frost. The first step down from premium only switches the frost off,
standing a regular blur in for it, and keeps the premium lens, rim and highlight; the next
step goes to standard, and recovery takes the same steps back. GlassAdaptiveScopeData
gains frostEnabled. Default off.
GlassBackdropGroup (#357): On Impeller every BackdropFilter copies the whole
screen, and premium glass uses two or three per surface. Glass inside a group shares one
backdrop read for its blur and frost; refraction stays per surface. Same idea as SwiftUI's
GlassEffectContainer. Glass under an opacity, shader mask, save-layer clip or inside
another glass surface stays out on its own. Wrap a screen's bars rather than a Navigator.
No effect on web or Skia.
GlassTabBar and GlassAppBar group their glass by default: new groupBackdrop
(default true; set false to opt out). Inside your own GlassBackdropGroup the bars join
it. Glass that overlaps the bar (selected-tab indicator, search pill while the keyboard is
up, extra button, bottom accessory) stays out. iPhone Air, ios27Light, opening a sheet:
23.6 → 9.3 ms average GPU time per frame. No visible change.
Cheaper iOS 27 material on premium: The shader skips frost and light work that has no
visible effect, and the frost's ghost takes 6 taps instead of 45 at the floor sigma
(ios27Light, blur: 0, glass cards: 59.7 → 50.0 ms GPU).
Less work for moving and resting glass: Frost rows are shifted instead of rebuilt, the geometry matte is kept when glass moves as a whole, and the light-mode shadow is baked into an image once the shape holds still.
Benchmark: example/integration_test/perf_glass_test.dart measures raster and GPU frame
times on a device in profile mode.
Thanks to @Ahmadre for the contribution (#357, PR #359), and to @JakeThomson for the review.
GlassMenu slide-to-select released between two rows (PR #375): Releasing over the 2px gap between two rows activated nothing and left the menu open. Each row's hit zone now includes half of the gap on either side, so a release there activates the nearer row.
Thanks to @F1orian for the fix (#375).
GlassMenu keyboard and screen-reader activation (PR #376): On menus that don't scroll, pressing Enter or Space on a focused row, or activating it with VoiceOver or TalkBack, did nothing; only touch worked. These now activate the row, and a touch tap still activates it exactly once.
Thanks to @F1orian for the fix (#376).
GlassMenu pointer events after unmount (fixes #363, PR #364): Ignore events from active menu and continuous-swipe pointers after disposal, preventing unmounted context and disposed notifier errors when a route is removed during a gesture.
Thanks to @yairsts for the fix (#364).
In-route bar items slot size (PR #362): Size in-route bar items in GlassPinnedBarChrome to match the pinned cluster's slot geometry even with custom icon sizes.
Thanks to @DFelten for the fix (#362).
Pinned chrome icons blur on pop to a route with no pinned bar (follow-up to #351): When popping from a route with a pinned capsule to a route with no pinned bar, item icons no longer blur out on the first frame while the glass capsule stays visible. Items now dissolve together with the capsule glass, matching native iOS behaviour.
GlassPinnedBarChrome action items order in RTL (fixes #374): Keep action items in consistent visual order when a modal sheet triggers hand-over under RTL layout.
iOS 27 presets on the standard path and web (visible change): ios27Light and
ios27Dark came out flat and nearly clear on standard and on web, since the lightweight
shader draws neither the frost nor the rim light. They now get a regular blur in place of
the frost and the highlight in place of the rim light.
LiquidGlassWidgets.wrap() passes the warm-up thresholds on: warmupPremiumThresholdMs
and warmupStandardThresholdMs from GlassAdaptiveScopeConfig never reached the scope, and
the config's == and hashCode ignored them.
Docs: The adaptive scope degrades after 2 windows (not 3) and caps explicit quality:
parameters too; docs/ARCHITECTURE.md no longer claims resting premium glass skips live
backdrop passes.
`GlassScrollEdgeEffect` stale texture on theme switch (fixes #352): Recaptures the background when brightness or fadeColor changes; stale in-flight ca
GlassScrollEdgeEffect stale texture on theme switch (fixes #352): Recaptures the background when brightness or fadeColor changes; stale in-flight captures are discarded via a generation counter.buttonSettings and inset on pop (fixes #351): buttonSettings and horizontalInset now adopt the destination route's values from the first frame of a pop transition, rather than snapping at completion.GlassButton / GlassIconButton isolated semantics node (fixes #354): The button label, role, and tap action now land on a single isolated semantics node. Previously they merged into an ancestor boundary or split across two nodes.StateError: RenderBox was not laid out crashes that occurred when a glow animation updated in the same frame a route transition wrapper (e.g. SlideTransition) was inserted above the route.CurvedAnimation and ValueNotifier disposal (fixes #356): All interactive and overlay widgets now correctly dispose their CurvedAnimation instances and ValueNotifiers on unmount, preventing listener leaks and leak_tracker failures in widget tests.feat: GlassMenuItem.closeDelay, iOS 26/27 Apple Mail showcase demo im…
feat: GlassMenuItem.closeDelay, iOS 26/27 Apple Mail showcase demo im…
iOS 27 material for GlassQuality.premium: New LiquidGlassSettings terms, each
measured against a SwiftUI glassEffect(.regular) control on the same screen: frost,
frostOpacity, frostClamp and frostWeight for the cloud that a copy of the content
still shows through (blurWeight shapes that copy), rimShade and rimShadeEnds for the
half-point outline, rimLight for its highlight lobes, and GlassLensModel.paraxial for
the evenly folded rim band. LiquidGlassSettings.ios27Light and ios27Dark put them
together, and GlassThemeSettings carries each term so a theme can apply them app-wide.
All default to off, and existing settings render exactly as before. The frost
costs one blur pass, written to alternate pixel rows of the shape, plus a colour pass when
frostWeight is not 1.
Thanks to @JakeThomson for the contribution (#347).
GlassMenuItem.closeDelay: New optional Duration that defers menu dismissal after onTap fires. Useful when the item hosts an animated trailing widget (e.g. GlassSwitch) so its animation completes before the menu morphs away. Defaults to null (synchronous close — backward compatible).
Apple Mail iOS 26/27 showcase demo: Added an Apple Mail showcase in example/lib/apple_mail/ featuring pinned bar navigation with gel-morph across mailbox, inbox, and detail screens, living liquid morph compose sheet via GlassMorphTrigger and GlassModalSheet, floating triage bar with GlassButtonGroup, options popover with GlassMenu, and full Cupertino styling. Includes a GlassSwitch row (with closeDelay: 650 ms) in the Inbox ··· menu to toggle between iOS 26 and iOS 27 glass modes, with state persisted globally across the full navigation stack via kMailUseIos27 and MailGlassScope.
Liquid droplet morph return and metaball fusion for GlassMenu and GlassPopover: Implements centroid flight trajectory on close for GlassMenu and GlassPopover, unpinning the returning shape from the trigger corner to fly directly toward the trigger anchor center. Adds an ease-in droplet contraction curve and early anchorScale ramp-in to enable authentic SDF metaball bridging, morphs border radius directly to capsule/droplet shape on close, rapidly flushes menu items over the first 15% of close to maintain a pure liquid droplet in flight, and resolves narrow constraint layout in GlassMenuItem.
GlassMenu and GlassPopover cleanly route through simplified fade and scale fallbacks on close rather than attempting premium shader metaball blending.GlassModalSheet submerged offset tracking via SheetStateInfo.submergedBottom: Exposes submergedBottom on SheetStateInfo via GlassModalSheetStateProvider so sheet descendants can accurately compensate for the submerged depth of GlassModalSheet in GlassSheetState.full when positioning keyboard toolbars, floating action bars, or scroll view insets above the software keyboard and safe area floor.BackdropFilterLayer (such as GlassButton inside GlassModalSheet) from children painted outside the pass via super.paint (such as AnimatedGlassIndicator inside AdaptiveLiquidGlassLayer / GlassTabBar.bottom). Retains backdropPassClipRectLocal across frames for isolated descendant repaints (preventing buttons in modal sheets from disappearing when clicked or animated), while ensuring peer children of layer coordinators correctly resolve against root screen coordinates.AdaptiveGlass honors useOwnLayer: Ensures nested glass controls explicitly marked with useOwnLayer: true preserve their own refractive shader layer and specular highlights rather than collapsing into an ancestor's 2D vibrancy backer.GlassNavPinnedHost trailing action groups align to the trailing edge: Pairs action groups from the anchored trailing edge when anchoredAtStart is false instead of raw index 0. When navigating between routes with differing numbers of trailing groups, the rightmost buttons now correctly morph into each other in place on the trailing edge while remaining groups cleanly materialize or dematerialize without unexpected layout shifting.GlassGlow touch specular coordinates in nested layers: Corrects screen-to-pass coordinate translation in _TouchSpecularBridge so touch specular highlights track accurately within nested backdrop passes.`GlassTabBar` no longer exposes an unlabelled bar-wide semantics node (fixes #340, PR #343): The drag and tap detector that spans the full bar is now
GlassTabBar no longer exposes an unlabelled bar-wide semantics node (fixes #340, PR #343): The drag and tap detector that spans the full bar is now excluded from semantics across both GlassTabBar.bottom and GlassTabBar.searchable, leaving the labelled per-tab controls as the only announced actions.
Thanks to @DeepanshuPal for the fix (#343).
GlassTabBar default indicator follows app brightness (fixes #341, PR #344): The selected pill now resolves its default black/white tint through GlassTheme.brightnessOf(context) across both GlassTabBar.bottom and GlassTabBar.searchable instead of the device appearance, so app-level light and dark overrides remain visible.
Thanks to @DeepanshuPal for the fix (#344).
iOS Reduce Motion is now respected (fixes #342, PR #345): Accessibility motion handling now combines Flutter's disableAnimations flag with iOS's separate reduceMotion platform feature, including popover, menu, and modal-sheet morph controllers.
Thanks to @DeepanshuPal for the fix (#345).
Prevent infinite `borderRadius` from collapsing clipping and shadows (#339): Guards against double.infinity evaluating to 0.0 in Flutter's corner-scal
Prevent infinite borderRadius from collapsing clipping and shadows (#339): Guards against double.infinity evaluating to 0.0 in Flutter's corner-scaling formula across clipping layers, drop shadows, and shader uniforms. Centralizes safe radius handling on GlassDefaults and LiquidShape.
Thanks to @iimrudy for the fix (#339).
GlassTabBar.searchable RTL support (fixes #338): Fixes miscalculated indicator positioning, drag hit-testing, and tab selection under TextDirection.rtl.
Continuous swipe-to-select for `GlassMenu` and `GlassPullDownButton` (#331): Adds native iOS-style continuous press-and-slide interaction. Users can p
GlassMenu and GlassPullDownButton (#331): Adds native iOS-style continuous press-and-slide interaction. Users can press and hold the trigger, slide directly onto an item, and release to select in a single fluid motion without requiring a second tap.
GlassPullDownButton (enableContinuousSwipe: true), opt-in on GlassMenu.continuousSwipeSlop (defaults to 10 logical px) to distinguish quick taps from deliberate swipes without latency.Geometry matte pixel budget while a premium surface animates (#330): GlassQuality.premium
surfaces that resize (e.g. a GlassModalSheet opening) now rasterize their geometry matte at a
capped 1 MP budget during animation, settling at full resolution once the shape comes to rest.
Reduces animation memory peaks from 400–800 MB to ~340–394 MB on iPhone 17 Pro Max at 120 Hz.
Thanks to @JakeThomson for the contribution (#330).
Async sheet presenters no longer lose their hoisted capsule (#335, fixes #334): A
GlassBarItem.sheet presenter that does async work before calling GlassModalSheet.show
(e.g. measuring content offscreen, awaiting a fetch) could lose the shell's hold, reverting to
the pre-#325 symptom. GlassBarItem.sheet no longer requires a synchronous present.
Thanks to @JakeThomson for the fix (#335).
GlassTabBar.searchable: textColor is no longer overridden by hintStyle's colour (#336):
When both GlassSearchBarConfig.textColor and a colour on hintStyle were set, the hint colour
was applied to typed text too, ignoring textColor. The standard iOS look — muted placeholder,
strong typed text — can now be expressed by setting both. Callers using only hintStyle are
unaffected; font metrics are shared between hint and text in all cases.
Thanks to @jfhair for the fix (#336).
Rim refraction no longer bleeds past the glass boundary on small surfaces (#337): On small
GlassQuality.premium pills and icon buttons the rim refraction could reach far enough to sample
content above the surface's top edge, producing rainbow-coloured artefacts once chromatic
dispersion split it. Displacement is now capped to the surface's own footprint. Larger surfaces
are unaffected.
Thanks to @hinata-platform for the fix (#337).
Premium glass inside a BackdropFilter ancestor no longer renders displaced on Impeller (#333):
Glass nested inside any backdrop-reading ancestor was displaced by the ancestor's screen origin
due to FlutterFragCoord() being relative to the offscreen pass. LiquidGlassRenderObject now
resolves uniforms relative to the enclosing Impeller compositor pass. Top-level surfaces are
unaffected.
Thanks to @brockbrunson for the root-cause analysis and fix (#333).
fix(ci): pass test directories to avoid Windows cmd.exe 8191-char limit
fix(ci): pass test directories to avoid Windows cmd.exe 8191-char limit
Native interaction glow for GlassTabBar (#329, fixes #323): interactionGlowRadius is now nullable (defaulting to null), bringing GlassTabBar to parity with GlassButton's iOS 26 native glow calibration (1.6 radius, sigma-16 blur, soft sheen). Explicit values still preserve custom palettes and geometry.
Thanks to @azizibahram for the contribution (#329, fixes #323).
Configurable GlassSlider.thumbShadow with softer default (#327): Adds thumbShadow to GlassSlider for custom resting thumb shadow lists (pass [] to disable), while softening the default resting shadow opacity from 25% to 15% black for a cleaner look on light backgrounds.
Thanks to @leoluobuqi for the contribution (#327).
GlassTabBar overdrag no longer clips the edge tab icon (#328): Rubber-banding past the first or last tab no longer slides the selection window off the edge icon. JellyClipper now clamps the clip window against the bar boundary so the icon stays visible and the pill presses elastically against the wall.GlassPinnedBarChrome exposes the presenting item via chrome.presenting. GlassNavBarRegistration.presentSheet is deprecated.
Hoisted capsule kept through sheet morph (#325, fixes #324): When a
GlassBarItem.sheet was tapped, the capsule it morphed out of vanished mid-flight
and the route's copy appeared under the sheet barrier in its place. The shell now
keeps only the tapped capsule hoisted through the presentation while all other chrome
hands back. GlassPinnedBarChrome exposes the presenting item via chrome.presenting.
GlassNavBarRegistration.presentSheet is deprecated.
Thanks to @JakeThomson for the fix (#325).
GlassSlider resting shadow no longer clipped on light backgrounds (#319): The
thumb's BoxShadow was part of the material child inside GlassEffect, whose content
is clipped to the glass shape — hiding the shadow on white backgrounds. The shadow is
now painted as a sibling behind GlassEffect, outside the clip, and its alpha is
multiplied by the material's own fade so it vanishes cleanly during interaction and
returns on release or cancellation.
Thanks to @leoluobuqi for the fix (#319).
Cupertino back-swipe no longer crashes with a layout assertion (#326): Returning through a Cupertino page transition could throw a layout assertion when glass surfaces were present. The coordinate snapshot is now refreshed during paint, after all ancestors have been laid out, preserving UV freeze behaviour across repeated sheet presentations.
Thanks to @leoluobuqi for the fix (#326).
Glow highlight no longer dies when dragging off a bar (#322): GlassGlow was
forwarding unclamped pointer coordinates to GlassGlowLayer. On captured moves outside
the widget the centre walked past the layer's clip and the light faded out mid-gesture,
while the selection indicator — which does clamp — kept tracking the same finger. The
position is now clamped to the layer bounds so the highlight parks on the nearest edge
and keeps travelling with the finger, matching platform behaviour.
Thanks to @azizibahram for the fix (#322).
Added tintColor to GlassBarItem — floods the entire glass capsule with the colour; foreground flips to white or black automatically. Separate-backgrou
tintColor to GlassBarItem — floods the entire glass capsule with the colour;
foreground flips to white or black automatically. Separate-background items only.
Demonstrated in the example app under Nav Patterns → Tinted Bar Items (#312) with
an interactive 8-color palette switcher and before/after comparison.Slow drags over glass inside a scroll view no longer freeze (#317):
GeometryTransformTrackingLayer was calling onTransformChanged directly from
addToScene — inside a frame — where no new frame gets scheduled. The callback now
defers to a post-frame callback during compositing.
Thanks to @almazfm for the fix (#317).
GlassTabBar.bottom no longer drags backwards under RTL (#313): The drag path was
mirroring the pointer twice — a press landed on the correct tab but the slide ran
backwards. DraggableIndicatorPhysics.getAlignmentFromGlobalPosition now takes a
mirrorForRtl flag; TabDragGestureMixin passes false.
Thanks to @azizibahram for the fix (#316).
GlassTabBar tabs expose a semantics tap action (#314): Tab nodes were buttons with
a selected state but no SemanticsAction.tap — TalkBack and VoiceOver could read every
destination but activate none of them.
Thanks to @azizibahram for the fix (#315).
Fixed AdaptiveGlass ignoring explicit settings when inside a glass container
(useOwnLayer: false). Broke GlassBodyMode.clear tinting at standard quality.
Fixed UV freeze jitter with responsive_framework (#292): UV freezing now requires
an explicit LiquidGlassPushBackScope signal emitted by GlassPage during a real
CupertinoSheet push-back. Static app-level scales from responsive_framework,
FittedBox, and InteractiveViewer no longer trigger the freeze. Zero user code
changes required.
feat: 1.5.0 — Touch Specular, Nested Vibrancy Fill, Light-Mode Goldens & Search Indicator Parity
feat: 1.5.0 — Touch Specular, Nested Vibrancy Fill, Light-Mode Goldens & Search Indicator Parity
Shader-level touch specular (uTouchPosition): When a GlassGlow touch is active,
the specular highlight on the glass rim now concentrates on the side facing the finger — a
physically correct optical glint matching Apple iOS 26 Liquid Glass, driven by two new GLSL
uniforms (uTouchPosition vec2, uTouchIntensity float). The specular calculation is isotropic
in physical pixels (immune to aspect ratio warping on wide pills and bars), includes contact
distance falloff (smoothstep), aligns outward rim normals (anisoN) with touch rays, is
Reinhard-compressed, and costs zero at rest (GPU uniform coherence culls the branch when
uTouchIntensity == 0). Wired with zero frame lag via synchronous controller listeners on
GlassGlowLayerState and dual-direction propagation: child _RenderGlassGlowLayer objects
propagate directly to ancestor LiquidGlassRenderObjects, while ancestor GlassGlowLayers
feed _TouchSpecularBridge on RenderLiquidGlassLayer without widget rebuilds.
Vibrancy fill for nested glass (avoidsRefraction: true): When a GlassEffect or
AdaptiveGlass sits inside a GlassContainer (or any surface that sets
InheritedLiquidGlass.avoidsRefraction), it now renders a proper _VibrancyFill — a translucent
tinted ShapeDecoration with _SpecularRimPainter rim highlights and full GlassMaterializeScope
support — instead of silently routing to GlassQuality.minimal and producing a near-invisible
widget. No BackdropFilter, no compositor stall, zero GPU overhead. Matches iOS 26 UIKit behaviour:
a UIVibrancyEffect nested inside a UIVisualEffectView never issues a second backdrop read.
AdaptiveGlass.vibrancy() static factory added as the public entry-point.
Continuous menu pointer tracking (GlassMenu.glowOnTapOnly default: false):
Changed the default value of glowOnTapOnly from true to false in GlassMenu.
Because GlassMenu operates within a modal dismiss barrier without nested scroll rows,
pointer tracking now continuously moves the specular glow across menu items during touch drag,
matching native iOS 26 context menu optics. Menus containing scrollable content can still explicitly
set glowOnTapOnly: true to prevent persistent glow during scrolling.
Official AI Agent Skill for vibecoding & pair programming (#306): Added a
comprehensive agent skill (skills/liquid-glass-widgets/SKILL.md) and onboarding guide
(skills/README.md) that teaches AI assistants (Antigravity, Cursor, Claude Code, GitHub Copilot)
the library's architecture, setup lifecycle (LiquidGlassWidgets.initialize()), GlassScaffold
layout, component substitution dictionary, and anti-hallucination checklists to prevent synthetic
BackdropFilter implementations or obsolete pre-1.0 APIs. Complemented by repository-level
AGENTS.md and README.md integration instructions.
GlassTabBar.searchable and the tab bar collapses to the left dismiss/reopen button, tapping
it now features the native press growth (LiquidStretch.nativePressGrowth), micro-tremor
(AnchorStretchSettings.nativeTremor), and surface highlight (PressAmbientLift), exactly
matching GlassButton, SearchPill, and MinimizableTrailingPill (PR #272 parity).goldenTestLight() helper and companion LightGoldenTestGroup / LightGoldenTestScenario / buildWithLightBackground() utilities to test/shared/test_helpers.dart. New test/golden/light_mode_widgets_golden_test.dart provides 5 targeted light-mode goldens — GlassButton, GlassAppBar, GlassTabBar.bottom, AdaptiveGlass/GlassCard, and GlassToolbar — exercising subsystems that the all-dark suite cannot reach: _InverseShapeClipper/_InverseBarClipper drop-shadow rendering, ambientBaseLight doubling (0.14 vs 0.07), and Rec.709 adaptive glass strength (0.8× on high-luminance backgrounds). Goldens generated on macOS (Impeller); excluded from CI by existing dart_test.yaml tag filter.Engine organization & attribution: Consolidated foundational rendering primitives into lib/src/engine/ with formal attribution (lib/src/engine/ATTRIBUTION.md) crediting original work by Tim Lehmann. Internal refactoring only; no public API changes.
Shader rename: liquid_glass_final_render.frag → liquid_glass_render.frag. Drops the redundant _final_ prefix, aligning the on-disk filename 1:1 with the Dart constant ShaderKeys.liquidGlassRender. All references updated across pubspec.yaml, shaders.dart, setup docs, attribution, ROADMAP, and changelogs.
Touch specular pow() elimination: Replaced pow(rimTouchDot, 6.0) in the touch-specular block of liquid_glass_render.frag with a two-multiply chain (x²)³ = x⁶. pow() compiles as exp2(6·log2(x)) on Mali/Adreno/Apple GPU — two transcendental SFU calls. The multiply chain is exact, branchless, and 4–8× faster per fragment on mobile.
Beer-Lambert meniscus deduplication in lightweight_glass.frag: Extracted the 5-line hemisphere-lens × light-modulated absorption formula (previously copy-pasted at 3 call sites: PATH A fallback, PATH A normal, and PATH B) into a shared meniscusAbsorption() function. Mathematically identical output; reduces maintenance surface and compiled shader binary size.
`GlassPinnedBarChrome.horizontalInset` — the guide a hoisted bar is drawn on (#307): The chrome was always positioned at GlassNavPinnedMetrics.horizon
GlassPinnedBarChrome.horizontalInset — the guide a hoisted bar is drawn on (#307): The chrome was always positioned at GlassNavPinnedMetrics.horizontalPadding, which is right for a bar the package draws at both ends and wrong for one the app draws. Presenting a sheet hands the chrome back to its route, and a bar aligned to its own page gutter stepped sideways at every hand-over. It now takes the inset its bar reports, defaulting to the old one.Thanks to @JakeThomson for the feature (#308).
GlassPinnedBarChrome.platformViewBackdrop — pinned chrome over a platform view (#310): The shell drew a hoisted capsule with its own GlassButton on the shader path, whose captured backdrop excludes a platform view — so over a map the capsule had nothing to refract and rendered clear with a rim, whatever buttonSettings said, while the bar's own capsule blurred through a live BackdropFilter. The registration carries the flag now and the host forwards it to the capsule and menu it draws, resolved to the route being entered so a pop back over the view is on the backdrop from its first frame.Thanks to @JakeThomson for the feature (#311).
GlassSheet removes redundant inner SafeArea (#309): GlassSheet's inner column wrapped its content in SafeArea(bottom: true) without disabling top: true, causing unconsumed top status-bar padding from the window to leak into the sheet and push the top drag handle 50+ dp down. In addition, the inner wrapper forced bottom safe-area insetting even when GlassSheet.show(useSafeArea: false) was requested. The redundant inner SafeArea has been removed while preserving outer useSafeArea behavior.Thanks to @yohom for the fix (#309).
`GlassBarItem.sheet` — a bar item whose sheet morphs out of the capsule (#304): Nothing reached the capsule a pinned bar draws, so an app wanting the
GlassBarItem.sheet — a bar item whose sheet morphs out of the capsule (#304): Nothing reached the capsule a pinned bar draws, so an app wanting the liquid morph had to hoist its cluster as one GlassBarItemBackground.own item and give up the cross-route morph. The new item hands its tap a GlassMorphAnchor for GlassModalSheet.show(morphFrom:) — the route's own capsule, which is the one still on screen once the sheet has handed the chrome back, and the only one a sheet can cover.
GlassAppBar.pinned(
actions: [
GlassBarItem.sheet(
icon: const Icon(CupertinoIcons.add),
onPresent: (anchor) => GlassModalSheet.show<void>(
context: context,
morphFrom: anchor,
builder: (context) => const AddSheet(),
),
),
],
)
Thanks to @JakeThomson for the feature (#304).
Pinned chrome morphs over a sprung route's full duration (#302): The shell drove its choreography from the route's animation value, which is non-linear for spring-based transitions (zoom), so the morph completed in the first few frames while the page was still flying. A route that answers createSimulation now plays chrome on the shell's own controller over transitionDuration; duration-driven routes are unchanged.
Two custom items that draw their own glass no longer overlap mid-morph (#302): A matched pair of GlassBarItemBackground.own items sharing the same id cross-faded like plain content, stacking both surfaces mid-transition and causing a visible brightness artifact. The outgoing surface now dissolves before the incoming one materializes.
Thanks to @JakeThomson for the fix (#302).
GlassTabBar.searchable text colour now follows the app's ThemeMode, not the device OS brightness (#305): GlassSearchBarConfig.textColor defaulted to CupertinoColors.label.resolveFrom(context), which reads MediaQuery.platformBrightness rather than the app's ThemeData. On a device in OS dark mode with the app forced to light mode this produced invisible white-on-white text. Resolution now routes through GlassTheme.brightnessOf, the package's single brightness authority, consistent with how icon colours are resolved in the same widget.Custom bar items that draw their own glass no longer flash across a push or pop (#296): The pinned chrome fades and blurs item content by painting it
Custom bar items that draw their own glass no longer flash across a push or pop (#296): The pinned chrome fades and blurs item content by painting it under opacity and image-filter layers, and a glass surface painted under either has no backdrop to sample — a GlassBarItem.custom carrying its own GlassButton.custom capsule rendered as its opaque backer for the whole transition and snapped to glass on the last frame. GlassBarItemBackground.own marks such an item, and the cluster dissolves it through the surface's own visibility instead.
Pinned clusters now dissolve across a push or pop instead of popping in and out: GlassMenu wraps its trigger in a resting GlassMaterializeScope so the trigger can fade under an open menu, and every pinned cluster sits inside that wrapper — so the materialize the shell runs around a cluster only one route has never reached the glass. The outgoing shell stayed solid until it was dropped, the incoming one appeared solid, and for a few frames both were drawn. The menu's scope now composes with an enclosing one.
Thanks to @JakeThomson for the fix (#297).
Thanks to @hank205 for the fix (#299).
OverlayChildLocation.rootOverlay with OverlayChildLocation.nearestOverlay in both GlassMenu and GlassPopover. In addition, trigger position is now calculated locally relative to nearestOverlay (renderBox.localToGlobal(Offset.zero, ancestor: overlayBox)), preventing menu positioning drift in nested layouts while ensuring the menu lives in the same overlay stack as its route. As a result, incoming pushed routes render in front of closing menus instead of allowing the menu and its glass blur to float above the incoming page.go_router / Navigator.pages): Immediate route dismissal calls that fire during Flutter's persistent callbacks phase (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) are now deferred to post-frame callbacks via SchedulerBinding.instance.addPostFrameCallback. This completely eliminates the SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks assertion crash when updating pages in declarative routers.GlassMenuNavigationDemoPage (example/lib/demos/glass_menu_navigation_demo.dart) providing an interactive demonstration of clean overlay dismissal across imperative, declarative (go_router), and nested tab navigator setups with slow-motion transition controls.0.2126 R / 0.7152 G / 0.0722 B
(the standard for sRGB and Display P3) instead of the legacy BT.601 SDTV coefficients
that were previously in use. The correction is most visible on blue- and cyan-heavy
backgrounds where BT.601 measurably overestimated perceived brightness.`GlassNavigationShell` no longer marks its chrome dirty mid-build (#293): The shell listens to every registered route's animations and re-resolved the
GlassNavigationShell no longer marks its chrome dirty mid-build (#293): The shell listens to every registered route's animations and re-resolved the pinned chrome synchronously on each tick. A page-based Navigator applies its pages inside didUpdateWidget — the build phase — and a route with no transition completes its animation right there, so the tick landed as setState() or markNeedsBuild() called during build on the chrome's ListenableBuilder for every such push and pop, and the chrome missed that frame. Ticks now defer past build the way status changes already did.Thanks to @JakeThomson for the fix (#294).
GlassScaffold dark mode stuck white in MaterialApp (#289): GlassScaffold now scopes its internal CupertinoTheme and GlassStatusBarStyle.auto icon style through GlassTheme.brightnessOf. This correctly honours ThemeMode.dark / .light even when the device OS brightness differs from the app theme.
Menu and popover dismissal across nested Navigators (#274): GlassMenu and GlassPopover now listen to transitions across all ancestor ModalRoutes up to the root. When navigation occurs on an enclosing shell or root Navigator, the overlay dismisses immediately instead of lingering over the incoming destination page.
Thanks to @Vincen-dev for the reproduction and test (#274).
`GlassBodyMode` (`adaptive` vs `clear`) — exact design token color fidelity (#269): Introduced GlassBodyMode enum and bodyMode property on LiquidGlass
GlassBodyMode (adaptive vs clear) — exact design token color fidelity (#269):
Introduced GlassBodyMode enum and bodyMode property on LiquidGlassSettings (defaulting to GlassBodyMode.adaptive), achieving 1:1 parity with Apple iOS 26 Liquid Glass Glass.regular vs Glass.clear.
GlassBodyMode.adaptive (default): Employs iOS 26 dynamic luminosity normalization, ambient tint modulation, and brightness compensation based on underlying backdrop luminance.GlassBodyMode.clear: Bypasses luminosity normalization, white-point lift, and content-adaptive modulation. Directly composites the designer's exact hex color and alpha from glassColor over the refracted scene while preserving all 3D optical properties (specular rim reflections, Fresnel edge glow, meniscus edge absorption, and surface refraction).blur: 0 is combined with custom tinted glass surfaces.liquid_glass_render.frag, standard lightweight_glass.frag, and Skia _FrostedFallback).Decoupled track background quality in GlassTabBar:
Added backgroundQuality: GlassQuality? across GlassTabBar.bottom, GlassTabBar.inline, GlassTabBar.searchable, and GlassTabBar.minimizable.
UITabBarAppearance.backgroundEffect decoupling from the active selection capsule.backgroundQuality: GlassQuality.minimal or .standard) while the moving selection pill retains full liquid refraction (quality: GlassQuality.premium).null, which seamlessly inherits from quality without any visual regression.GlassTabBarTrailingButton.menu and GlassTabBarExtraButton.menu — native pull-down menus from tab bars (#275): Both the trailing pill on GlassTabBar.minimizable and the extra action button on GlassTabBar.bottom and GlassTabBar.searchable now support an optional GlassMenu pull-down, using the same .menu named-constructor pattern established by GlassButtonGroupItem.menu and GlassBarItem.menu.
// Minimizable trailing pill → opens a menu on tap
GlassTabBarTrailingButton.menu(
icon: const Icon(CupertinoIcons.ellipsis_circle),
label: 'More',
menuItems: [
GlassMenuItem(label: 'Edit', onTap: _edit),
GlassMenuItem(label: 'Share', onTap: _share),
],
)
// Bottom-bar extra button → opens a menu on tap
GlassTabBarExtraButton.menu(
icon: const Icon(CupertinoIcons.plus),
label: 'New',
menuItems: [
GlassMenuItem(label: 'New Note', onTap: _newNote),
GlassMenuDivider(),
GlassMenuItem(label: 'Import', onTap: _import),
],
)
GlassBarItem.menu and UIBarButtonItem(image:menu:) in SwiftUI.autoAdjustToScreen: true is always applied, so menus anchored inside a bottom bar always expand upward, matching the iOS 26 Liquid Glass UIMenu behaviour on toolbar items.enabled toggle: Setting enabled: false on either constructor dims the button and suppresses the menu — consistent with the existing tap-callback variant.Semantics node using the required label so screen readers announce the control correctly.GlassTabBarTrailingButton gains: menuItems, menuAlignment, menuWidth, label, enabled.GlassTabBarExtraButton gains: menuItems, menuAlignment, menuWidth. The label and enabled fields were already present.isMenu getter on both classes returns true when the menu variant is active.GlassMenu.triggerBuilder directly for fully custom triggers.Thanks to @JoetineY for the feature request (#275).
GlassModalSheet drag indicator geometry (#288): dragIndicatorHeight (default 4) and dragIndicatorTopPadding (default 8) join dragIndicatorWidth, so a sheet can match a host's own pill. Apple's own apps vary the pill's thickness, width and inset from sheet to sheet, and sit it higher where a control row follows (Maps), so there is no one right value to bake in; the defaults are unchanged.
Thanks to @jfhair for the feature (#288).
blur: 0, the surface previously suffered from unexpected luminance and saturation drift because the shader applied adaptive ambient and light calculations intended for blurred glass. Developers can now set bodyMode: GlassBodyMode.clear on LiquidGlassSettings to bypass adaptive tinting and composite the exact designer hex color while maintaining specular highlights, Fresnel sheen, and 3D meniscus edge refraction.Thanks to @JoetineY for the bug report (#269).
GlassTabBar.minimizable, the minimized pill drew the selected tab's icon in unselectedIconColor, so a custom selectedIconColor dropped out on minimize and returned on expand. The pill now uses selectedIconColor, as the native bar does — the tab is still selected, only the bar has shrunk. GlassTabBar.searchable is unchanged: its collapsed pill shows the tab search was opened from, which is no longer the selected one.Thanks to @JakeThomson for the fix (#280).
GlassScrollEdgeStyle.blur stays on its own route (#278): The progressive blur is a backdrop filter, and left unclipped a backdrop filter frosts everything beneath it up to the nearest ancestor clip — under a Cupertino pop that included the route being revealed, which showed the outgoing screen's top and bottom bands until the transition settled. The shader path lost its ClipRect when the region moved to paint time in 0.30.1; it is back, so a ProgressiveBlur now frosts nothing outside its own rectangle wherever it sits.Thanks to @JakeThomson for the fix and on-device integration tests (#281).
GlassAppBar title alignment (#282): Left-aligned titles (centerTitle: false) no longer add an unintended 8 px start gap when no leading widget is present, correctly aligning with content padding.Thanks to @Vincen-dev for the bug report and reproduction test (#282).
GlassMenu and GlassPopover dismiss immediately instead of playing the close spring across the destination route transition.Thanks to @Vincen-dev for the bug report (#274).
GlassModalSheet finishes its travel when a drag hands over to the content (#284): An upward content drag hands the pointer to the scroll view once the sheet is within 5% of its top detent, and that pointer's release is a scroll, not a drag — so the sheet parked up to 5% short of the top with no onStateChanged, the host still believed it sat at the lower detent, and the next collapse skipped its scroll-to-top. The sheet now snaps the remainder at the moment of handover: it arrives, reports full and gives its haptic as the content starts to scroll, not when the finger eventually lifts.Thanks to @jfhair for the fix (#284).
GlassModalSheetController.progress and value are current inside progressListenable (#285): Both reported the position the sheet had last built at, and the listener fires before that build — a frame behind, so the final tick of any drag never showed where the sheet stopped. They now refresh on every controller tick, before listeners run.Thanks to @jfhair for the fix (#285).
GlassModalSheet drags after a background launch (#290): The sheet read the window size once, in its first post-frame callback, and re-read it only on a change from a non-zero size. An app the system launches in the background for a push builds against a 0×0 window, so a sheet created then kept the zero for life: when the app came to the foreground, its first drag divided by that zero and parked the sheet at infinity — nothing painted, no onStateChanged, and every later snapToState starting from infinity — until the process was killed. The size is now taken from the view whenever the cache still holds that zero and refreshed on every window-size change, and a drag on a window with no size is ignored.Thanks to @jfhair for the fix (#290).
`GlassButton` presses like a native button (behaviour change) (#267): Measured at 120 fps, the press now grows by ~17 pt on a snappy spring (interacti
GlassButton presses like a native button (behaviour change) (#267): Measured at 120 fps, the press now grows by ~17 pt on a snappy spring (interactionScale: null), drags stretch no more than ~5 %, and the surface combines ambient lift (ambientBaseLight: 0.3) with a subtle shape-clipped specular sheen (glowRadius: null, resolving to a wide 1.6 radius with soft sigma-16 blur). The highlight is clipped strictly to shape via ShapeBorderClipper, staying bounded within the button geometry and sweeping across grouped buttons without creating a pointy hotspot. LiquidStretch declares its scale through LiquidGlassSelfScaleScope so the release undershoot no longer freezes refraction.
Migration: Pass
glowRadius: 0.0to disable the directional sheen (pure ambient lift). PassinteractionScale: 1.05,ambientBaseLight: 0.08, or useGlassInteractionSettingstheme-wide to customize.
Search circle and trailing pill press like native buttons (#272, #276): The collapsed pills now use GlassButton's ~17 pt growth, tremor stretch, and ambient lift, staying round through inflation. The expanded search field follows a lengthwise drag (≈7 pt saturation) and gives a couple of points of vertical stretch, measured against Photos. pressScale and ambientBaseLight are both nullable — null means the native behaviour, a number is a fixed override.
Thanks to @JakeThomson for the native press implementation, LiquidOval corner fix, and axis-constrained expanded field stretch (#276).
GeometryTransformTrackingLayer.addToScene now checks renderObject.attached and wraps getTransformTo(null) in a try-catch, preventing the StateError that occurred while render objects were detaching during page transitions.Thanks to @kdbhalala for the fix (#268).
Thanks to @kdbhalala for the optimisation (#268).
_RenderGlassGlowLayer): Eliminates per-frame Path allocations during gesture spring animations. _RenderGlassGlowLayer now follows the Flutter engine's RenderCustomClip pattern: clip paths are cached keyed on size, shouldReclip prevents re-clipping on value-equal rebuilds, and canvas.translate replaces .shift(). Drops native path heap churn from up to 240 allocations/s on 120 Hz ProMotion displays to zero steady-state allocations during drags.THIRD_PARTY_NOTICES added (#273): The pub.dev archive now includes full MIT copyright notices for the vendored liquid_glass_renderer and adapted motor spring utilities. README updated to link to the notices file.Inline bottom accessory reaches the trailing edge without a trailing button (#264): On GlassTabBar.minimizable, a minimized bar with a bottom accessor
GlassTabBar.minimizable, a minimized bar with a bottom accessory and no trailing button left a dead gap on the trailing edge — the accessory geometry reserved space for the trailing pill regardless of whether one was present. The accessory now extends flush to the horizontal padding when no trailing button is configured, and still clears the pill when one is.Thanks to @eomgerm for the fix (#265).
MinimizableTrailingPill — dedicated stateless pill for the trailing slot: The trailing-button pill was previously synthesised by re-using GlassSearchBarConfig with a dummy search config, creating a double-source-of-truth between _buildMinimizable and the layout engine. It is now a dedicated MinimizableTrailingPill widget sharing the same LayoutBuilder/oval-detection morph pattern as SearchPill — identical pill physics, single authoritative gate. A debug-mode assert now fires if searchConfig and trailingButton are both non-null; these are mutually exclusive rendering slots.Scrollable segmented control — selection stability and picker behaviors (#263): The pill no longer slides in from the track edge on mount or blinks ou
VelocitySpringBuilder.teleportEpoch). GlassSegment.id keeps surviving cells mounted across list changes. New opt-in picker behaviors: SegmentSelectionAlignment.center, SegmentDragBehavior.scroll, regridDuration (anchored re-grid morph, default Duration.zero), and an externally injectable scrollController.Thanks to @jfhair for the full implementation, tests, and example demo (#263).
chore(release): prepare 1.2.1
chore(release): prepare 1.2.1
Quality recovery is reachable again (#261): Glass quality demoted by a transient cost spike (a map, large images, a native view) now recovers after a sustained run of under-budget frames. Previously, frames landing in the neutral band between the upgrade and downgrade thresholds reset the recovery counter, making recovery essentially unreachable in practice.
Tab indicator settles back when host declines selection (#255): Dragging or tapping to a new tab when the host declines the selection (action tab, navigation guard, failed route push) previously left the indicator parked on the wrong tab. It now springs back to the host's current tab on the next frame across all gesture paths.
Sheet ↔ content scroll handover (#258): A multi-detent GlassModalSheet now grows and then scrolls its content on one unbroken drag, and reverses the same way. Content on a ScrollController of its own is now observed too — no call-site changes required.
GlassNavigationShell pinning no longer gated on blur quality (#262): Pinning is chrome geometry, not a shader effect — it costs no render pass of its own. The minimal quality gate that previously switched pinning off has been removed. GlassQualityAdapter steps down to minimal automatically under frame pressure, so pinning was silently disabling itself in debug builds and on low-end devices that need it most.
Thanks to @Nixxx19 for the quality recovery, tab indicator, and pinning gate fixes (#255, #261, #262).
Thanks to @JakeThomson for the sheet ↔ content scroll handover (#258).
Native gel morph for pinned clusters: A cluster present on both routes now reshapes the way the iOS 26 bar does instead of gliding between widths. Its
Native gel morph for pinned clusters: A cluster present on both routes now reshapes the way the iOS 26 bar does instead of gliding between widths. Its width and item positions ride the package's bouncy spring profile for the full length of the route transition, settling in the same breath the page lands; the spring's overshoot is deliberately not walked in width — it is expressed as a whole-shell squeeze instead, layered on a gel pulse that inflates the shell early — height, radius and glyphs together, as real geometry through the glass renderer. Glyphs no longer plainly cross-fade: an outgoing glyph smears away under heavy blur while the shell reshapes beneath it, and an incoming one arrives soft and sharpens last. A pop plays the same forward choreography toward the other cluster rather than the push in reverse. The morph stays a pure function of the route clock; timing and blur constants live together in GlassNavPinnedMetrics, and the choreography is locked down by a frame-by-frame trace test. (Clusters only one route has materialize instead — see below.)
Header Actions Morph demo: New pattern in the example app's Navigation Patterns page — a repository-style drill-down where each destination carries a different trailing cluster (contract to one, identifier-matched hold, widen to three), so every capsule morph can be exercised and scrubbed in isolation.
Progressive Blur Scroll Edge Style (GlassScrollEdgeStyle.blur): Introduces a hardware-accelerated GPU progressive Gaussian frost option via ProgressiveBlur, applying an ImageFilter.shader pass with ease-in quadratic falloff (falloff: 1.2) directly over live scrolling content. Default remains GlassScrollEdgeStyle.soft (the diffused gradient fade matching iOS 26's .scrollEdgeEffectStyle(.soft)). Developers can opt into .blur for richer frosting over custom dynamic gradients, video backdrops, or media grids.
Configurable maxSigma & Signed Fade Extents: Exposes maxSigma (default 18.0) for GlassScrollEdgeStyle.blur, alongside signed topEdgeFadeExtent and bottomEdgeFadeExtent (default 20.0) on GlassScaffold and GlassScrollEdgeEffect for granular transition zone control (including negative extents for tight floating-bar insets).
Materialize entrance & exit transitions (GlassMaterialize): Glass items now appear and disappear the way iOS 26 does, mirroring SwiftUI's glassEffectTransition(.materialize): the glass fades up as it settles inward from slightly oversized, and its content sharpens only after the shape has resolved — reversed on the way out, where the content blurs away first and leaves the shell briefly empty before it dissolves. GlassMaterialize is the implicit visible:-driven form (the AnimatedOpacity idiom); GlassMaterializeTransition is the explicit Animation-driven form (the FadeTransition idiom), and doubles as an AnimatedSwitcher.transitionBuilder via GlassMaterializeTransition.switcherBuilder. Works on any glass surface at any quality tier: rather than fading a layer — which pops, because a backdrop pass renders fully or not at all — the effect drives the glass shader's own visibility uniforms, where the refraction warp lerps to identity and the render pass drops out entirely at zero.
Pinned navigation chrome materializes (behaviour change): A pinned back button or actions capsule that only one of the two routes has no longer switches on or off at the transition midpoint — it materializes or dematerializes over a window straddling it. The phase is a pure function of route progress, so a pop plays the windows in reverse with the exit still leading the entrance in both directions. An interactive back-swipe does not scrub them: the page and title track the finger, but the chrome holds still until the gesture commits and then plays its transition over the travel that remains, so a swipe you abandon never half-dissolves anything. A capsule present on both routes is unaffected: it still morphs in place with one persistent glass shell. Opt out with GlassNavigationShell.effectTransition: GlassEffectTransition.identity, which restores the 1.1.0 behaviour exactly; Reduce Motion selects it automatically, and the standalone widgets likewise fall back to a plain cross-dissolve with no scale or blur.
Materialize Playground Demo: Added example/lib/demos/materialize_demo.dart — a single glass button, a multi-item capsule, and an AnimatedSwitcher swapping glass chips, each toggleable, over a busy backdrop with a live Reduce Motion switch.
GlassPinnedBarChrome — pin a bar that isn't a GlassAppBar: The registration handshake behind GlassAppBar.pinned is now public, so an app whose bars are its own widgets — a Material AppBar with a bespoke backdrop, a collapsing large-title sliver — can join a GlassNavigationShell without reimplementing it. Items are declared once as data; the builder receives chrome.leading and chrome.actions, which hold the real glass buttons until the shell has both accepted the registration and had a frame to render its copy, and same-sized unpainted placeholders after — so the bar never builds a second copy and nothing shifts at the hand-over. GlassAppBar.pinned now builds its own slots the same way, so there is one implementation rather than two. An enabled flag keeps a nested navigator's roots out of the shell, which ranks routes within a single Navigator. New Custom Bar Pinning pattern in the nav-patterns demo pins a plain Material AppBar.
Pinned leading items (GlassAppBar.pinned(leading:)): The pinned bar's leading slot is no longer limited to its own automatic back button, so a screen with a Cancel, a close button or a profile photo can pin its chrome instead of falling back whole. leading takes the same GlassBarItem vocabulary as actions and inherits the whole morph — width interpolation, id matching, icon cross-fade, taps swallowed mid-transition — because the cluster render object now anchors to whichever edge it is pinned to rather than always the trailing one. A non-empty leading replaces the back button, matching UINavigationItem.leftBarButtonItems and Flutter's own AppBar.leading; leadingItemsSupplementBackButton: true shows both, mirroring UINavigationItem.leftItemsSupplementBackButton. A lone back button renders exactly as before.
GlassBarItemBackground — per-item glass: Collapses the two booleans iOS 26 added to UIBarButtonItem into their three distinct results: shared (the default — sharesBackground, items form one capsule), separate (sharesBackground: NO — its own shell, which at a lone icon's size is the circular button iOS 26 draws for a single bar item) and none (hidesSharedBackground — no glass at all, for content that carries its own shape, such as a profile photo). A cluster now renders one shell per group rather than always one capsule.
Leading Items demo: The nav-patterns showcase gains a pattern walking the four leading configurations — a bare avatar, the implied back button, a leading that replaces it, and one beside it — plus the lone circular shell growing into a two-item capsule.
Scroll Edge Playground Demo: Added a comprehensive interactive showcase in the example app (example/lib/demos/scroll_edge_style_demo.dart) featuring live style switching (soft, hard, blur), real-time extent/sigma sliders, top/bottom toggles, and floating GlassAppBar & GlassTabBar.bottom integration with solid content cards.
Bottom accessory follows the bar (behaviour change) (#226): on
GlassTabBar.minimizable, a bottomAccessory with no explicit
bottomAccessoryPlacement now resolves to
GlassTabBarAccessoryPlacement.inline while the bar is minimized, matching
how iOS 26 animates a tabViewBottomAccessory down into the minimized bar.
Previously it stayed expanded unless inline was passed explicitly.
This changes what GlassTabBarAccessoryPlacementScope.of(context) returns
for affected callers — an accessory that switches on it will render its
compact variant on scroll where it previously did not, with no code change on
your side — and shrinks preferredSize by
bottomAccessorySpacing + bottomAccessoryHeight while minimized. Affects only
bars that have an accessory, pass no explicit placement, and reach the
minimized state. Pass GlassTabBarAccessoryPlacement.expanded to keep the
previous behaviour.
GlassTabBar.searchable is deliberately unchanged. Auto-collapsing on search
was removed in 0.x because it hid the mini-player behind the search capsule,
and that decision stands — a search field expanding is not the bar minimizing.
PlatformViewGlassMode.passthrough — glass over platform views (#247): New enum value on LiquidGlassSettings. Instead of resolving to black where the backdrop capture held nothing (over a map, camera, video, or WebView), coverage follows what was actually sampled so the live view shows through. Default is PlatformViewGlassMode.fallbackColor — no behaviour change for existing callers. GlassTabBar gains a matching passthroughOverPlatformView flag that lifts selected-tab content above the glass to avoid the doubled-label the refracted icon layer would otherwise cause.
GlassChip.platformViewBackdrop (#250): GlassButton already exposed this parameter; GlassChip now surfaces and forwards it, closing the gap for chips rendered over platform views. Default is false, no change for existing callers.
GlassNavPinnedMetrics is now exported from the package barrel: The geometry the pinned shell redraws hoisted chrome at — 44pt back circle, 46pt action slots, 44pt toolbar band, 8pt edge inset. A bar that is not a GlassAppBar had no supported way to reach it and had to hardcode the numbers, which drift the first time the package retunes them. The show clause exposes the metrics only; GlassNavPinnedHost and the cluster render objects stay internal. Documented under Glass Navigation Transition.
GlassNavPinnedMetrics.crossFadeStart/crossFadeEnd changed meaning: These are now fractions of the morph window (morphStart..morphEnd) rather than raw route progress; glyphSharpenStart/glyphSharpenEnd follow the same convention. Read them through GlassNavPinnedMetrics.morphProgressAt to align custom chrome.Icon(Icons.x) literals on different routes cross-faded with themselves on every push. Icons are now compared by value (glyph, size, colour, key).GlassTabBarMinimizeController drivable without a ScrollController: handleSample is no longer @visibleForTesting, and a new handleNotification(ScrollNotification) method drives the bar directly from a NotificationListener. Leave GlassTabBar.minimizable's scrollController unset when using this path.blur: 0 no longer downgrades any tier to _FrostedFallback (#253): A blur: 0 surface now keeps its rim, Fresnel, and specular — the shader's own guard already handles the zero-blur pass cleanly. Visual change at standard: surfaces that previously resolved to _FrostedFallback now render the lightweight shader. Use GlassQuality.minimal explicitly if frost fallback was intentional.GlassModalSheet, or fullscreenDialog previously left the pinned glass back button and actions capsule painted above the presentation at full brightness. The shell now hands the chrome back to the presented route so it dims with everything else.Thanks to @JakeThomson for the materialize transitions, pinned navigation chrome, leading API and per-item glass backgrounds, scroll-to-minimize controller improvements, bottom accessory inline behaviour, metrics export, and the presented-route chrome fix (#240, #238, #236, #239, #233, #234, #259).
Thanks to @marco242424 for the native gel morph (#243).
Thanks to @Nixxx19 for the platform-view glass passthrough mode, GlassChip backdrop parameter, and the blur tier-downgrade fix (#247, #250, #253).
feat(GlassNavigationShell): pin navigation-bar chrome across route transitions
GlassNavigationShell hosts the glass back button and trailing actions capsule above the Navigator, reproducing the iOS 26 navigation bar: page content and title slide during push/pop (including the interactive back-swipe, scrubbed proportionally) while the glass chrome stays pinned and morphs in place. Screens opt in with the GlassAppBar.pinned constructor, declaring actions as data (GlassBarItem.icon / GlassBarItem.custom); items sharing an id are treated as the same item across routes, mirroring UIBarButtonItem.identifier. Custom widgets are measured at intrinsic width, exactly as UIKit measures a customView. Works with any Pages-API router (go_router, auto_route, beamer) — the shell only reads ModalRoute animations. Without a shell, or where the effect cannot render, the same data renders in-route as today's glass capsule.GlassBarItem.menu — pull-down menus in a pinned bar: The UIBarButtonItem.menu analogue, and the iOS 26 overflow button. The whole capsule morphs into the pull-down, matching GlassButtonGroupItem.menu. Menus cannot be opened mid-transition, and one already open is dismissed when navigation starts — the pinned capsule outlives the route that owns it, so nothing else would. Falls back to GlassButtonGroupItem.menu in-route wherever pinning does.GlassModalSheet swipe-dismissals morph back from the release point (#223): Dragging a morphed sheet away used to skip the morph and slide the sheet off. Below the lowest detent the sheet now shrinks about the grabbed point as it follows the finger — the interactive zoom-dismissal measured off iOS 26 — and the release hands that exact frame to the closing morph. Sideways, the card chases the finger through a tracking spring, pinning at the screen edge. A release short of the dismiss threshold springs back. Sheets shown without morphFrom keep their plain slide-away unchanged.GlassTabBar.minimizable (#228): A new GlassTabBarMinimizeController drives the minimize from scrolling — the Flutter equivalent of SwiftUI's .tabBarMinimizeBehavior(_:). GlassBarMinimizeBehavior mirrors Apple's enum case for case. The trigger is accumulated distance, not a per-frame delta — fixing the 1.0.0 ProMotion 120 Hz reliability issue.SearchableBottomBarController.makeSpring now carries in-flight velocity through retargets; reversing mid-morph no longer reads as a stall followed by a restart. Also benefits GlassTabBar.searchable.ScrollController crash fixed (#228): The searchable and minimizable placements reading ScrollController.position now safely guard when multiple scroll views share one controller during transitions.RenderLiquidGlassLayer froze shader UVs under any uniform scale-down, which was correct for the CupertinoSheet push-back but wrong for a swiped GlassModalSheet whose backdrop holds still. A new internal LiquidGlassSelfScaleScope lets the sheet declare which arrangement it is; the push-back path is unchanged.GlassNavActionSlot.crossFades — documented widget-equality behaviour: Cross-fade detection uses reference equality; const icon widgets share an instance across routes and never trigger a spurious cross-fade. Documented in the API dartdocs and the navigation guide.GlassBarItem.menu in one cluster — debug warning: Using more than one menu item in a single cluster silently treated the extras as plain icons. A debug-mode debugPrint now surfaces this so developers see the constraint immediately rather than wondering why a second menu does not open.GlassModalSheet fling velocity carried into the closing morph (#227): A fast swipe no longer stalls at the release point — the closing droplet now starts at the speed the fling was running at rather than from rest.GlassModalSheet.peekSize docs: Clarified that peekSize accepts both absolute pixels (> 1.0) and a screen-height fraction (≤ 1.0), consistent with halfSize and fullSize.docs/GLASS_NAVIGATION_TRANSITION.md — setup (including .router), matching rules, a behaviour table, a direction section, and known limitations.ROADMAP.md — GlassNavigationTransition checked off; the "pinning becomes the default" trajectory and parity work recorded.Thanks to @JakeThomson for the navigation transition, modal sheet, tab bar scroll-to-minimize, and premium renderer fix improvements (#221, #223, #227, #228, #230).
Release v1.0.0 — General Availability
Release v1.0.0 — General Availability
This release marks the 1.0.0 General Availability of liquid_glass_widgets, delivering a stable, unified API surface, zero third-party dependencies, and production-grade iOS 26-style liquid glass with hardware-adaptive quality tiers (Metal, Vulkan, Skia, and shader-free fallbacks) across all supported Flutter platforms.
GlassTabBar.minimizable() — SwiftUI tabBarMinimizeBehavior parity (#217): A new named constructor that collapses the tab bar to a single selected-tab circle without requiring a search bar. minimized replaces isSearchActive, onMinimizedTabTap handles the restore tap, minimizedBarHeight shrinks both pills while minimized, and an optional GlassTabBarTrailingButton fills the trailing slot — exactly modelling Tab(role: .search) priority-visibility behaviour. Scroll-driven minimize is caller-controlled, matching the SwiftUI .onScrollDown pattern.GlassSearchBarConfig.showPill (#217): A new boolean on GlassSearchBarConfig (default true) that removes the search pill entirely and returns its width to the tab pill. Hiding is done by unmounting the pill and spring-scaling it at its slot — Opacity and zero-width leave a glass remnant fused to the tab pill on the shared blend layer; only absence can hide a grouped glass surface.Thanks to @JakeThomson for the contribution (#217).
GlassModalSheet morphs from a trigger button (#219): show() gains morphFrom — the GlassMorphAnchor from a new GlassMorphTrigger wrapper — presenting the sheet with the iOS 26 liquid morph instead of the slide-up: the trigger empties, a glass droplet detaches and inflates as it travels, and lands as the sheet. Makes the sheet the second consumer of the Liquid Morph Engine after GlassMenu. morphSpeed tunes the spring.GlassMorphTrigger / GlassMorphAnchor (#219): The wrapper owns the trigger's key and opacity so the trigger can empty itself for the morph and take the closing bounce on its own ticker. morphFromRect remains for triggers that can't be wrapped, blooming from a point. The morph degrades gracefully to the slide presentation on Skia/web, GlassQuality.minimal, and platformViewBackdrop.Thanks to @JakeThomson for the contribution (#219).
GlassTabBar):
GlassTabBar via named constructors: GlassTabBar.bottom(), GlassTabBar.searchable(), and GlassTabBar.inline().GlassBottomBar and GlassSearchableBottomBar.GlassTab across all tab bars.GlassTabBarCollapseConfig and GlassTabBarCollapseDirection have been removed. The collapse-to-extra-button pattern is not an iOS 26 design primitive and was unreliable on 120 Hz ProMotion displays. Use GlassTabBar.minimizable instead — it directly mirrors SwiftUI's tabBarMinimizeBehavior(.onScrollDown) with spring physics.enablePeek parameter from GlassModalSheet, GlassModalSheet.show(), and GlassModalSheetScaffold. Sizing and peeking behavior is now governed cleanly and declaratively by detents and mode.respectsAccessibility from LiquidGlassWidgets.initialize(). System accessibility preferences (Reduce Motion, Reduce Transparency) are now automatically detected and respected out of the box.warmUpImpellerPipeline from LiquidGlassWidgets.initialize(). Shader bytecode preloading is handled asynchronously and safely during app bootstrap.usesBackdropFilter getter from GlassQuality.LiquidGlassScope.stack, GlassRefractionSource, and LiquidGlassBackground in favor of GlassPage and GlassBackgroundSource.GlassBackdropScope stub.For pre-1.0 history see CHANGELOG_ARCHIVE.md or the GitHub Releases page.
GlassPullDownButton / GlassMenu crash in minimal quality (#214): Fixed a crash when quality falls back to GlassQuality.minimal. LiquidGlassBlendGroup
GlassQuality.minimal. LiquidGlassBlendGroup is now skipped when no LiquidGlassLayer is present in the tree. Same fix applied to GlassPopover.Dark mode collapsed tab pill icon (#208): Fixed an issue where the collapsed tab pill icon in GlassTabBar.searchable rendered opaque black in dark mod
GlassTabBar.searchable rendered opaque black in dark mode instead of white when unselectedIconColor was unset.ProgressiveBlur rendering black or losing its gradient when not positioned at the top-left of the backdrop layer (modal sheets, inset containers, scroll edges). The shader region origin is now resolved at paint time, keeping the gradient correct through drags and animated transitions.v0.30.0: Eliminate Windows Impeller startup hang and Android GLES ANR…
v0.30.0: Eliminate Windows Impeller startup hang and Android GLES ANR…
LiquidGlassWidgets.initialize() no longer submits blocking GPU draw calls to the raster thread prior to runApp(). On Flutter 3.47+ Windows Impeller (ANGLE / OpenGLESSDF), runtime GLSL driver compilation previously locked the raster thread during OS surface initialization, preventing the native window from presenting. The app window now displays immediately on Frame 1 on all Windows and desktop configurations.runApp offscreen warm-up draw. All Android devices launch with zero splash-screen delay, and GLES devices are safely protected from runtime driver compile lockups.LiquidGlassWidgets.initialize() now preloads shader bytecode asynchronously into memory via fast I/O on Android (Vulkan and GLES), iOS, and macOS, eliminating first-frame placeholder flashes while ensuring zero GPU raster stalls before runApp().runApp(): Removed all toImageSync calls from preWarm(). Internal 1×1 sampler dummy textures are now allocated lazily on first paint in the widget tree.GlassWarmUpMode configuration: Added warmUpMode (GlassWarmUpMode.auto, .always, .never) to LiquidGlassWidgets.initialize(). Default .auto preloads all shaders for Android, iOS, and macOS, while skipping unused premium shaders on statically-capped desktop/web backends. Deprecated warmUpImpellerPipeline.GlassAdaptiveScope static probe defaults Windows and Linux to GlassQuality.standard (lightweight_glass.frag with real iOS 26 squircle geometry, dual specular highlights, meniscus absorption, and blur) for guaranteed 60/120fps performance without driver compile delays.shaders/sdf.glsl now caps shape evaluation at 8 shapes on OpenGL ES / ANGLE backends via #ifdef LGR_OPENGLES_CAP_SHAPES (shaders/gles_compat.glsl), reducing the inlined AST size for runtime JIT drivers while leaving Metal (iOS/macOS) and Vulkan (Android) on the full 16-shape unrolled AOT path with zero changes.Pure Flutter SDK dependencies: Removed all third-party and external dependencies across runtime and dev environments (equatable, flutter_shaders, logg
equatable, flutter_shaders, logging, meta, alchemist, and mocktail). Golden tests now use Flutter's native matchesGoldenFile. Package depends purely on flutter: sdk: flutter.Equatable with native operator == and Object.hashAll across shapes and settings, eliminating heap allocations during hot animation loops.dart:ui.FragmentProgram with cached isolate pipelines and zero-overhead uniform setters.Progressive blur: 50% fewer texture reads. Gaussian tap count halved with identical ±3σ coverage; GPU memory bandwidth for the blur pass is halved wit
exp() eliminated. Per-tap exponential replaced with a two-scalar IIR recurrence — saves 24 GPU instructions per blur pass with mathematically identical output.edgeAbsorption parameter added (LiquidGlassSettings, GlassThemeSettings, default 0.0): Physical rim darkening — the glass absorbs more light at the thickest edge. Default 0.0 matches iOS 26's crisp luminous glass. Increase (0.10–0.20) for physical-depth or iOS 27-style recipes.fresnelStrength parity: fresnelStrength now controls grazing-angle rim highlights identically across all rendering engines (Skia, Web, Windows, Android, and Impeller).MeniscusAndBlurDemoPage): Interactive calibration workbench for live testing of edgeAbsorption, fresnelStrength, thickness, blur, and progressive blur performance.GlassTabBarExtraButton loses backdrop blur in minimal quality (#203): Added isStationary flag to GlassButton (default false). Setting isStationary: true on GlassTabBarExtraButton retains its BackdropFilter blur in GlassQuality.minimal.A new shaders/gles_compat.glsl header gates the flip on IMPELLER_OPENGLES_UNFLIPPED_DEPRECATED, the migration macro introduced by the Flutter engine t…
shaders/gles_compat.glsl header gates the flip on IMPELLER_OPENGLES_UNFLIPPED_DEPRECATED, the migration macro introduced by the Flutter engine team for exactly this transition. The fix is correct across all Flutter versions from the existing >=3.41.0 minimum; the pubspec.yaml constraint does not move. A new source-level regression guard in test/shaders/gles_flip_guard_test.dart prevents the pattern from re-appearing.Thanks to @TIANLI0 for the contribution (#202).
`GlassAppBar` title not centred when `leading` is set (#198): Toolbar layout rewritten using a CustomMultiChildLayout delegate, matching the approach
GlassAppBar title not centred when leading is set (#198): Toolbar layout rewritten using a CustomMultiChildLayout delegate, matching the approach used by Flutter's own CupertinoNavigationBar. The title is now centred on the full bar width regardless of leading/trailing widget sizes, and is constrained to never overlap either button group. centerTitle: false correctly left-aligns the title after the leading widget in both LTR and RTL locales.`GlassModalSheet` stale `_currentState` (#197): A drag ending at its origin state left _currentState holding the predicted mid-drag target. _currentSt
GlassModalSheet stale _currentState (#197): A drag ending at its origin state left _currentState holding the predicted mid-drag target. _currentState is now reconciled on every snap unconditionally, preventing the sheet from appearing stuck after a short drag.GlassModalSheet overscroll axis guard (#197): _onScrollNotification now ignores notifications whose metrics.axis is not Axis.vertical, preventing horizontal descendant lists (carousels, date strips) from hijacking the sheet gesture.GlassModalSheet one-shot axis lock (#197): Gesture axis is decided once on the first movement past the threshold and held until the touch lifts, preventing a sideways swipe from later grabbing the sheet mid-gesture.Thanks to @jfhair for the contribution (#197).
Premium glass lens detaches during `CupertinoSheet` drag (#192): The refracted lens drifted away from its pill while an interactive CupertinoSheet dra
CupertinoSheet drag (#192): The refracted lens drifted away from its pill while an interactive CupertinoSheet drag scaled the background. Fixed by snapshotting the layer's unscaled screen-space coordinates on every paint frame and freezing them the moment a uniform ancestor scale-down is detected, keeping UV mapping locked to the captured texture for the duration of the drag.GlassAppBar title typography (#194): The title widget is now wrapped in DefaultTextStyle using CupertinoTheme's navTitleTextStyle, matching native CupertinoNavigationBar behaviour. A plain Text widget now automatically picks up correct Cupertino typography (weight, size, ellipsis) without manual styling. Also adds Semantics(header: true) for VoiceOver/TalkBack navigation.The Liquid Glass rendering engine now achieves 1:1 mathematical parity across all display densities (e.g., macOS 2.0x, iOS 3.0x, and various Android f
The Liquid Glass rendering engine now achieves 1:1 mathematical parity across all display densities (e.g., macOS 2.0x, iOS 3.0x, and various Android fractional densities like 2.75x or 3.5x). Previously, running shaders in physical pixels caused inconsistent refraction scaling on high-density screens.
effectiveThickness is scaled by DPR, guaranteeing identical refraction depth across devices.0.15 → 0.0) from the default animated pill to eliminate the rainbow rim artifact, while preserving true lens distortion.brightnessResolver before CupertinoTheme.of. This acts as a defensive backstop for older Flutter versions or edge cases where the MaterialBasedCupertinoThemeData bridge fails to propagate ThemeMode correctly.GlassModalSheet drag indicator now exposes a Semantics.onTap action that correctly triggers sheet dismissal, matching Material's handle behavior.GlassModalSheet's dragIndicatorColor is now honored. It was previously accepted by the API but dropped internally in favor of hard-coded defaults.GlassAdaptiveScope now seeds at maxQuality (premium) on Android from the very first frame. Previously, Android cold-started at standard and promoted to premium only after the 3-second Phase 2 benchmark. The ANR safety net (shader pre-compilation in LiquidGlassWidgets.initialize()) makes this safe; Phase 2 continues to demote genuinely slow/budget devices.fix: normalize all physical pixel constants by DPR to achieve pixel-p…
fix: normalize all physical pixel constants by DPR to achieve pixel-p…
GlassSegmentedControl — duplicate unlabeled semantics node (#188)
Each segment emitted two tappable nodes (one unlabeled), breaking VoiceOver/TalkBack. Fixed by adding excludeFromSemantics: true to the internal GestureDetector; semantics are fully handled by GlassFocusRegion.
GlassTabBar shadow lost in Dark OS + Light app (#124)
Shadow disappeared when the device was in Dark Mode but ThemeMode.light was set. Introduced a zero-material IoC bridge: pass brightnessResolver: Theme.maybeBrightnessOf to LiquidGlassWidgets.wrap() so the package correctly honours ThemeMode without importing flutter/material.dart.
Migration — MaterialApp users must add this one line to fix #124:
runApp(LiquidGlassWidgets.wrap( child: const MyApp(), brightnessResolver: Theme.maybeBrightnessOf, ));
CupertinoAppusers: no change required.
Dead flutter/material.dart import removed from tab_bar_bottom_internal.dart
Leftover from pre-0.26.0, never cleaned up. lib/ is now 100% zero-material for cupertino_ui compatibility.
Eliminates production ANRs on Android devices running Impeller GLES (devices without Vulkan support, including many MediaTek and budget Qualcomm Snapdragon SoCs).
On Android GLES, glCompileShader + glLinkProgram executes synchronously on the
Flutter raster thread at first use (100–800 ms on mid-range hardware). When this
coincides with FlutterJNI.nativeSurfaceChanged during surface setup, Android's
watchdog declares an ANR. The previous warm-up implementation instantiated a
LiquidGlassLayer widget outside the widget tree — an unmounted widget is never
rasterized, so no GPU work occurred. The warm-up was a no-op.
True GPU warm-up (liquid_glass_setup.dart): _warmUpImpellerPipeline() now
draws both premium glass shaders to a 1×1 off-screen surface using
Picture.toImage() and awaits rasterization. This forces GLES pipeline compilation
on the raster thread while initialize() is still running — before runApp — so
compilation completes behind the native splash screen and cannot race with surface
setup.
Android-only execution: The warm-up is guarded by
defaultTargetPlatform == TargetPlatform.android. iOS and macOS use precompiled
Metal shaders and skip this step entirely, preserving their zero startup overhead.
Reuses cached programs: The warm-up now calls MultiShaderBuilder.cachedProgram()
to retrieve the FragmentProgram objects already loaded by precacheShaders() in
step 1 of initialize(). No duplicate GPU objects are created.
warmUpImpellerPipeline parameter: LiquidGlassWidgets.initialize() accepts a
new warmUpImpellerPipeline: bool parameter (default true). On non-Android
platforms the parameter is a no-op. Pass false only if you are managing Android
shader warm-up yourself.
Conservative Android quality seeding (glass_adaptive_scope.dart): Fixed a
code-comment mismatch in _GlassAdaptiveScopeState.initState. The file header
documented seeding at GlassQuality.standard; the code seeded at maxQuality
(premium). On Android, initState now correctly seeds at GlassQuality.standard
so Phase 2 benchmarks the device from a stable baseline. iOS and macOS continue to
seed at maxQuality for an immediate premium experience.
Existing call sites (await LiquidGlassWidgets.initialize()) are unchanged and
benefit from the fix automatically. The adaptiveQuality: true path also benefits
from the corrected initState seeding on Android.
Added bottomAccessory / bottomAccessoryHeight / bottomAccessoryEnabled / bottomAccessorySpacing / bottomAccessoryPlacement to both GlassTabBar.bottom
tabViewBottomAccessory SupportAdded bottomAccessory / bottomAccessoryHeight / bottomAccessoryEnabled / bottomAccessorySpacing / bottomAccessoryPlacement to both GlassTabBar.bottom and GlassTabBar.searchable — mirroring Apple's tabViewBottomAccessory modifier.
6.0px, calibrated to match Apple's native spacing).searchable only) — set bottomAccessoryPlacement: GlassTabBarAccessoryPlacement.inline to have the accessory slide horizontally into the gap between the collapsed tab indicator and search capsule, with a simultaneous width squish, matching the iOS 26 .inline placement.accessoryT drives the inline↔expanded morph (height, left, right) while searchT tracks the tab-pill→search-capsule height change so the accessory follows the bar downward during the search activation, maintaining a consistent visual overlap gap throughout.bottomAccessoryPlacement defaults to .expanded. The accessory never collapses inline automatically; developers must explicitly opt into .inline placement, mirroring the iOS 26 model where placement intent is declared at the call site.preferredSize is always in sync with the layout engine so GlassScaffold's edge fade reserves the exact correct amount of space in both expanded and inline states.GlassTabBarAccessoryPlacement enum — expanded and inline values, readable inside the accessory widget itself via GlassTabBarAccessoryPlacementScope.of(context) to adapt the accessory's own layout between the full row and compact strip.bodyOverlaysPreviously, the recommended pattern for a floating mini-player was to place it in GlassScaffold.bodyOverlays and manually manage its position using AnimatedPositioned with scroll-offset math. That approach still works and bodyOverlays remains available for other use cases (e.g. floating action overlays, toast banners).
For a bottom accessory that is architecturally part of the tab bar — which is exactly what iOS 26 tabViewBottomAccessory models — the bottomAccessory API is the correct replacement:
// Before (bodyOverlays workaround)
GlassScaffold(
bodyOverlays: [
AnimatedPositioned(
bottom: _isMiniMode ? barH : barH + accessoryH + spacing,
left: 0, right: 0,
child: MiniPlayer(),
),
],
)
// After (iOS 26-aligned)
GlassTabBar.searchable(
bottomAccessory: MiniPlayer(),
bottomAccessoryHeight: 50.0,
bottomAccessoryPlacement: _isMiniMode && !_isSearching
? GlassTabBarAccessoryPlacement.inline
: GlassTabBarAccessoryPlacement.expanded,
)
The new API removes all manual position arithmetic, keeps the GlassScaffold edge fade pixel-accurate, and persists the accessory automatically across tab switches.
100% Dartdoc coverage — every public member is documented. public_member_api_docs is now permanently enabled in analysis_options.yaml; future undocume
public_member_api_docs is now permanently enabled in analysis_options.yaml; future undocumented public API additions will fail dart analyze.lib/src/ — 9 internal implementation files relocated per Dart convention so dart doc and pub.dev omit them from the generated API reference. No public API changes. 2517 tests passing.Completed the Right-to-Left (RTL) layout audit. All directional padding and alignment primitives now use EdgeInsetsDirectional / AlignmentDirectional
Completed the Right-to-Left (RTL) layout audit. All directional padding and
alignment primitives now use EdgeInsetsDirectional / AlignmentDirectional
so widgets mirror correctly in RTL locales (Arabic, Hebrew, Persian, etc.)
without any API changes for existing callers.
GlassGroupedSection — header and footer labels now use
EdgeInsetsDirectional.only(start: 16, end: 16). Under RTL the text aligns
to the correct physical edge.GlassDivider — horizontal divider indent / endIndent now use
EdgeInsetsDirectional.only(start: indent, end: endIndent). The leading
indent is always on the logical leading side regardless of text direction.
Vertical divider top / bottom are unchanged (not directional).GlassAppBar — non-centered title now uses
AlignmentDirectional.centerStart + EdgeInsetsDirectional.only(start: 8)
so the title anchors to the leading edge in both LTR and RTL.GlassSearchBar — cancel button gap uses
EdgeInsetsDirectional.only(start: 10) so the gap appears between the
search field and the cancel button in both directions.GlassDialog — horizontal two-button layout gap uses
EdgeInsetsDirectional.only(start: 8) so buttons remain correctly spaced
in RTL.GlassLargeTitle — two improvements:
padding and searchBarPadding defaults changed from EdgeInsets.* to
EdgeInsetsDirectional.* — the field types were already EdgeInsetsGeometry,
so this is a zero-breaking-change improvement. Callers who pass custom
asymmetric directional padding (e.g. EdgeInsetsDirectional.only(start: 32))
now get correct physical mirroring in RTL.Transform.scale alignment changed from Alignment.bottomLeft to
AlignmentDirectional.bottomStart — under RTL, the rubber-band overscroll
stretch now scales from the correct logical leading edge rather than always
pinning to the physical left.GlassProgressIndicator.linear — custom canvas drawing now explicitly
respects Directionality. In RTL locales, both the determinate fill and
indeterminate moving bar correctly animate from the physical right (logical start)
to the left.GlassSlider — drag logic and active track drawing now invert cleanly under
RTL. Dragging left increases the value, and the track anchors to the physical right.GlassTabBar.bottom — Resolved an edge-case visual bug in RTL mode where the animated glass pill would jump to the mirror-image tab when selecting an end tab. Fixed by enforcing Alignment(x, y) physical coordinates instead of AlignmentDirectional within the physics engine bounds.GlassSegmentedControl & GlassTabBar — Unselected segments now correctly emit a "not selected" accessibility state (resolves #184). Thanks to @Xodus-CO for the detailed report!Every interactive widget now supports full keyboard traversal, Space/Enter activation, and VoiceOver/TalkBack semantics.
Every interactive widget now supports full keyboard traversal, Space/Enter activation, and VoiceOver/TalkBack semantics.
GlassFocusRegion — new shared widget that is the single source of truth for all focus behaviour. Two modes:
FocusableActionDetector, registers ActivateIntent (Space/Enter), lifts hover/focus state to parent via ValueNotifier, and paints the iOS 26-style outset focus ring..observe()) — for widgets that own their own FocusNode (e.g. GlassTextField). Listens passively and paints the ring; no duplicate traversal or intent handling.GlassFocusRingPainter (pure CustomPainter; zero cost for touch users via ValueListenableBuilder).GlassStepper keyboard fix — Space/Enter triggers a single-shot activation; the pointer-only long-press repeat timer is no longer erroneously started by keyboard input.GlassButton, GlassSwitch, GlassSlider, GlassSegmentedControl, GlassListTile, GlassMenuItem, GlassActionSheet, GlassButtonGroup, GlassTextField, GlassSearchBar, GlassStepper, GlassChip.isButton, isSlider, isSelected, toggled, value) set on every widget matching Material and Cupertino SDK conventions.GlassBadge — semanticLabel and semanticCount overrides — resolves two user-reported limitations:
semanticLabel (optional String?) fully replaces the VoiceOver/TalkBack announcement; enables non-notification domains ("5 downloads", "Online", etc.).semanticCount (optional int?) overrides only the spoken number while leaving the visual cap (99+) unchanged; allows "2500 notifications" to be announced when the badge visually shows "99+".GlassBadge and GlassBadge.dot) support semanticLabel; semanticCount is only applicable to count badges.GlassPageControl — semanticLabel param — the capsule now announces 'Page N of M' to VoiceOver/TalkBack by default (1-indexed). A semanticLabel override lets callers substitute domain-specific wording (e.g. 'Slide 3 of 5'). Non-interactive controls (no onPageChanged) omit the tap hint automatically.GlassPasswordField — toggle button semantics — the show/hide password suffix icon is now wrapped in Semantics(label: 'Show password' / 'Hide password', button: true). Previously the GestureDetector was invisible to screen readers; VoiceOver now correctly announces the button and its toggled state.GlassProgressIndicator — semanticLabel param — the hardcoded 'Progress' label is now an overridable default. Callers can pass semanticLabel: 'Download progress' (or any domain string) to both .circular() and .linear() constructors. Backwards-compatible.GlassPullDownButton — semanticLabel param — icon-only pull-down buttons previously announced an empty string. A new semanticLabel param (e.g. 'More options') is used as the GlassButton.label in the icon-only code path. Visible-label variants are unaffected.GlassInteractionStateMixin — internal State mixin (modelled on SingleTickerProviderStateMixin) providing isPressed, isFocused, isHovered ValueNotifiers and their Listenable.merge combinations. Replaces ~76 lines of identical boilerplate across GlassListTile, GlassMenuItem, _ActionSheetButton, and _GlassGroupItemWidget. No public API change.ValueNotifier + ListenableBuilder — only the AnimatedContainer highlight layer rebuilds on interaction; the surrounding subtree is stable.`GlassAdaptiveScope` quality oscillation after `reset()` — when allowStepUp: false, a reset()-triggered warm-up could silently promote quality back up
GlassAdaptiveScope quality oscillation after reset() — when
allowStepUp: false, a reset()-triggered warm-up could silently promote
quality back up (e.g. standard → premium). Warm-up now respects the flag
and can only confirm or lower the current quality, never raise it. Thanks to
@jingluoguo for the contribution (#180).Remove GlassScaffold.floatingActionButton (breaking, pre-v1 cleanup)
GlassScaffold.floatingActionButton removed — iOS does not use Floating
Action Buttons. floatingActionButton is removed entirely; this package is
pre-v1 and that is the window to get the API right.
Migration:
// Before
GlassScaffold(
floatingActionButton: FloatingActionButton(onPressed: _add, child: Icon(Icons.add)),
body: ...,
)
// After — glass-treated, iOS-idiomatic
GlassScaffold(
bodyOverlays: [
Positioned(
bottom: 24, right: 24,
child: GlassButton(
onTap: _add,
child: const Icon(CupertinoIcons.add, color: CupertinoColors.white),
),
),
],
body: ...,
)
bodyOverlays is above the body and below the bars. GlassButton applies
the correct liquid glass treatment.
material.dart 36 → 0)GlassScaffold now uses CupertinoPageScaffold internally. GlassPage no
longer injects a Material Theme shim. All Colors.* constants replaced with
CupertinoColors or explicit hex literals. No visual changes.
glass_brightness.dart was completely rewritten to drop its dependency on
Theme.maybeBrightnessOf. It now reads CupertinoTheme.of(context).brightness,
which natively inherits from the Material ThemeMode when used inside a
MaterialApp (thanks to Flutter's automatic MaterialBasedCupertinoThemeData
injection).
With this final swap, liquid_glass_widgets now has zero imports of
package:flutter/material.dart in its lib/ directory. It is fully
decoupled and ready for the cupertino_ui package split. The example's
Apple replica demos (apple_music, apple_podcasts, apple_messages,
apple_news, apple_lockscreen) run with zero Material imports as well,
proving the library works purely within a Cupertino-only context.
LightweightLiquidGlass incorrect brightness in GlassTheme override
contexts — was calling Theme.of(context).brightness instead of the
canonical GlassTheme.brightnessOf(context). Could produce wrong brightness
when GlassTheme set a different mode than the ambient Material theme. Fixed.chore: bump version to 0.25.1 and clean up changelog
chore: bump version to 0.25.1 and clean up changelog
GlassMenu mispositioned inside nested Navigators — OverlayPortal
now targets the root Overlay (via OverlayChildLocation.rootOverlay),
matching the screen-global coordinates captured by localToGlobal. Previously
the menu drifted by the offset of the nearest nested Overlay from the screen
edge (e.g. a side-rail in a StatefulShellRoute layout). Thanks to
@sinanhaci for the contribution (#179).chore: release 0.25.0 — GlassModalSheet detents, dismissible flag, po…
chore: release 0.25.0 — GlassModalSheet detents, dismissible flag, po…
Optional sheet detents — GlassModalSheet (and .show()) gained a
detents set (Set<GlassSheetDetent>, mirroring UIKit's sheet detents) plus
a dismissible flag, to compose which stops a sheet offers:
{GlassSheetDetent.medium} → a half-only glass sheet that never morphs
to the opaque full state (its content still scrolls at the half detent).{GlassSheetDetent.large} → a full-only opaque sheet that opens
straight to full.{GlassSheetDetent.medium, GlassSheetDetent.large} → the default two-stop
sheet.{GlassSheetDetent.small, ...} → adds the maps-style peek floor
underneath, so one mechanism now describes every resting stop.dismissible: false → the sheet rubber-bands at its lowest detent instead
of swiping away (the Apple Pay / Sign in with Apple pattern).The set must be non-empty (asserted). Style the small detent with the
existing peek* params (peekSettings, peekWidth, …), which stay
top-level: a Set whose members carried per-instance payload would need
equality that ignores that payload, or detents.contains(small) breaks and
two differently-configured smalls could sit in one set.
GlassModalSheet.enablePeek → use GlassSheetDetent.small in detents.
Peek is now a detent like medium and large. enablePeek is still honoured
and takes precedence over the set when set explicitly, so existing code keeps
working unchanged; it will be removed in a future release.
GlassSheetMode.persistent keeps its peek floor with or without the detent —
a persistent sheet is defined by resting rather than dismissing.GlassModalSheetController reattachment — the controller no longer
detaches when its GlassModalSheet is swapped under a stable controller
(e.g. a ValueKey change): the replacement's initState runs before the
outgoing widget's dispose, so the detach is now guarded to only clear the
attachment it still owns. Previously this left the controller inert and the
sheet un-openable.full state, so a
half-only sheet scrolls its content correctly.Thanks to @jfhair for the contribution (#178).
`GlassBottomBar` collapse trajectory fixed — when GlassBottomBarCollapseConfig with direction: towardsExtraButton was set, the collapsed tab pill stop
GlassBottomBar collapse trajectory fixed — when GlassBottomBarCollapseConfig with direction: towardsExtraButton was set, the collapsed tab pill stopped short of the extra button, leaving a visible gap between the two circles. The pill now travels to the exact horizontal centre of the extra button on both left and right placements, and the extra button correctly overlays the pill at the end of the animation. Thanks to @jingluoguo for the contribution (#176).GlassBottomBarCollapseConfig default animationDuration bumped from 220 ms → 280 ms — the collapse animation carries semantic meaning (the tab pill merges into the action button) and the extra 60 ms is enough for the eye to track the trajectory without feeling sluggish. Override it any time via the config:
collapseConfig: GlassBottomBarCollapseConfig(
animationDuration: Duration(milliseconds: 220), // snappier
),
Transparent scaffold during navigation fixed — GlassScaffold was unconditionally setting the inner Scaffold.backgroundColor to Colors.transparent, eve
GlassScaffold was unconditionally setting the inner Scaffold.backgroundColor to Colors.transparent, even when no background widget was provided. This caused the previous route to bleed through the incoming screen during MaterialPageRoute slide transitions, making the new page appear transparent. Fixed by only forcing the scaffold transparent when GlassScaffold actually has a background widget or backgroundColor to render — matching the existing GlassPage behaviour. Screens without an explicit background now inherit the theme's opaque scaffoldBackgroundColor, producing correct, opaque transitions (issue #177).Android cold-launch crash fixed — On Android, Flutter's warm-up frame can produce zero-width or unbounded (Infinity) layout constraints before the win
Infinity) layout constraints before the window fully resolves. RenderLiquidGlassGeometry._buildGeometryPicture would attempt to scale and ceil() those non-finite bounds, throwing UnsupportedError: Infinity or NaN toInt. Fixed by guarding against empty or non-finite bounds and returning a safe empty Picture for the one affected frame. A matching guard was added in LiquidGlassRenderObject.paint to prevent the same non-finite values propagating into toImageSync.LightweightLiquidGlass or LiquidGlassRenderObject changed settings that affect alwaysNeedsCompositing, the compositing bit was not synchronised, causing incorrect layer decisions on the first paint under Vulkan. Fixed by calling markNeedsCompositingBitsUpdate() whenever the compositing contract changes.`GlassButton.custom` border invisible in release builds with `--obfuscate` — GlassEffect and LightweightLiquidGlass were using dynamic property access
GlassButton.custom border invisible in release builds with --obfuscate — GlassEffect and LightweightLiquidGlass were using dynamic property access and runtimeType.toString() heuristics to extract the shape's corner radius for the shader. Both techniques are silently broken by the Dart AOT obfuscator: property names are mangled and class names become single-character tokens. The fallback silently resolved to 0.0, producing a perfectly square (borderless) shape in release builds. Fixed by adding a typed effectiveRadius abstract getter to LiquidShape implemented by every concrete shape class — a single virtual dispatch that the compiler can optimise and the obfuscator cannot break.`GlassAppBar.preferredSize` constructor parameter removed. Replace with toolbarHeight: double (default 44.0).
GlassAppBar.preferredSize constructor parameter removed. Replace with toolbarHeight: double (default 44.0).
// Before
GlassAppBar(preferredSize: Size.fromHeight(52))
// After
GlassAppBar(toolbarHeight: 52)
GlassScaffold dark-mode gradient flash fixed. When backgroundColor is set and the device is in dark mode, the edge-fade gradient around GlassTabBar / GlassBottomBar could briefly flash dark. The scaffold now always renders with a transparent Material background (preventing theme bleed), and passes the explicit backgroundColor to the edge-effect fallback gradient.GlassBottomBar default radius inconsistency fixed. Was 32.0; now GlassDefaults.capsuleRadius — consistent with GlassTabBar.bottom.GlassSegmentedControl, GlassTabBar, GlassBottomBar) now share a unified radius calculation that is guaranteed correct during jelly-bloom expansion.3-tier indicator radius system — all interactive bar/control widgets now follow iOS 26 defaults out of the box:
barBorderRadius to a custom value and the indicator automatically tracks at barBorderRadius − padding (concentric nested arcs).indicatorBorderRadius explicitly to override everything.GlassDefaults.capsuleRadius — named constant (9999.0) for the capsule sentinel:
GlassBottomBar(barBorderRadius: GlassDefaults.capsuleRadius)
GlassAppBar.bottom — Accepts any PreferredSizeWidget (typically a TabBar) rendered below the navigation bar title. The scaffold automatically reserves the combined height — no manual sizing needed.
GlassAppBar(
title: const Text('Browse'),
bottom: TabBar(tabs: [...]),
)
Removed `useSuperellipse` *(see Breaking Changes above)*: This parameter was mathematically incorrect for dynamic stretching indicators.
AnimatedGlassIndicator.useSuperellipse removed. This parameter has been deleted from the constructor. Any call site passing useSuperellipse: true or useSuperellipse: false will fail to compile.
Migration: simply remove the parameter. The indicator is always a rounded rectangle (capsule) now, which is mathematically correct. Squircle geometry is unstable for dynamic stretching elements.
// Before (0.22.1)
AnimatedGlassIndicator(
useSuperellipse: false,
...
)
// After (0.23.0) — just remove the parameter
AnimatedGlassIndicator(
...
)
LiquidGlassSettings.fresnelStrength — New parameter (range 0.0–1.0, default 1.0) that scales the natural Fresnel edge luminosity on the Premium rendering path. At 1.0 the glass behaves as physically lit glass with a rim highlight at grazing angles (existing default). At 0.0 the rim is completely suppressed, producing a pure blur-overlay appearance that matches iOS 26 system UI glass (Messages buttons, notification banners, lock screen controls). Intermediate values interpolate smoothly. Fully backwards compatible — omitting the parameter preserves all existing rendering. Also exposed on GlassThemeSettings so it can be set app-wide via GlassTheme.
GlassMenuItem.enablePressScale — New bool parameter (default true) that controls the 0.98× scale-down animation on press. Set to false on fill-rate-limited devices to eliminate the per-frame GPU cost of animating a Transform.scale over the glass layer. Fully backwards compatible.
GlassExtraButtonPlacement — Added GlassExtraButtonPlacement.left and right for non-searchable bottom bars so GlassTabBarExtraButton can be placed on either side. Defaults to right to preserve existing behavior. Thanks to @jingluoguo (#169).
useOwnLayer: true buttons during press-scale animation on Impeller.This release fundamentally solves the long-standing geometry tension between Flutter's path rendering and our GPU shaders. We completely rewrote the squircle math to use pure analytical curves, fixed stretching bugs in tab indicators, and simplified the API.
sdf.glsl): We completely removed the old piecewise 45-degree seam approach and the hacky blend safety valve. sdfSquircle now uses a pure, analytic Lamé curve (|x|^n + |y|^n = 1). Squircles now perfectly match Apple's continuous curve geometry with zero flattening on the edges.r = 18 on a 36px tall button), the shader now dynamically recalculates the exponent n based on the clamped available space. As space runs out, n smoothly degrades to 2.0, collapsing into a perfect circle. This mathematically guarantees that the shader's interaction glow always aligns perfectly flush against Flutter's clipping path, eliminating the dark corner gaps.Apple never uses squircles for pill shapes (like "Edit" buttons or Tab Indicators). They use pure circular-arc capsules. We audited the library to align with this:
GlassChip & Demo Buttons: Replaced LiquidRoundedSuperellipse with LiquidRoundedRectangle for all pill-shaped elements. They now explicitly use sdfRRectAsym, ensuring perfect circular ends.GlassMenu & GlassPopover morph blobs: Replaced LiquidOval with LiquidRoundedRectangle. The rounded rect SDF is mathematically stable at all aspect ratios during dynamic morphs.The glass tab indicator previously suffered from a "stretching bug" where it turned squarish during drag expansion because its finite borderRadius was outgrown by its expanding height.
useSuperellipse (see Breaking Changes above): This parameter was mathematically incorrect for dynamic stretching indicators.borderRadius (Default 9999.0): borderRadius is no longer required. It defaults to 9999.0, which offloads the math entirely to the shader's min(r, shortest) clamp. This guarantees a perfect capsule at any drag size.GlassSegmentedControl explicitly passes borderRadius: containerRadius - 3, ensuring it retains its correct inset rounded-rectangle geometry rather than defaulting to a capsule.shape_debug_demo.dart — corrected the Standard-mode description banner from the inaccurate "_SquircleClipper + lightweight shader (CPU L4/L2 path)" to the accurate "ShapeBorderClipper + lightweight blur shader (shape-blind)". There is no _SquircleClipper class; the lightweight shader is shape-type-blind by design.Internal Tab Models — Deprecated GlassBottomBarTab is no longer used by the internal layout engines. They now natively accept GlassTab, removing the n…
GlassTabBar and GlassBottomBar:
GlassTab.semanticLabel is now properly propagated, allowing icon-only tabs to be correctly announced instead of defaulting to 'Tab'.Text widget in ExcludeSemantics.GlassBottomBarTab is no longer used by the internal layout engines. They now natively accept GlassTab, removing the need for mapping closures and SizedBox.shrink() sentinels.ci: bump checkout and codecov actions to v7 to resolve Node 20 deprec…
ci: bump checkout and codecov actions to v7 to resolve Node 20 deprec…
ProgressiveBlur — a graduated backdrop blur that is strongest at one
edge and dissolves to sharp at the opposite edge (the iOS 26 / Signal header
look). Self-contained — no LiquidGlassLayer ancestor required.
Positioned(
top: 0, left: 0, right: 0, height: 96,
child: ProgressiveBlur(maxSigma: 20),
)
maxSigma — blur sigma at the strong edge (0 ⇒ passthrough).direction — which edge is strongest
(topToBottom / bottomToTop / leftToRight / rightToLeft).falloff — gradient gamma (default 1.2).Pre-warmed by LiquidGlassWidgets.initialize() at no extra startup cost;
ProgressiveBlur.preload() is available for standalone use. Degrades to a
uniform BackdropFilter on Skia / web. See
docs/PROGRESSIVE_BLUR.md.
Thanks to @Ahmadre (#162).
GlassPopover blur ramp — the backdrop blur now eases in over the opening
morph instead of rendering at full strength from frame one. Raster avg halved
(10.1 ms → 5.8 ms), worst-case halved, missed-budget frames 15 → 6 on the
reference device. See docs/POPOVER_BLUR_RAMP.md.
Two new backwards-compatible params:
blurRampDuration (default Duration(milliseconds: 260)) — set to
Duration.zero to restore the previous always-full-blur behaviour.blurRampCurve (default Curves.easeOut).Automatically disabled when "reduce motion" is active. Thanks to @Ahmadre (#161).
GlassPopover drifted off its trigger in nested overlays — the morph
portal now targets OverlayChildLocation.rootOverlay to match the
root-relative coordinates it is placed at. Top-level usage is unaffected.
Thanks to @Ahmadre (#163).
Intrinsic-height GlassPopover overflowed on live content growth — the
popover now re-measures via SizeChangedLayoutNotifier when content grows
while open, instead of clamping to the height frozen at open time.
Fixed-popoverHeight popovers are unchanged. Thanks to @Ahmadre (#163).
`GlassTabBar.bottom` / `GlassBottomBar` distorted tap hit regions with >2 tabs (#157) — Tapping a tab incorrectly routed through DraggableIndicatorPhy
GlassTabBar.bottom / GlassBottomBar distorted tap hit regions with >2 tabs (#157) — Tapping a tab incorrectly routed through DraggableIndicatorPhysics.getAlignmentFromGlobalPosition, which applies an indicator-center remap (±½ tab-width padding) designed for continuous drag tracking. For discrete taps this shifted the hit boundaries from the correct 25/50/75 % to ~31.25/50/68.75 % on a 4-tab bar, making the right ~25 % of tab 2 select tab 3 instead. A new tabIndexFromGlobalPosition helper bypasses the drag remap and computes the tab index directly from the raw position fraction. The same incorrect remap was present in the recoverIfGestureStuck path (PlatformView gesture recovery) and is fixed there too. The drag path (onHorizontalDragUpdate / onHorizontalDragStart) is unaffected. Thanks to @bayraktarmdkaraca for the precise root-cause analysis!`GlassTabBar` loses shadow when OS is Dark but app is `ThemeMode.light` — fixed an issue where glass components incorrectly resolved to Brightness.dar
GlassTabBar loses shadow when OS is Dark but app is ThemeMode.light — fixed an issue where glass components incorrectly resolved to Brightness.dark when the device OS was in Dark Mode but the MaterialApp was explicitly set to ThemeMode.light. The brightness cascade in resolveGlassBrightness was checking the CupertinoTheme before the Material ThemeMode; inside a MaterialApp the Cupertino theme is implicitly derived from the OS, causing it to return the wrong brightness. The cascade now correctly prioritises ThemeMode in MaterialApp contexts. Thanks to @minhtritc97 for the detailed report!Vertical `GlassSegmentedControl` — fixed controls now accept direction: Axis.vertical and an optional segmentExtent. Layout, fractional indicator posi
GlassSegmentedControl — fixed controls now accept direction: Axis.vertical and an optional segmentExtent. Layout, fractional indicator positioning, jelly expansion, drag velocity, snapping, and gesture recognition all follow the vertical axis. The horizontal default and scrollable constructor remain unchanged. Thanks to @F1orian!shadowElevation on LiquidGlass and GlassButton rendered as solid black circles on Windows, Web, and certain Android emulators due to two known Flutter engine bugs (saveLayer texture corruption on Vulkan and Path.combine clipping failures). The package now selectively bypasses the GPU shadow cutout on affected platforms and uses a safe evenOdd winding rule for the fallback, producing perfect shadows without engine issues.SVG and custom icons restored — SizedBox-wrapped icons (e.g. SvgPicture) were silently stripped from the render tree since 0.20.0. The SizedBox.shrink
SizedBox-wrapped icons (e.g. SvgPicture) were silently stripped from the render tree since 0.20.0. The SizedBox.shrink() sentinel detection now checks width, height, and child fields so a caller-supplied SizedBox wrapping a real icon is always rendered correctly.GlassTabBar.searchable collapsed back to its resting state when the finger passed over the currently selected tab mid-drag. The thickness spring now includes the tabIsDragging guard, matching the behaviour already present in GlassTabBar.bottom.JellyClipper Impeller radius guard — the clip radius is now clamped to strictly less than half the indicator's shortest side, preventing a malformed RRect path that caused content to vanish under Impeller's Metal renderer in certain animation frames.1.15 (GlassDefaults.refractiveIndex) to match all Apple demos; both GlassTabBar.inline variants now wire live tuner sliders for expansion and pinch strength.Extra button stretch disabled over platform views — GlassTabBarExtraButton now correctly disables its stretch effect when platformViewBackdrop: true,
GlassTabBarExtraButton now correctly disables its stretch effect when platformViewBackdrop: true, matching the behavior of GlassTabBar.bottom and fixing the jittery spring animation.GlassMenu item scroll wiggle fixed — menu items with wrapped text (e.g. maxLines: 2) could drift sub-pixel vertically during slide-to-select dragging because ClampingScrollPhysics allowed fractional scroll offsets even when no overflow was intended. Non-scrollable menus now use NeverScrollableScrollPhysics, locking the content completely in place during drag.GlassPopover first-frame height fixed — the popover was briefly rendered at full-screen height before its content height was known, producing a visible flash and forcing users to wrap content in a SingleChildScrollView as a workaround. An invisible Offstage measurement pass now runs on Frame 1, letting Flutter's layout engine calculate the exact intrinsic content height before the morph animation starts. The animation launches on Frame 2 with perfect geometry — no mid-flight height correction, no flash, no heuristics.GlassBottomBar — the RepaintBoundary wrapping the icon layer in _buildHighQualityMode is now only mounted when platformViewBackdrop: true, where it is required for the Platform View capture path (bug #99). In the common case (platformViewBackdrop: false) the boundary was creating a GPU offscreen texture every frame with no caching benefit, since the JellyClipper changes on every animation frame.GlassPopover idle trigger optimised — when the popover is fully closed, the trigger widget now skips unnecessary Transform, Opacity, and IgnorePointer compositing layers. This removes redundant GPU work in the common idle state.child! force-unwrap in GlassPopoverInternal with a null-safe child ?? const SizedBox.shrink() fallback, preventing a hard crash if child is ever omitted in a future refactor.GlassTabBar.bottom's internal default) for tuning the refractiveIndex on the Premium (Impeller) glass indicator. Standard indicators do not perform background capture so the parameter has no visual effect there.Two-pills misalignment fixed — at GlassQuality.standard, the background rim and glass lens now share the same shape geometry (ShapeDecoration) and rid
GlassQuality.standard, the background rim and glass lens now share the same shape geometry (ShapeDecoration) and ride the same jelly Transform, eliminating the visible separation mid-morph. Regression reported against GlassTabBar.inline / segmented controls.tabIsDragging so the indicator stays fully active for the entire gesture duration.GlassQuality.standard is now proportionally mapped from indicatorSettings.thickness (same value as Premium) rather than scaling from the raw glass-depth value, which produced a ~2.8 px border. The result is a fine hairline that gracefully matches the Premium look on Standard-quality devices.`GlassModalSheet` drag progress — controller now exposes a progress getter and progressListenable reporting the live 0–1 drag position between half↔fu
GlassModalSheet drag progress — controller now exposes a progress getter and progressListenable reporting the live 0–1 drag position between half↔full snap points, so hosts can drive coordinated UI in real time. (#148, @jfhair)GlassTabBar.inline spring control — the .inline factory now accepts springDescription, matching the other factory constructors. Previously inline tab bars were locked to the shared default spring. (#149, @jfhair)LiquidGlassSettings.ambientRim — tunable full-perimeter rim on the moving indicator pill. Defaults match Apple Music's segmented control (brighter in light mode, off in dark). (#150, @jfhair)AnimatedGlassIndicator shadow — shadowElevation/shadow in indicatorSettings now correctly paints a drop shadow on the glass jelly. Previously these values were silently ignored. (#151, @jfhair)PlatformView (e.g. a map or WebView).
The iOS gesture arena can silently drop terminal callbacks, leaving the recognizer
wedged. Fixed with proactive cleanup on PointerDown, post-frame recovery, and
a gesture ID guard to prevent rapid-tap state corruption.platformViewBackdrop: true: When the tab bar
floats over a PlatformView, the visual indicator now animates to its new
position instantly on touch-down (matching native iOS responsiveness), while the
actual tab content swap is deferred safely to touch-up. This prevents the iOS
UIKit view system from dropping the touch stream mid-gesture due to a mid-frame
unmount of the PlatformView. No impact on any screen where
platformViewBackdrop is false — those continue to swap instantly on down.platformViewBackdrop: true: Flutter's BackdropFilter
must re-acquire the native pixel buffer every time its bounding box changes.
Stretch animations resize the glass container, causing a one-frame flicker over
a PlatformView. Stretch is now skipped on all bar elements when
platformViewBackdrop is set; press-scale (interactionScale) is unaffected
because it is a GPU-level transform that leaves the backdrop bounds stable.
No impact on any other screen or platform.Migration: No breaking changes. If you were relying on the undocumented 56px default height, explicitly set height: 56.
GlassButton.custom layout expansionFixes: GlassButton.custom unexpectedly expanding to fill bounded parent constraints (e.g., inside AppBar actions) (#146).
GlassButton.custom height now correctly defaults to null (matching documented behavior).56px default height, explicitly set height: 56.GlassListTile no longer draws its own divider. The isLast, showDivider, and dividerIndent parameters have been removed.
GlassListTile divider refactorGlassListTile no longer draws its own divider. The isLast, showDivider,
and dividerIndent parameters have been removed.
Rationale: A list tile should be a clean, position-agnostic item. Divider rendering is a layout concern that belongs to the parent container.
Inside GlassGroupedSection — no changes needed.
GlassGroupedSection now automatically injects GlassDividers between tiles
with smart leading-indent detection (56px if the preceding tile has a leading
widget, 16px otherwise). The last tile never gets a trailing divider.
Standalone column layouts — compose GlassDivider explicitly:
// Before:
Column(children: [
GlassListTile(title: Text('A')), // showDivider: true (default)
GlassListTile(title: Text('B'), isLast: true), // suppresses divider
])
// After (standard Flutter composition pattern):
Column(children: [
GlassListTile(title: Text('A')),
GlassDivider(indent: 16),
GlassListTile(title: Text('B')),
])
This aligns with Flutter's own ListTile + Divider composition model.
GlassTabBar.inlineA compact glass tab bar for pinned content-filter sections — sits fixed between a page header and its scrollable list, not inside the scroll view itself.
Performance note:
GlassQuality.premiumre-runs the full shader pipeline whenever the backdrop changes. Placing the bar inside a scroll view invalidates the backdrop on every scroll frame. Pin it outside the scrollable region or drop toGlassQuality.standardif embedding inside a list.
GlassTabBar.inline(
tabs: const [
GlassTab(label: 'For You'),
GlassTab(label: 'Following'),
GlassTab(label: 'New'),
],
selectedIndex: _selectedIndex,
onTabSelected: (i) => setState(() => _selectedIndex = i),
)
Key differences from GlassTabBar.bottom:
barBorderRadius: 100, clamped to height/2).indicatorBorderRadius defaults to barBorderRadius).h:12, v:10 — same horizontal weight as GlassSegmentedControl and GlassTabBar.bottom, +2px vertical to compensate for the shorter bar height giving the glass pill proportional visual mass.indicatorPinchStrength: 0.4 — aligned with all other pill controls.1.0x) — text labels never grow on selection.extraButton not supported (structural navigation feature only).All standard physics parameters (indicatorPinchStrength, indicatorExpansion,
indicatorSettings, quality) are still fully configurable.
See the updated Indicator Parity demo (indicator_parity_demo.dart) where
GlassTabBar.inline now appears alongside the other five pill widgets — including
a text-only variant (40px) and an icon + text variant (52px) — with live tuning sliders.
`GlassTabBar.bottom` / `GlassBottomBar` — fixes RTL support (#143, @naeemeltaief). The pill and tap/drag hit-testing now correctly align with the visu
GlassTabBar.bottom / GlassBottomBar — fixes RTL support (#143, @naeemeltaief).
The pill and tap/drag hit-testing now correctly align with the visually reversed tab order
under RTL. The bottom layout normalises the tab data, selected index and callback for RTL,
and pins only the two inner tab Rows to LTR — leaving indicatorExpansion, tabPadding
and all text to resolve against the ambient direction as expected. Consumers no longer
need to force Directionality.ltr and reverse tabs by hand. +2 tests.
AnimatedGlassIndicator — restores the resting selection pill inside a GlassContainer
(#144, @jfhair). The background pill was permanently hidden whenever an ancestor set
avoidsRefraction, which is a steady-state layout flag on GlassContainer — not a
transient capture signal. The guard was incorrect and is removed. The pill now renders
correctly in all contexts. No API change. +1 test.
Your coding agent can read these notes before it upgrades. Set up the MCP server →