liquid_glass_ui_kit 0.1.2
liquid_glass_ui_kit: ^0.1.2 copied to clipboard
An accessible, customizable Liquid Glass design system for Flutter with translucent themes and ready-made widgets for mobile, web, and desktop.
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
