glassy_ui_ios 0.3.1
glassy_ui_ios: ^0.3.1 copied to clipboard
90 native iOS components for Flutter built with SwiftUI and UIKit. Real Liquid Glass on iOS 26+, classic styling on iOS 17-18, customizable from Dart.
Real SwiftUI & UIKit components in your Flutter app.
Apple's Liquid Glass on iOS 26, automatic fallbacks on iOS 17–18.
Installation · Quick start · Widget examples · All 90 components · FAQ
See it in action #
![]() Glass morphing glassMorphing |
![]() Floating action button floatingActionButton |
![]() Glass transition glassTransition |
![]() App Store download downloadProgress |
Why glassy_ui_ios? #
Flutter draws every pixel itself, so Cupertino-style widgets can only imitate iOS. Liquid Glass is rendered by the system, with real-time refraction and morphing, and can't be faithfully recreated in Flutter. glassy_ui_ios embeds Apple's own components instead.
| glassy_ui_ios | Flutter look-alike widgets | |
|---|---|---|
| Rendering | ✅ Real SwiftUI / UIKit | Recreated in Flutter |
| Liquid Glass (refraction, morphing) | ✅ Native glassEffect |
❌ Blur approximation |
| Matches every iOS update | ✅ Automatically | Needs a package update |
| System sheets, menus, popovers, haptics | ✅ Native behavior | Imitated |
| VoiceOver & Dynamic Type | ✅ Built in | Partial |
| iOS 17–18 support | ✅ Classic materials fallback | ✅ |
| Customizable from Dart | ✅ Colors, images, radius, fonts, glass | ✅ |
Features #
- 90 components: navigation bars, toolbars, glass buttons, tab bars, sidebars, split views, sheets, alerts, menus, pickers, progress, media and camera controls, and Liquid Glass effects. See the gallery below.
- Truly native: SwiftUI and UIKit render each component, so system sheets, menus, popovers and haptics behave exactly like iOS.
- Adaptive design:
glassEffect,.glass/.glassProminent,GlassEffectContainerand glass morphing on iOS 26+, with material fallbacks on iOS 17–18. - Configurable from Dart: title, images (SF Symbols, assets, bytes), items, values, colors, background, corner radius, glass variant, fonts and light/dark. Changes apply live.
- Two-way communication: every interaction comes back to Dart as a
NativeEvent, and Dart drives components withtrigger(),reset()and custom commands. - Extensible: register your own SwiftUI views and use them from Dart.
- Copy-paste examples: every component comes with ready-to-use Dart code in the example app.
- Fully documented: every public API has dartdoc, including each component's config fields, commands and events.
- Swift Package Manager and a privacy manifest are included.
Requirements #
| Minimum | |
|---|---|
| iOS | 17.0 (Liquid Glass needs iOS 26) |
| Flutter | 3.27 |
| Xcode | 26 (to compile the Liquid Glass APIs) |
| Integration | Swift Package Manager |
Installation #
flutter pub add glassy_ui_ios
or add it to pubspec.yaml yourself:
dependencies:
glassy_ui_ios: ^0.3.1
This plugin is distributed with Swift Package Manager, so turn it on for your app in pubspec.yaml:
flutter:
config:
enable-swift-package-manager: true
You can also turn it on globally with flutter config --enable-swift-package-manager.
Then set the iOS deployment target to 17.0: in Xcode, select Runner → General → Minimum Deployments. If your app still has a Podfile, set platform :ios, '17.0' there as well.
Quick start #
import 'package:glassy_ui_ios/glassy_ui_ios.dart';
SizedBox(
height: 400,
child: NativeComponent(
type: NativeComponentType.prominentGlassButton,
config: const NativeComponentConfig(
title: 'Continue',
image: NativeImage.symbol('arrow.right'),
style: NativeStyle(
tintColor: Colors.teal,
cornerRadius: 20,
backgroundGradient: [Colors.indigo, Colors.pink],
),
),
onEvent: (event) => debugPrint('${event.name}: ${event.value}'),
),
);
NativeComponent sizes itself to its parent, like any platform view. Give it bounded constraints: an Expanded, a SizedBox, or a full page.
Glass widgets #
Every everyday control is also a typed Flutter widget: named parameters, typed callbacks, a normal widget size, and a tint and light/dark appearance taken from your app's Theme. They all start with Glass, so autocomplete lists them:
Scaffold(
extendBody: true, // pages scroll under the glass bar
appBar: AppBar(
title: const Text('Home'),
actions: [
GlassIconButton(
icon: const NativeImage.symbol('person.crop.circle'),
onPressed: openProfile,
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
GlassSegmentedControl(
items: const [NativeItem(title: 'Day'), NativeItem(title: 'Week')],
selectedIndex: range,
onChanged: (i) => setState(() => range = i),
),
GlassSwitch(
label: 'Wi-Fi',
value: wifi,
onChanged: (v) => setState(() => wifi = v),
),
GlassSlider(value: volume, onChanged: (v) => setState(() => volume = v)),
GlassMenuButton(
title: 'Choose an option',
width: 260,
items: const [NativeItem(title: 'Option 1'), NativeItem(title: 'Option 2')],
onSelected: (id) => NativeDialog.alert(title: 'Selected', message: id),
),
],
),
bottomNavigationBar: GlassTabBar(
items: const [
NativeItem.symbol('Home', 'house.fill'),
NativeItem.symbol('Profile', 'person.crop.circle.fill', badge: '2'),
],
selectedIndex: index,
onChanged: (i) => setState(() => index = i),
),
);
| Group | Widgets |
|---|---|
| Navigation | GlassTabBar (also expandable), GlassToolbar, GlassBackButton, GlassCloseButton |
| Buttons | GlassButton, GlassIconButton, GlassFloatingActionButton (expands into actions), GlassAvatarButton, GlassImageButton |
| Menus | GlassMenuButton (submenus, overflow), GlassPopupButton |
| Controls | GlassSegmentedControl, GlassSwitch, GlassStepper, GlassSlider, GlassPageIndicator, GlassCheckbox |
| Input | GlassTextField (secure, multiline, keyboards), GlassSearchBar, GlassDatePicker (compact, wheel, calendar), GlassWheelPicker, GlassColorPicker |
| Progress & media | GlassProgressIndicator (bar, ring, spinner), GlassPlayPauseButton, GlassMediaControls |
Native UI that appears over the app is one call. Each returns what the user chose:
| Call | Native UI |
|---|---|
NativeDialog.alert / .actionSheet / .prompt |
UIAlertController, with a text field for prompt |
NativeSheet.show |
Sheet with medium / large detents listing options |
NativeToast.show |
Glass toast at the top or bottom; touches pass through |
NativeShare.share |
UIActivityViewController |
On Android and other platforms most widgets fall back to their Material equivalent (Switch, Slider, SegmentedButton, TextField, MenuAnchor…), and every widget takes a fallback to show your own instead. Pass context: to the dialog, sheet and toast calls to get Material dialogs, bottom sheets and snack bars there; this also makes the iOS versions follow your app's light/dark theme.
Each widget is a platform view, which takes every touch inside its bounds, so give it the size it needs (width / height). Inside scroll views the widgets only claim taps, so drags still scroll the page; sliders also claim horizontal drags, and wheels and calendars claim all drags.
Copy-paste code for each one is in Widget examples below; the example app shows them all on its Widgets tab (example/lib/widgets_gallery.dart).
Controlling a component from Dart #
late NativeComponentController controller;
NativeComponent(
type: NativeComponentType.halfSheet,
onCreated: (c) => controller = c,
);
await controller.trigger(); // primary action: present, toggle, next…
await controller.reset(); // back to the initial state
await controller.send('custom', 42); // custom commands for your own components
controller.events.listen(print); // stream of NativeEvent
NativeComponentType.triggerLabel describes what trigger() does for each component.
Live updates #
Put the config in state and change it. The native view updates in place without being recreated:
setState(() {
config = config.copyWith(
title: 'Updated',
style: config.style.copyWith(tintColor: Colors.orange, glass: NativeGlass.clear),
);
});
Widget examples #
Copy-paste examples for every Glass… widget and native presenter, grouped like the table above. Each snippet goes in a build method; the ones with a value keep it in your State and call setState in the callback. Every snippet also works on Android, where it shows the Material fallback.
- Navigation: tab bar · toolbar · back & close
- Buttons: button · icon button · floating action button · avatar · image button
- Menus: menu button · pop-up button
- Controls: segmented control · switch · stepper · slider · page indicator · checkbox
- Input & pickers: search bar · text field · date picker · wheel picker · color picker
- Progress & media: progress · play / pause · media controls
- Alerts, sheets & toasts: alert · action sheet · prompt · sheet · toast · share
- Full screen example
Navigation #
GlassTabBar
A floating glass tab bar. Only the bar is native; the pages stay Flutter widgets.
int tab = 0;
Scaffold(
extendBody: true, // pages scroll under the glass bar
body: IndexedStack(index: tab, children: pages),
bottomNavigationBar: GlassTabBar(
items: const [
NativeItem.symbol('Home', 'house.fill'),
NativeItem.symbol('Search', 'magnifyingglass'),
NativeItem.symbol('Inbox', 'tray.fill', badge: '3'),
NativeItem.symbol('Profile', 'person.crop.circle.fill'),
],
selectedIndex: tab,
onChanged: (i) => setState(() => tab = i),
expandable: false, // true: only the selected tab shows its title
fallback: NavigationBar(
selectedIndex: tab,
onDestinationSelected: (i) => setState(() => tab = i),
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.search), label: 'Search'),
NavigationDestination(icon: Icon(Icons.inbox), label: 'Inbox'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
Placed anywhere other than the bottom of the screen, pass safeArea: false.
GlassToolbar
A glass capsule of buttons. Items with an icon show the icon; the destructive role is red.
GlassToolbar(
width: 240,
items: const [
NativeItem.symbol('Share', 'square.and.arrow.up'),
NativeItem.symbol('Favorite', 'heart'),
NativeItem.symbol('Info', 'info.circle'),
NativeItem.symbol('Delete', 'trash', role: NativeItemRole.destructive),
],
onPressed: (index) => debugPrint('Toolbar button $index'),
)
GlassBackButton · GlassCloseButton
Both pop the current route unless you pass onPressed.
AppBar(
leading: const GlassBackButton(),
actions: [GlassCloseButton(onPressed: () => Navigator.pop(context))],
)
Buttons #
GlassButton
GlassButton(
label: 'Glass',
onPressed: () {},
)
GlassButton(
label: 'Continue',
icon: const NativeImage.symbol('arrow.right'),
prominent: true, // filled glass
size: NativeControlSize.large,
onPressed: () {},
)
GlassIconButton
GlassIconButton(
icon: const NativeImage.symbol('person.crop.circle'),
semanticLabel: 'Profile',
onPressed: () {},
fallback: IconButton(icon: const Icon(Icons.person), onPressed: () {}),
)
GlassIconButton(
icon: const NativeImage.symbol('plus'),
prominent: true,
onPressed: () {},
)
GlassFloatingActionButton
Without actions it calls onPressed; with actions it expands into labelled glass buttons.
Scaffold(
floatingActionButton: GlassFloatingActionButton(
actions: const [
NativeItem.symbol('Photo', 'photo'),
NativeItem.symbol('Note', 'note.text'),
NativeItem.symbol('Reminder', 'checklist'),
],
onActionSelected: (index) => debugPrint('Create $index'),
fallback: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
),
);
GlassAvatarButton
An avatar (initials or photo) that opens a native menu.
GlassAvatarButton(
initials: 'RP',
// image: const NativeImage.asset('assets/me.jpg'),
size: 40,
items: const [
NativeItem.symbol('Profile', 'person'),
NativeItem.symbol('Settings', 'gear'),
NativeItem.symbol('Sign Out', 'rectangle.portrait.and.arrow.right',
role: NativeItemRole.destructive),
],
onSelected: (id) => debugPrint(id), // 'Profile', 'Settings', 'Sign Out'
)
GlassImageButton
Artwork with a glass badge, e.g. a video or album cover.
GlassImageButton(
image: const NativeImage.asset('assets/cover.jpg'),
badge: 'play.fill', // null hides the badge
width: 160,
height: 160,
cornerRadius: 18,
onPressed: () {},
)
Menus #
GlassMenuButton
Entries support icons, the destructive role and nested children (submenus). onSelected gets the entry's id, or its title.
GlassMenuButton(
title: 'File',
width: 140, // platform views can't size themselves: fit the title
items: const [
NativeItem.symbol('New File', 'doc.badge.plus'),
NativeItem(
title: 'Export As',
symbol: 'square.and.arrow.up',
children: [NativeItem(title: 'PDF'), NativeItem(title: 'PNG')],
),
NativeItem.symbol('Delete', 'trash', role: NativeItemRole.destructive),
],
onSelected: (id) => debugPrint(id),
)
An icon-only "more" menu:
GlassMenuButton(
icon: const NativeImage.symbol('ellipsis'),
showChevron: false,
prominent: false,
width: 52,
items: const [
NativeItem.symbol('Select', 'checkmark.circle'),
NativeItem.symbol('View Options', 'slider.horizontal.3'),
],
onSelected: (id) => debugPrint(id),
)
A menu that shows an alert for the chosen option:
GlassMenuButton(
title: 'Choose an option',
width: 260,
items: const [
NativeItem(title: 'Option 1'),
NativeItem(title: 'Option 2'),
NativeItem(title: 'Option 3'),
],
onSelected: (option) => NativeDialog.alert(
title: 'Selected',
message: 'You selected $option',
context: context,
),
)
GlassPopupButton
Shows the current choice and opens a native picker menu.
int repeat = 0;
GlassPopupButton(
label: 'Repeat',
items: const [
NativeItem(title: 'Never'),
NativeItem(title: 'Daily'),
NativeItem(title: 'Weekly'),
NativeItem(title: 'Monthly'),
],
selectedIndex: repeat,
onChanged: (i) => setState(() => repeat = i),
)
Controls #
GlassSegmentedControl
int range = 1;
GlassSegmentedControl(
items: const [
NativeItem(title: 'Day'),
NativeItem(title: 'Week'),
NativeItem(title: 'Month'),
],
selectedIndex: range,
onChanged: (i) => setState(() => range = i),
)
GlassSwitch
bool wifi = true;
GlassSwitch(
label: 'Wi-Fi', // omit for a bare switch
value: wifi,
onChanged: (v) => setState(() => wifi = v),
)
GlassStepper
double guests = 2;
GlassStepper(
label: 'Guests',
value: guests,
min: 1,
max: 10,
step: 1,
onChanged: (v) => setState(() => guests = v),
)
GlassSlider
double brightness = 0.6;
double rating = 3;
GlassSlider(
value: brightness,
minIcon: 'sun.min',
maxIcon: 'sun.max.fill',
onChanged: (v) => setState(() => brightness = v),
onChangeEnd: (v) => debugPrint('Saved $v'),
)
GlassSlider(
value: rating,
min: 1,
max: 5,
divisions: 4, // snaps to 1, 2, 3, 4, 5
onChanged: (v) => setState(() => rating = v),
)
GlassPageIndicator
int page = 0;
GlassPageIndicator(
count: 4,
currentIndex: page,
onChanged: (i) => setState(() => page = i), // tapped dot
)
GlassCheckbox
bool terms = false;
GlassCheckbox(
label: 'I agree to the terms',
value: terms,
onChanged: (v) => setState(() => terms = v),
)
Input & pickers #
GlassSearchBar
final search = GlassTextFieldController(); // optional: clear(), focus(), unfocus()
String query = '';
GlassSearchBar(
placeholder: 'Search photos',
controller: search,
onChanged: (q) => setState(() => query = q),
onSubmitted: (q) => debugPrint('Search "$q"'),
)
GlassTextField
GlassTextField(
placeholder: 'Email',
keyboardType: GlassKeyboardType.email,
onChanged: (text) => debugPrint(text),
onSubmitted: (text) => debugPrint('Done: $text'),
)
const GlassTextField(placeholder: 'Password', obscureText: true)
const GlassTextField(placeholder: 'Notes', multiline: true)
GlassDatePicker
DateTime date = DateTime.now();
// Compact field with a label
GlassDatePicker(
label: 'Date',
value: date,
onChanged: (d) => setState(() => date = d),
)
// Time only
GlassDatePicker(
label: 'Time',
value: date,
mode: GlassDatePickerMode.time,
onChanged: (d) => setState(() => date = d),
)
// Inline calendar, limited to the next year
GlassDatePicker(
value: date,
style: GlassDatePickerStyle.graphical, // or .wheel
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
onChanged: (d) => setState(() => date = d),
)
GlassWheelPicker
int size = 1;
GlassWheelPicker(
items: const [
NativeItem(title: 'Small'),
NativeItem(title: 'Medium'),
NativeItem(title: 'Large'),
],
selectedIndex: size,
onChanged: (i) => setState(() => size = i),
)
GlassColorPicker
iOS only: pass a fallback for other platforms.
Color accent = Colors.indigo;
GlassColorPicker(
label: 'Accent colour',
value: accent,
supportsOpacity: false,
onChanged: (c) => setState(() => accent = c),
fallback: const Text('Colour picker is available on iOS'),
)
Progress & media #
GlassProgressIndicator
double progress = 0.4;
GlassProgressIndicator(value: progress, label: 'Uploading') // bar
GlassProgressIndicator(
value: progress,
style: GlassProgressStyle.circular, // ring with percentage
)
const GlassProgressIndicator() // no value: spinner
GlassPlayPauseButton
bool playing = false;
GlassPlayPauseButton(
isPlaying: playing,
onChanged: (v) => setState(() => playing = v),
)
GlassMediaControls
GlassMediaControls(
isPlaying: playing,
onPlayPause: (v) => setState(() => playing = v),
onPrevious: () => debugPrint('Previous'),
onNext: () => debugPrint('Next'),
)
Alerts, sheets & toasts #
Pass context: so the iOS UI follows your app's light/dark theme and other platforms get the Material equivalent.
NativeDialog.alert
Returns the id (or title) of the tapped action.
final choice = await NativeDialog.alert(
title: 'Delete photo?',
message: 'This photo will be deleted from all your devices.',
actions: const [
NativeItem(title: 'Delete', role: NativeItemRole.destructive),
NativeItem(title: 'Cancel', role: NativeItemRole.cancel),
],
context: context,
);
if (choice == 'Delete') deletePhoto();
Without actions it shows a single "OK".
NativeDialog.actionSheet
final choice = await NativeDialog.actionSheet(
title: 'You have unsaved changes',
actions: const [
NativeItem(title: 'Discard', role: NativeItemRole.destructive),
NativeItem(title: 'Save Draft'),
NativeItem(title: 'Cancel', role: NativeItemRole.cancel),
],
context: context,
);
NativeDialog.prompt
Returns the entered text, or null when cancelled.
final name = await NativeDialog.prompt(
title: 'Rename',
placeholder: 'Name',
initialValue: 'Holiday.jpg',
confirmLabel: 'Save',
context: context,
);
if (name != null) rename(name);
NativeSheet.show
Returns the id (or title) of the tapped item, or null when dismissed.
final target = await NativeSheet.show(
title: 'Share to',
message: 'Choose where to send the photo.',
items: const [
NativeItem.symbol('Messages', 'message.fill'),
NativeItem.symbol('Mail', 'envelope.fill'),
NativeItem(title: 'AirDrop', subtitle: 'Nearby devices', symbol: 'airplayaudio'),
],
detents: const [NativeSheetDetent.medium, NativeSheetDetent.large],
context: context,
);
NativeToast.show
A glass toast over the whole app; touches pass through it.
NativeToast.show(
'Copied to clipboard',
icon: 'doc.on.doc.fill',
position: NativeToastPosition.top, // or .bottom
duration: const Duration(seconds: 2),
context: context,
);
NativeShare.share
Returns whether the user completed a share.
await NativeShare.share(
text: 'Check out glassy_ui_ios',
url: 'https://pub.dev/packages/glassy_ui_ios',
);
Full screen example #
A complete app using the tab bar, an app bar button, a menu and an alert:
import 'package:flutter/material.dart';
import 'package:glassy_ui_ios/glassy_ui_ios.dart';
void main() => runApp(const MaterialApp(home: HomePage()));
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) {
final pages = [
_Page(
title: 'Home',
onProfile: () => setState(() => tab = 1),
child: GlassMenuButton(
title: 'Choose an option',
width: 260,
items: const [NativeItem(title: 'Option 1'), NativeItem(title: 'Option 2')],
onSelected: (option) => NativeDialog.alert(
title: 'Selected',
message: 'You selected $option',
context: context,
),
),
),
const _Page(title: 'Profile', child: Text('Profile Screen')),
];
return Scaffold(
extendBody: true,
body: IndexedStack(index: tab, children: pages),
bottomNavigationBar: GlassTabBar(
items: const [
NativeItem.symbol('Home', 'house.fill'),
NativeItem.symbol('Profile', 'person.crop.circle.fill'),
],
selectedIndex: tab,
onChanged: (i) => setState(() => tab = i),
fallback: NavigationBar(
selectedIndex: tab,
onDestinationSelected: (i) => setState(() => tab = i),
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
);
}
}
class _Page extends StatelessWidget {
const _Page({required this.title, required this.child, this.onProfile});
final String title;
final Widget child;
final VoidCallback? onProfile;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
actions: [
if (onProfile != null)
GlassIconButton(
icon: const NativeImage.symbol('person.crop.circle'),
semanticLabel: 'Profile',
onPressed: onProfile!,
fallback: IconButton(icon: const Icon(Icons.person), onPressed: onProfile),
),
const SizedBox(width: 8),
],
),
body: Center(child: child),
);
}
}
Configuration #
Every component reads the same NativeComponentConfig. Each one documents the fields it uses in its Swift source (ios/glassy_ui_ios/Sources/glassy_ui_ios/Components/).
| Field | Purpose |
|---|---|
title, subtitle, message, placeholder |
Text content |
image |
Main icon or artwork: NativeImage.symbol('star.fill'), NativeImage.asset('assets/cover.jpg'), NativeImage.bytes(data) |
items |
Tabs, menu actions, segments, options, rows (List<NativeItem>; nest with children for submenus and columns) |
value, min, max, step |
Initial value and range. DateTime and Color values are encoded automatically |
enabled |
Disables the whole component |
extra |
Component-specific options, e.g. {'detents': ['0.25', 'medium', 'large']} |
style |
A NativeStyle (see below) |
NativeStyle #
| Property | Description |
|---|---|
tintColor |
Accent color for buttons, switches, sliders and selection |
foregroundColor |
Primary text and icon color |
backgroundColor / backgroundGradient |
Background behind the component |
showBackdrop |
Colorful default backdrop that makes glass refraction visible |
surfaceColor |
Card color when glass is disabled |
cornerRadius |
Corner radius of cards, sheets and rounded glass |
glass |
NativeGlass.regular, .clear or .none |
glassTint, interactive |
Glass tint and touch reaction |
padding, spacing |
Layout |
fontSize, fontWeight, fontDesign |
Title typography |
colorScheme |
Force light or dark |
controlSize |
mini … extraLarge |
NativeItem #
const NativeItem.symbol('Inbox', 'tray', badge: '3');
const NativeItem(id: 'delete', title: 'Delete', symbol: 'trash', role: NativeItemRole.destructive);
const NativeItem(title: 'Export', children: [NativeItem(title: 'PDF'), NativeItem(title: 'PNG')]);
Images from assets #
Declare the asset in your app's pubspec.yaml and pass its path:
NativeComponentConfig(image: const NativeImage.asset('assets/album.jpg'));
Events #
NativeEvent has a name, a value and a time. The names are consistent across components:
| Event | Sent when |
|---|---|
change |
A value changed (slider, toggle, picker, tab…) |
select |
An item, row or menu entry was chosen (value = item id) |
tap / action |
A button or action was pressed |
presented / dismissed |
A sheet or overlay appeared or disappeared |
complete |
A progress indicator finished |
Custom native components #
Any SwiftUI view in your app can become a component. Make it conform to NativeComponent and register it:
import glassy_ui_ios
import SwiftUI
struct RatingView: NativeComponent {
static let componentId = "rating"
@EnvironmentObject private var context: ComponentContext
@Environment(\.nativeConfig) private var config
@State private var rating = 0
var body: some View {
ComponentScaffold {
GlassCard(config.title ?? "Rate") {
HStack {
ForEach(1...5, id: \.self) { star in
Button {
rating = star
context.emit("change", star)
} label: {
Image(systemName: star <= rating ? "star.fill" : "star")
}
}
}
}
}
.seed($rating, from: config.int(or: 0))
.onNativeCommand { if $0.name == "reset" { rating = 0 } }
}
}
// AppDelegate, after GeneratedPluginRegistrant.register(...)
NativeComponentRegistry.register(RatingView.self)
NativeComponent.custom(id: 'rating', config: const NativeComponentConfig(title: 'Rate us'));
These building blocks are available to your views: adaptiveGlass, nativeGlass, nativeButtonStyle, AdaptiveGlassContainer, adaptiveGlassID, adaptiveGlassUnion, ComponentScaffold, ComponentBackground, GlassCard, NativeIcon, NativeArtwork, NativeItemLabel, NativeItemButton, CloseButton, GlassIconButton and GlassSupport.isAvailable.
System helpers #
final info = await NativeSystem.info(); // iOS version, liquidGlass, registered ids
await NativeSystem.haptic(NativeHaptic.success);
Component gallery #
Screenshots were taken on an iPhone simulator running iOS 27 (Liquid Glass), using each component's default configuration.
The example app contains all 90 components, a live Customize panel, and a Code button with copy-paste ready code for each component (also in example/lib/snippets.dart).
Navigation & Toolbars (9) #
![]() Navigation Bar / App Bar NativeComponentType.navBarNavigationStack + .toolbar — glass bar on iOS 26 | ![]() Back Button NativeComponentType.backButtonNavigationStack(path:) system back button | ![]() Close Button NativeComponentType.closeButtonButton(role: .close) / xmark.circle.fill |
![]() Navigation Title NativeComponentType.navTitle.navigationTitle + .toolbarTitleMenu | ![]() Large Navigation Title NativeComponentType.largeNavTitle.navigationBarTitleDisplayMode(.large) | ![]() Toolbar NativeComponentType.toolbarToolbarItemGroup + ToolbarSpacer (iOS 26) |
![]() Floating Toolbar NativeComponentType.floatingToolbarGlassEffectContainer + .glassEffect / material capsule | ![]() Bottom Toolbar NativeComponentType.bottomToolbarToolbarItem(placement: .bottomBar) | ![]() Toolbar Action Button NativeComponentType.toolbarActionButton.confirmationAction + .glassProminent |
Buttons (9) #
![]() Glass Button NativeComponentType.glassButton.buttonStyle(.glass) / .bordered | ![]() Prominent Glass Button NativeComponentType.prominentGlassButton.buttonStyle(.glassProminent) / .borderedProminent | ![]() Image Button NativeComponentType.imageButtonButton with image label + custom ButtonStyle |
Circular Icon Button NativeComponentType.circularIconButton.buttonBorderShape(.circle) + glass | ![]() SF Symbols Button NativeComponentType.sfSymbolsButton.symbolEffect, rendering modes, variableValue | ![]() Plus Button NativeComponentType.plusButtonGlass circle + .numericText transition |
![]() Minus Button NativeComponentType.minusButtonGlass circle + .numericText transition | ![]() Floating Action Button NativeComponentType.floatingActionButtonGlass morphing with glassEffectID | Avatar Action Button NativeComponentType.avatarActionButtonToolbar Menu with avatar label |
Controls (7) #
![]() Segmented Control NativeComponentType.segmentedControlPicker(.segmented) | ![]() Toggle / Switch NativeComponentType.toggleToggle(.switch) | ![]() Stepper NativeComponentType.stepperStepper(value:in:step:) |
![]() Slider NativeComponentType.sliderSlider with min/max value labels | ![]() Slider with Tick Marks NativeComponentType.sliderTicksSlider(tick:) iOS 26 / drawn ticks | ![]() Page Indicator NativeComponentType.pageIndicatorTabView(.page) + .indexViewStyle |
![]() Checkbox-Style Control NativeComponentType.checkboxCustom ToggleStyle with SF Symbols |
Tabs & Structure (10) #
![]() Tab Bar NativeComponentType.tabBarTabView + .tabItem | ![]() Floating Tab Bar NativeComponentType.floatingTabBar.tabBarMinimizeBehavior(.onScrollDown) / custom capsule | ![]() Tab Bar Item NativeComponentType.tabBarItem.tabItem + .badge |
![]() Expandable Tab Bar NativeComponentType.expandableTabBarGlass capsules that expand with glassEffectID | ![]() Search Tab NativeComponentType.searchTabTab(role: .search) iOS 18+ / tabItem | ![]() Sidebar NativeComponentType.sidebarNavigationSplitView + .listStyle(.sidebar) |
![]() Navigation Stack NativeComponentType.navigationStackNavigationStack(path:) + navigationDestination | ![]() Split View NativeComponentType.splitViewThree-column NavigationSplitView | ![]() Floating Accessory View NativeComponentType.floatingAccessory.tabViewBottomAccessory (iOS 26) / safeAreaInset |
![]() Inspector Panel NativeComponentType.inspector.inspector(isPresented:) |
Sheets, Dialogs & Overlays (11) #
![]() Bottom Sheet NativeComponentType.bottomSheetSheet with .height detent + background interaction | ![]() Half-Screen Sheet NativeComponentType.halfSheet.presentationDetents([.medium]) | ![]() Full-Screen Sheet NativeComponentType.fullSheet.fullScreenCover |
![]() Detented Sheet NativeComponentType.detentedSheetDetents with selection binding | ![]() Sheet Drag Indicator NativeComponentType.dragIndicator.presentationDragIndicator | ![]() Alert Dialog NativeComponentType.alert.alert with roles + text field |
![]() Confirmation Dialog NativeComponentType.confirmationDialog.confirmationDialog — popover on iOS 26 | ![]() Action Sheet NativeComponentType.actionSheetUIKit UIAlertController(.actionSheet) | ![]() Popover NativeComponentType.popover.popover + .presentationCompactAdaptation |
![]() Toast / Notification Overlay NativeComponentType.toastGlass capsule overlay with transition | ![]() Confirmation Popup NativeComponentType.confirmationPopupCustom glass card modal |
Menus (7) #
![]() Popup Menu NativeComponentType.popupMenuMenu + Picker (pop-up button) | ![]() Dropdown Menu NativeComponentType.dropdownMenuMenu with sections, Picker and Toggle | ![]() Context Menu NativeComponentType.contextMenu.contextMenu with preview |
![]() Overflow Menu NativeComponentType.overflowMenuToolbar Menu with ellipsis | ![]() Nested Submenu NativeComponentType.nestedSubmenuMenu inside Menu | ![]() Share Menu NativeComponentType.shareMenuShareLink + UIActivityViewController |
![]() Text Selection Menu NativeComponentType.textSelectionMenuUITextView editMenuForTextIn |
Input & Pickers (10) #
![]() Text Field NativeComponentType.textFieldTextField styles + glass field | ![]() Secure Password Field NativeComponentType.secureFieldSecureField with reveal + strength | ![]() Search Field NativeComponentType.searchField.searchable + .searchSuggestions |
![]() Multiline Text Editor NativeComponentType.textEditorTextEditor + FocusState | ![]() Date Picker NativeComponentType.datePickerDatePicker(.graphical / .compact) | ![]() Time Picker NativeComponentType.timePickerDatePicker(.hourAndMinute, .wheel) |
![]() Wheel Picker NativeComponentType.wheelPickerPicker(.wheel) multi-column | ![]() Menu Picker NativeComponentType.menuPickerPicker(.menu) | ![]() Color Picker NativeComponentType.colorPickerColorPicker (returns hex to Dart) |
![]() Floating Search Bar NativeComponentType.floatingSearchBarGlass search capsule over content |
Progress (7) #
![]() Circular Progress Indicator NativeComponentType.circularProgressGauge(.accessoryCircularCapacity) + ring | ![]() Linear Progress Bar NativeComponentType.linearProgressProgressView(value:) | ![]() Determinate Progress Bar NativeComponentType.determinateProgressProgressView(value:total:) + Gauge |
![]() Indeterminate Loading Indicator NativeComponentType.indeterminateLoadingProgressView() + animated bar | ![]() Download Progress Indicator NativeComponentType.downloadProgressApp Store-style ring button | ![]() Upload Progress Indicator NativeComponentType.uploadProgressPer-file ProgressView |
![]() Activity Indicator NativeComponentType.activityIndicatorUIKit UIActivityIndicatorView |
Media & Camera (11) #
![]() Gallery Toolbar NativeComponentType.galleryToolbarGrid + bottomBar selection toolbar | ![]() Photo Viewer Toolbar NativeComponentType.photoViewerToolbarGlass top/bottom chrome | ![]() Media Playback Controls NativeComponentType.mediaControlsNow Playing glass card |
![]() Play / Pause Button NativeComponentType.playPause.contentTransition(.symbolEffect(.replace)) | ![]() Skip Forward / Backward Button NativeComponentType.skipButtonsgobackward.15 / goforward.30 + bounce | ![]() Volume Slider NativeComponentType.volumeSliderSlider + MPVolumeView |
![]() Full-Screen Media Close Button NativeComponentType.mediaCloseGlass xmark over media | ![]() Picture-in-Picture Controls NativeComponentType.pipControlsDraggable mini player + AVKit support check | ![]() Camera Controls NativeComponentType.cameraControlsShutter, modes, flash, flip |
![]() Zoom Controls NativeComponentType.zoomControlsZoom pills + MagnifyGesture | ![]() Crop Controls NativeComponentType.cropControlsAspect ratios, straighten dial, rotate |
Liquid Glass (9) #
![]() Scroll-Edge Glass Effect NativeComponentType.scrollEdge.scrollEdgeEffectStyle(.soft / .hard) | ![]() Glass Background NativeComponentType.glassBackgroundGlass.regular / .clear / .tint | ![]() Custom Glass Shape NativeComponentType.customGlassShape.glassEffect(in: customShape) |
![]() Glass Button Group NativeComponentType.glassButtonGroup.glassEffectUnion | ![]() Glass Effect Container NativeComponentType.glassContainerGlassEffectContainer(spacing:) blending | ![]() Glass Morphing Animation NativeComponentType.glassMorphing.glassEffectID morphing |
![]() Glass Transition NativeComponentType.glassTransition.glassEffectTransition(.materialize) | App Icon with Layered Glass Styling NativeComponentType.appIconLayered icon: default / dark / clear / tinted | ![]() Widget with Clear or Tinted Appearance NativeComponentType.widgetAppearanceFull colour / clear / tinted rendering |
Limitations #
- Glass refracts only native content inside the platform view, not the Flutter widgets behind it. That's why components draw their own backdrop (
NativeStyle.showBackdrop). - iOS only. On other platforms
NativeComponentshows itsfallbackwidget. - Picture-in-picture and camera controls are UI only: no video asset and no camera session are used.
MPVolumeViewrenders only on physical devices. - Every component is a platform view, so prefer a few large components per screen over many small ones.
FAQ #
Does it work on Android, web or desktop?
No. The components are Apple's own, so they only exist on iOS. On other platforms NativeComponent renders its fallback widget, so you can show a Material version there.
Do my users need iOS 26?
No. The minimum is iOS 17. On iOS 26+ components use Liquid Glass. On iOS 17–18 the same components use classic materials (.regularMaterial, .bordered…) automatically. Check at runtime with NativeSystem.info() → liquidGlass.
My app still uses CocoaPods. Can I use this package?
Yes. Turn on Swift Package Manager for your app (see Installation). Flutter then integrates this plugin through SPM, while your other CocoaPods plugins keep working.
How do I get the code for a specific component?
Run the example app and tap the Code button on any component, or browse example/lib/snippets.dart. Every snippet is compiled and tested against the current API.
What about performance?
Each component is a platform view. That's fine for whole screens, bars, sheets and cards. Prefer a few larger components per screen over dozens of tiny ones in a scrolling list.
Can I add my own SwiftUI views?
Yes. See Custom native components.
Support the project #
If glassy_ui_ios saves you time, please give it a 👍 on pub.dev and a ⭐ on GitHub. It helps other Flutter developers find it.
Found a bug or want a component? Open an issue. Pull requests are welcome; see CONTRIBUTING.
License #
MIT. See LICENSE.



























































































