Liquid Glass UI
An iOS 26.4 "Liquid Glass" theme and component kit for Flutter. Drop in a frosted, translucent design system โ colors, typography and ready-made widgets โ and get the native-feeling glassmorphism look on any platform.
Everything is themeable through a single GlassTheme, and every component reads
its blur, tint, rim-highlight and radii from that one place, so your whole app
stays consistent.

Live documentation ยท Source code ยท Report an issue
- ๐ง Real
BackdropFilterglass with a rim highlight and soft shadow - ๐ Light & dark, monochrome by default (glass + white, no blue)
- ๐๏ธ Smooth nav-bar animations (sliding bubble + springy pop), each toggleable
- โจ๏ธ HIG-aligned text fields, settings toggles, and macOS autocomplete
- โฟ Honors Reduce Transparency / Increase Contrast and Reduce Motion
- ๐ฆ Tiny, dependency-free (only the Flutter SDK)
Install
dependencies:
liquid_glass_ui_kit: ^0.1.2
import 'package:liquid_glass_ui_kit/liquid_glass_ui_kit.dart';
Quick start
Wrap your app (or a subtree) in a GlassTheme, then use the components:
import 'package:flutter/material.dart';
import 'package:liquid_glass_ui_kit/liquid_glass_ui_kit.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _tab = 0;
@override
Widget build(BuildContext context) {
return GlassTheme(
data: GlassThemeData.dark(),
child: GlassScaffold(
appBar: const GlassAppBar(title: Text('Home')),
bottomNavigationBar: GlassBottomNavBar(
currentIndex: _tab,
onTap: (i) => setState(() => _tab = i),
items: const [
GlassNavItem(icon: Icons.home_rounded, label: 'Home'),
GlassNavItem(icon: Icons.sports_esports_rounded, label: 'Arcade'),
GlassNavItem(icon: Icons.group_rounded, label: 'Friends'),
GlassNavItem(icon: Icons.inventory_2_rounded, label: 'Library'),
GlassNavItem(icon: Icons.search_rounded, label: 'Search'),
],
),
body: ListView(
padding: const EdgeInsets.fromLTRB(16, 120, 16, 150),
children: [
GlassCard(child: Text('Frosted content')),
const SizedBox(height: 16),
GlassButton(
label: 'Continue',
variant: GlassButtonVariant.filled,
expand: true,
onPressed: () {},
),
],
),
),
);
}
}
Components at a glance
| Component | What it is | Key params |
|---|---|---|
GlassSurface |
The core blurred/tinted/rim-lit panel everything is built on | blurSigma, tint, highlight, borderRadius, shadow |
GlassScaffold |
Page shell that floats bars over a scrollable body | appBar, bottomNavigationBar, background, body |
GlassAppBar |
Translucent top bar (PreferredSizeWidget) |
title, leading, actions, floating |
GlassBottomNavBar |
Floating capsule tab bar with animated indicator | items, currentIndex, onTap, animateIndicator, animateBounce, showLabels, borderRadius |
GlassButton |
Capsule button โ glass / filled / plain | label, icon, variant, expand, onPressed |
GlassCard |
Padded glass panel, optionally tappable | child, padding, onTap |
GlassPill |
Small tag / chip / filter | label, icon, selected, onTap |
GlassSwitch |
Native-behaving Apple switch for a binary setting | value, onChanged, semanticLabel, activeTrackColor |
GlassToggleRow |
HIG-recommended labeled list-row presentation for GlassSwitch |
label, description, value, onChanged |
GlassTextField |
Apple-style short text entry with labels, Clear button, secure input, and validation | label, placeholder, controller, keyboardType, validator |
GlassComboBox<T> |
Editable macOS combo box with autocomplete and pull-down choices | options, displayStringForOption, initialSelection, onChanged, onSelected |
GlassSlider |
Slider with a translucent oversized glass thumb | value, onChanged, min, max, divisions, activeColor |
GlassSegmentedControl<T> |
Segmented tabs with a sliding, springy glass selection capsule | children, selectedValue, onValueChanged, animateBounce |
GlassSheet / showGlassBottomSheet |
Bottom sheet | title, builder, heightFactor, showDragHandle |
GlassDialog / showGlassDialog |
Alert popup | title, message, actions |
GlassActionMenu / showGlassActionMenu |
Anchored context menu | items |
Toggles and switches
Apple's toggle guidance recommends using a switch for one pair of opposing states, clearly identifying what it affects, making its states visually distinct without relying only on color, and placing the switch in a list row on iOS and iPadOS.
Use GlassToggleRow for that standard presentation:
GlassToggleRow(
label: 'Notifications',
description: 'Allow alerts about new activity.',
value: _notificationsEnabled,
onChanged: (value) => setState(() => _notificationsEnabled = value),
),
Use the lower-level switch only when its parent already supplies the labeled row context:
GlassSwitch(
value: _notificationsEnabled,
semanticLabel: 'Notifications',
onChanged: (value) => setState(() => _notificationsEnabled = value),
),
Both are controlled components. GlassSwitch uses native Cupertino mechanics,
including tap and drag interaction, RTL direction, keyboard focus, platform
haptics, VoiceOver state, and the system's On/Off Labels accessibility setting.
For more than two choices, use a segmented control or another selection
component instead.
The bottom nav bar & its animations
GlassBottomNavBar is the signature piece. Two animations, each independently
toggleable, and both auto-disabled under Reduce Motion:
| Flag | Default | Effect |
|---|---|---|
animateIndicator |
true |
The highlight slides ("bubbles") between items. Off โ it just appears on the selected item. |
animateBounce |
true |
The selected icon and pill pop with a spring. Off โ no pop. |
GlassBottomNavBar(
currentIndex: _tab,
onTap: (i) => setState(() => _tab = i),
items: _items,
// Animations โ turn either off on its own:
animateIndicator: true, // sliding bubble
animateBounce: true, // springy pop (icon + pill)
// Icons-only mode:
showLabels: false,
// Rounding โ big values clamp to a full capsule:
borderRadius: 40, // the bar
indicatorBorderRadius: 32, // the pill (defaults to concentric with the bar)
)
You supply currentIndex and rebuild on onTap โ it's a controlled widget, so
the selection is your state.
Text fields
GlassTextField follows Apple's
text-field guidance:
use it for small, specific values; keep a persistent label when the placeholder
alone would be ambiguous; select the keyboard that matches the data; obscure
sensitive input; and put useful validation feedback directly beside the field.
A localized, Apple-style Clear button appears at the trailing edge when text is
present.
import 'package:flutter/cupertino.dart' show CupertinoIcons;
GlassTextField(
label: 'Email',
placeholder: 'name@example.com',
prefix: const Icon(CupertinoIcons.mail),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
autofillHints: const [AutofillHints.email],
validator: (value) => value != null && value.contains('@')
? null
: 'Enter an email address, like name@example.com.',
),
GlassTextField(
label: 'Password',
placeholder: 'Required',
obscureText: true,
autocorrect: false,
enableSuggestions: false,
autofillHints: const [AutofillHints.password],
),
The field works with Flutter's Form, validator, onSaved, and
AutovalidateMode APIs. Use inputFormatters for constrained values, and
prefer a multiline text-view component when you need long-form input.
When you supply a controller or focusNode, you own and dispose it. Without
them, GlassTextField manages its own instances. A custom trailing suffix
replaces the Clear button, matching the underlying Cupertino behavior.
Autocomplete and dropdown combo boxes
GlassComboBox<T> follows Apple's
combo-box guidance:
it combines an editable text field with a pull-down list, accepts custom input
without adding it to the choices, supports a meaningful default, and keeps the
choices panel exactly as wide as the field. Typing filters relevant choices;
the trailing chevron opens the complete list.
GlassComboBox<String>(
label: 'Favorite City:',
placeholder: 'Enter a city',
helperText: 'Choose a suggestion or type another city.',
options: const ['Jakarta', 'London', 'San Francisco', 'Tokyo'],
initialSelection: 'Jakarta',
displayStringForOption: (city) => city,
onChanged: (text) => setState(() => _city = text),
onSelected: (city) => setState(() => _city = city),
),
Apple supports combo boxes on macOS, not iOS, iPadOS, tvOS, visionOS, or watchOS. On those platforms, prefer a platform-appropriate menu or selection flow. Keep the predefined choices relevant and short enough to read at the field's width.
Combo-box behavior
| Interaction | Result |
|---|---|
| Type in the field | Filters choices with a case-insensitive substring match; prefix matches appear first. |
| Press the chevron | Opens every predefined choice. |
| Up / Down | Moves the highlighted choice while the panel is open. |
| Enter | Selects the highlighted or exact matching choice; otherwise submits custom text. |
| Escape or outside click | Closes the choices panel. |
Use optionFilter for domain-specific matching and optionBuilder for custom
rows. onChanged receives typed and selected text, onSelected receives only a
predefined T, and onSubmitted receives custom text. If you provide a
controller or focusNode, their lifecycle remains yours.
Slider
GlassSlider is a controlled range input. Rebuild it with the value received by
onChanged; pass null to disable interaction. Use divisions when the value
must snap to discrete steps, and always provide a meaningful semanticLabel.
GlassSlider(
value: _volume,
min: 0,
max: 100,
divisions: 20,
activeColor: const Color(0xFF0A84FF),
semanticLabel: 'Volume',
onChanged: (value) => setState(() => _volume = value),
)
The white resting thumb expands to its full liquid-glass size while pressed or dragged. The control also supports arrow keys, Home/End, and screen-reader increment/decrement actions.
Tabs and segmented selection
Use GlassSegmentedControl<T> for a small set of mutually exclusive views,
filters, or modes inside the current screen. Use GlassBottomNavBar instead for
top-level app destinations; its full API is documented in
The bottom nav bar & its animations.
Like the slider, segmented tabs are controlled: update selectedValue from
onValueChanged, or pass null to disable interaction.
GlassSegmentedControl<String>(
children: const {
'for-you': Text('For You'),
'library': Text('Library'),
},
selectedValue: _section,
onValueChanged: (value) => setState(() => _section = value),
animateBounce: true,
)
Segmented tabs support taps, dragging across segments, RTL-aware Left/Right
keys, and Home/End. The selection capsule and active label use the same springy
bubble-pop motion as GlassBottomNavBar; set animateBounce: false to disable
it independently. System Reduce Motion disables the animation automatically.
Overlays: sheet, dialog & menu
All three need a Navigator / MaterialApp ancestor (as any modal route does).
// Bottom sheet
showGlassBottomSheet(
context: context,
title: 'Share',
builder: (context) => Column(mainAxisSize: MainAxisSize.min, children: [...]),
);
// Popup dialog
showGlassDialog(
context: context,
title: 'Delete item?',
message: 'This action cannot be undone.',
actions: [
GlassDialogAction(label: 'Cancel', isDefault: true, onPressed: () => Navigator.pop(context)),
GlassDialogAction(label: 'Delete', isDestructive: true, onPressed: () => Navigator.pop(context)),
],
);
// Action menu โ anchored to the widget owning `context`
showGlassActionMenu(
context: context,
items: [
GlassMenuItem(label: 'Edit', icon: Icons.edit_outlined),
GlassMenuItem(label: 'Delete', icon: Icons.delete_outline, isDestructive: true),
],
);
For the action menu, pass the trigger widget's own context (wrap it in a
Builder) so the menu anchors to it and flips above when there's no room below.
Buttons
GlassButton(label: 'Glass', variant: GlassButtonVariant.glass, onPressed: () {}),
GlassButton(label: 'Filled', variant: GlassButtonVariant.filled, onPressed: () {}),
GlassButton(label: 'Plain', variant: GlassButtonVariant.plain, onPressed: () {}),
// With an icon, full width, disabled:
GlassButton(
label: 'Continue',
icon: Icons.arrow_forward_rounded,
variant: GlassButtonVariant.filled,
expand: true,
onPressed: null, // null disables it
);
glassโ frosted capsule, accent-colored labelfilledโ solid accent capsule; label auto-picks black/white for contrastplainโ text-only, no fill
The building block: GlassSurface
Compose your own frosted widgets with the same material:
GlassSurface(
borderRadius: BorderRadius.circular(24),
padding: const EdgeInsets.all(16),
blurSigma: 30, // override the theme blur
tint: Colors.white24, // override the tint
child: const Text('Custom glass'),
)
Theming
Grab the active theme anywhere with GlassTheme.of(context):
final theme = GlassTheme.of(context);
theme.colors.accent; // monochrome accent
theme.colors.label; // primary text/icon color
theme.typography.title1; // type scale
theme.metrics.blurSigma; // shared blur strength
Customize globally by passing your own GlassThemeData:
GlassTheme(
data: GlassThemeData.light(
metrics: const GlassMetrics(blurSigma: 32, largeRadius: 32),
).copyWith(
// Opt into a color accent if you want one:
colors: GlassColors.light.copyWith(accent: const Color(0xFFAF52DE)),
),
child: ...,
)
Palettes
The default accent is monochrome (graphite in light, near-white in dark), so
out of the box it's pure glass + white with no color tint. GlassColors exposes:
background, secondaryBackground, label, secondaryLabel, tertiaryLabel,
accent, glassTint, glassHighlight, glassBorder, glassShadow, separator.
Follow the system light/dark mode
GlassTheme(
data: GlassThemeData(colors: GlassColors.of(context)),
child: ...,
)
Metrics
GlassMetrics centralizes blurSigma, borderWidth and the radii
(smallRadius, mediumRadius, largeRadius, pillRadius) so you can dial the
whole system up or down at once.
Fonts
The package doesn't bundle SF Pro (Apple's license forbids redistribution). On Apple platforms Flutter renders with the system font automatically, so it looks native. To match it on Android/web, bundle your own font and apply it:
final typography = GlassTypography.forColors(GlassColors.dark)
.apply(fontFamily: 'YourSFAlike');
GlassTheme(
data: GlassThemeData(colors: GlassColors.dark, typography: typography),
child: ...,
)
Accessibility
Translucent UIs can fail WCAG contrast over busy backgrounds, so the kit honors the system settings automatically:
- Reduce Transparency / Increase Contrast โ glass surfaces drop the blur and render as opaque, clearly-bordered panels.
- Reduce Motion โ the press, toggle, nav, dialog and menu animations are disabled.
The default palette is monochrome and high-contrast (black/white text). Still, follow Apple's guidance: keep to roughly one glass layer per view, avoid stacking glass on glass, and verify text contrast over your real backgrounds.
The glass effect & performance
GlassSurface uses a real BackdropFilter blur, so it needs content behind it
to refract โ float the bars over your body (as GlassScaffold does) rather than
stacking them in a plain Column. Backdrop blurs are GPU work; a handful per
screen is fine, but avoid dozens of overlapping ones in long lists.
Example
The animation at the top of this page is captured from the full demo in
example/. The current demo includes every component
in light and dark mode, including the text field, labeled toggles, slider,
segmented control, and autocomplete/dropdown combo box.
Run it locally:
cd example
flutter run
License
MIT ยฉ 2026
Libraries
- liquid_glass_ui_kit
- Liquid Glass UI โ an iOS 26.4 "Liquid Glass" theme and component kit for Flutter.