veneer 0.14.1
veneer: ^0.14.1 copied to clipboard
Real native iOS 26 UI for Flutter: Liquid Glass, UIKit tab and navigation bars, composers and sheets driven by Flutter layout, with matching Flutter UI on Android.
Changelog #
0.14.1 #
- Navigation bars keep clear of the screen's edge without a status bar: in landscape, or wherever the
top safe area is zero,
NativeNavigationBarnow sits 8 pt below the edge instead of flush against it, natively and in the Flutter replica. Content padding follows.
0.14.0 #
- Recording bar clip layout:
recording: NativeComposerRecording(...)onNativeComposerandNativePromptComposerswaps the Slack-style recording row for a clip layout: an optional title line (e.g. "Recording an audio clip…"), 40 pt cancel and done circles, 2 pt bars 1 pt apart inwaveColor, a00:00time and, withpausable, a pause button that turns into resume and carries on into the same clip. Icons, colours and VoiceOver labels are configurable; without it the bar is unchanged.
0.13.1 #
- Native menus inside Flutter-drawn sheets: inside a
VeneerFallbackScope(such as a Flutter-drawn sheet, where the overlay can't follow),NativeMenuAnchornow opens UIKit'sUIMenufrom a platform-view button instead of the Flutter replica. - Native navigation bars inside Flutter-drawn sheets: there,
NativeNavigationBaris now the realUINavigationBaras a platform view — glass bar buttons, title and menus — instead of the replica. Content padding and the fade under the bar stay as before; UIKit's scroll edge effect itself needs the overlay. - Fix: menus opening upward kept their order. UIKit reverses a button's menu when it opens upward (the
first item nearest the button);
GlassShapeandNativeMenuAnchormenus now keep the order they were given.
0.13.0 #
- Alerts:
showNativeAlert(context:, title:, message:, actions:, tint:)presents UIKit'sUIAlertControllerover whatever the app or sheet shows and answers with the picked action's index (NativeAlertActionwithnormal/cancel/destructivestyles and apreferredaction);CupertinoAlertDialogoff iOS 26. - Leaf controls:
NativeSwitch,NativeSliderandNativeSegmentedControl(text segments) are UIKit'sUISwitch,UISliderandUISegmentedControlas platform views laid out by Flutter, so they also work inside Flutter-drawn sheets. They're controlled like Flutter's: a parent that keeps its value puts the control back. Cupertino controls off iOS 26.
0.12.1 #
- Menu image size:
NativeMenuItem.iconSizesets an image or glyph icon's side in points instead of the menu's 20 — e.g. 24 for avatars, natively and in the Flutter menu.
0.12.0 #
- Glass behind Flutter content:
GlassBackground(child:, style:, tint:, cornerRadius:, onTap:)is Liquid Glass under any Flutter widget — a dock, a search field, a pill holding Flutter text — as a platform view (UIVisualEffectView+UIGlassEffect) composited beneath the child. WithonTapthe glass is interactive and takes the tap natively. Off iOS 26 it is the replica surface. - Menu titles:
NativeMenuAnchor(title:)andGlassShape(menuTitle:)head the menu with a title (UIKit's menu title; a header row in the Flutter menu). - Round images:
NativeIcon.imageandNativeIcon.imageFiletakecircular: trueto fill the box and crop to a circle — for avatars in menus.
0.11.0 #
- Richer menu items:
NativeMenuItemtakes asubtitle,checked(UIKit's checkmark for the current choice) andenabled(dimmed and unpickable when false), natively and in the Flutter menu. Items that use none of them encode exactly as before. - Menus from Flutter triggers:
NativeMenuAnchor(items:, child:)opens a nativeUIMenufrom a tap on any Flutter widget, growing out of it; the Flutter menu off iOS 26. - Glass-less shapes:
GlassStyle.nonemakes aGlassShapean invisible native hit region (a tap or a menu) with no glass, for content Flutter draws. - Fix: native views under platform-view areas stopped taking touches. Flutter adds a platform view's clipping and overlay layers to the FlutterView when its composition changes, often on a frame that moves no glass. The overlay only moved back on top when glass geometry was sent, so until then those layers took every touch over the platform view (a glass button on a platform-view surface did nothing). The overlay now checks its place at a low rate between frames as well.
0.10.0 #
- Full-screen sheets:
showNativeSheet(fullScreen: true)presents a page that fills the screen (UIKit's.fullScreenon iOS 26; a Flutter sheet at full height elsewhere), with no detents and no drag to dismiss.NativeSheet.enterFullScreen(context)turns an open sheet into one while keeping its content running and its state: a native sheet's engine moves to a full-screen presentation behind a cross-fade, a Flutter sheet grows to fill the screen.NativeSheetRoutetakesfullScreenand reportsisFullScreen. A kept-alive sheet comes back in the presentation it's asked for. Asked while the sheet is still coming up, or while something is presented over it, the move waits until it can. - Closing a sheet that's still coming up:
NativeSheetController.close(andNativeSheet.close) asked mid-presentation now closes it once it's up, instead of being dropped by UIKit; a sheet still measuring its content just ends. - Cleanup before a sheet's engine ends:
NativeSheet.setClosingHandlerruns inside a native sheet once it has closed, and its engine waits for it (up to 3 s) before ending — so work that has to reach native code, like stopping a camera, isn't cut off. - Plugins per sheet kind (iOS):
VeneerPlugin.sheetEntrypointPluginRegistrantis told the Dart entrypoint each sheet engine runs, so an app can register heavy plugins (a call's WebRTC, say) only in the engines that use them. It takes precedence oversheetPluginRegistrant.
0.9.3 #
- Bar button replicas (Android, iOS 15–25): a lone text button in
NativeNavigationBar's trailing items is a capsule that fits its label, instead of a 44 pt circle that clipped it.prominentbuttons draw as tinted glass (tint, then the bar'stintColor, then the accent) with a white label, as UIKit's.prominentstyle does.
0.9.2 #
- Colour emoji in the composer replicas: emoji that ask for colour presentation (U+FE0F), such as ❤️, ☀️, ⚠️, keycaps and ZWJ sequences like ❤️🔥, now draw in the platform's colour emoji font. Before, an app text font with its own glyph for the base character (e.g. ❤) drew them in black. Highlights and the keyboard's composing underline still apply.
0.9.1 #
- Tab bar handoff: when
NativeChromeScopes overlap — a route replacing the page holding one with another, or a route with its own scope pushed over one — the newest scope drives the native tab bar. An older scope going away no longer removes the newer one's bar or its tap handlers, and when the newest goes, the one before it takes the bar back with its own tabs and covered state.
0.9.0 #
- Search field:
NativeSearchFieldis a realUISearchTextFieldon Liquid Glass, positioned by Flutter layout like aGlassShape. UIKit draws the magnifier, placeholder, clear button and caret and owns the keyboard; text syncs both ways through aTextEditingController, withonChanged,onSubmittedandonFocusChanged, andNativeSearchFieldState.focus()/unfocus(). Off iOS 26 it renders a Flutter field of the same height. - Segment bar:
GlassSegmentBaris one glass capsule holding a row of icon (or emoji) buttons, with a highlight that springs to the selected segment, reporting taps throughonSelected. Off iOS 26 it renders the same capsule in Flutter. - Icon fallbacks:
leaf,fork.knife,soccerball,airplaneandflagnow map to Material icons off iOS 26.
0.8.0 #
- Voice clip attachments:
NativeComposerAttachment(audio: NativeComposerAudio(...))shows the attachment as a full-width row in the prompt composer: a play/pause circle in the tint, a waveform that fills with the tint asprogressmoves, the duration and a remove button. It sits 8pt from the composer's edges, 56pt tall, in the space the tile strip already takes. The app plays the clip and rebuilds with new values; the play/pause button callsonTap. Drawn natively on iOS 26 and by the Flutter replica elsewhere. - Attachments fold away while unfocused: with
NativePromptComposer(attachmentSummary: ...), the attachments collapse into a[📎 3]pill beside the buttons when the prompt loses focus (e.g. a scroll dismissed the keyboard), so the composer goes back to one row. Focusing the prompt, or tapping the pill, shows them again.
0.7.3 #
- Flutter sheet stacking matches UIKit: a Flutter-drawn sheet at its large detent now meets the status bar, as UIKit's does, instead of stopping 10pt lower. A sheet that comes up over another sheet stops 10pt lower, so the one behind shows above it. The sheet behind keeps its top edge and shrinks 9% (was: lifted 10pt and shrank 6%), measured against UIKit sheets on iOS 26.
0.7.2 #
- Plugin UI over sheets: a presentation aimed at a view controller that already presents a
native sheet now goes to the sheet on top. Plugins usually present from the window's root view
controller (contact pickers, calendar editors, share sheets), and UIKit refuses that while a
sheet is up, so their UI never appeared. Presentations UIKit would accept are untouched;
VeneerPlugin.forwardsPresentationsOverSheets = falseturns it off.
0.7.1 #
- Sheet appearance: inside a native sheet,
NativeSheet.appearedturns true once the sheet has finished sliding up, andNativeSheet.untilAppeared()waits for it (at once outside native sheets). UIKit won't present over a sheet mid-transition, so wait for it before showing native UI, such as a payment sheet or a system picker, over a sheet as it opens.
0.7.0 #
- Messages to sheets:
showNativeSheet(controller: NativeSheetController())lets the appsendmessages to the open sheet, which reads them fromNativeSheet.messages(context), andcloseit with a result. WithNativeSheet.request, a sheet can stay a view of state the app owns: the payload is its first state, requests carry actions to the app, and answers and messages bring fresher state back. Works in the Flutter fallback too. - Content-sized sheets open about 300 ms sooner: a sheet with a
NativeSheetDetent.content()detent now measures its content before it slides up. Flutter lays a view out only once it's in a window, so before, the measurement never came until the sheet was on screen: every such sheet waited out the 350 ms limit and then slid up at the wrong height. Its view now measures attached beside the presenter, off screen, and comes off the moment it's measured. In a benchmark the slide started after about 65 ms instead of about 370 ms, without the dropped frame each opening had. - The engine for the next sheet starts once the sheet has slid up, so it never competes with the measuring and the slide.
- Choose sheet engines' plugins:
VeneerPlugin.sheetPluginRegistrantregisters only the plugins your sheets use, instead of all of the app's, so plugins that observe the app (calls, push, analytics) don't run twice.
0.6.1 #
- Fix: a content-sized sheet presented over another sheet no longer hides it. The sheet's
engine lays out once before its view has a width, and
NativeSheetContentreported that squeezed, screen-tall height; the sheet slid up at full height and then shrank, leaving the sheet under it stacked behind the tall one. Measurements now wait for a real width.
0.6.0 #
- Sheet requests: a native sheet can ask the app that presented it for something while it's
open —
NativeSheet.request(name, arguments)inside the sheet, answered byNativeSheet.setRequestHandlerin the app — e.g. a fresh credential, since a sheet's engine shares no memory with the app. Answers and errors (PlatformException:no_handler,not_shown) go back to the sheet. In the Flutter fallback the handler is called directly.
0.5.1 #
- Long capsule titles fit: a
NativeBarTitle(capsule: true)that doesn'tfillnow truncates its title (and subtitle) to the space between the leading and trailing buttons, natively as in the Flutter replica. Before, a title wider than the bar pushed the capsule and the trailing buttons into UIKit's overflow menu. It still hugs its text at the leading edge, now in its own glass.
0.5.0 #
- Kept-alive sheets:
showNativeSheet(keepAlive: true)hides a native sheet on close instead of ending its engine. The sheet's app keeps running (timers, network, audio; no frames while hidden), and the nextshowNativeSheetat the same entrypoint presents the same app, chrome and all. Inside,NativeSheet.shownsays whether it's on screen andNativeSheet.presentedbrings each presentation's payload;NativeSheet.releasefrees the engine (for example on sign-out). Ignored witharguments, and on the Flutter fallback.
0.4.6 #
- Tappable attachments:
NativeComposerAttachment.onTapis called when the attachment tile itself is tapped, e.g. to preview a photo or play a video, natively and in the Flutter replica. The remove button stays on top. WithoutonTapa tap on the tile focuses the prompt, as before.
0.4.5 #
- Padded bar buttons:
NativeBarButton.iconPaddingputs its own padding around the icon instead of UIKit's bar-button insets, which widen a large icon's glass into a capsule; a 42 pt avatar with 1 pt padding now fills a 44 pt circle. On iOS 26 it hides the bar's shared glass and carries its own round Liquid Glass, since UIKit pads custom views sideways too. Badges aren't shown on padded buttons.
0.4.4 #
- Tab action icon size:
NativeTabAction.iconSizesets the side of a non-symbol action icon (an SVG or image), natively and in the Flutter replica. Before, it was always 25 pt on iOS and 26 pt in the replica.
0.4.3 #
- Titled bar buttons:
NativeBarButton.showsTitleshows the title beside the icon in a glass capsule, like a labelled toolbar button, instead of using it only for VoiceOver.NativeBarButton.titleStylesets its font, size, weight and colour, natively too. On iOS 26 it's aUIButtoncustom view that UIKit puts the bar's glass behind; the Flutter replica draws the same capsule. Badges aren't shown on titled buttons. - Explicit scroll state:
NativeNavigationBar.scrolledtells a bar that isn't an ancestor of the scrolling content (e.g. one inScaffold.appBar) whether content is under it, so its scroll edge effect still shows. Null keeps detecting it from the child's scroll notifications.
0.4.2 #
- Bar button tint:
NativeBarButton.tintsets the colour of a prominent button's glass, such as the app's primary colour. Before, prominent buttons always took the bar'stintColorand fell back to the system blue.
0.4.1 #
- Fixes:
- Opening a native sheet after another was dismissed no longer aborts the app
(
Spawning from an engine without a shell).FlutterEngineGroupspawns every engine from its oldest living one, which could be a finished sheet's engine whose context was already destroyed. Sheet engines now spawn from a dedicated idle engine that lives with the app.
- Opening a native sheet after another was dismissed no longer aborts the app
(
0.4.0 #
- Context menus:
NativeContextMenuopens UIKit's own context menu when you long-press any Flutter widget. You get the press shrink, the widget lifting over a blurred backdrop with the menu beside it, haptics, and the settle back. UIKit recognizes the long press on the Flutter view and hit-tests against where Flutter laid the widget out that frame, so it follows scrolling and never interrupts a scroll. The lifted preview is a snapshot clipped toborderRadius. Android and iOS 15–25 get a Flutter menu next to the widget instead. - Voice recording in composers:
NativeComposerController.startVoiceRecording()turns either composer into a recording bar, as in Slack: cancel, a live waveform, the elapsed time and a done button in the tint colour. Recording is native (AAC.m4a, with microphone permission and haptics). The clip arrives inonVoiceRecordedas aNativeVoiceRecording(path and duration). A refusal arrives inonVoiceRecordingFailed, and so does a Flutter fallback, so apps can record their own way there. Also new:finishVoiceRecording(),cancelVoiceRecording(),isRecording, an optionalmaxDuration, and VoiceOver labels for the bar's buttons. A recording is cancelled when its composer goes away. Your app needsNSMicrophoneUsageDescription. - Mentions in composers: the controller tracks the caret (
selection), and gainssetText(text, selection:)andreplaceRangefor completing a mention being typed. Thehighlightsparameter draws given runs of text (such as@names) in the tint colour, natively and in the replicas. - Navigation bar:
NativeBarTitle(fill: true)stretches a title capsule across the space between the leading and trailing buttons, as a chat header does.iconSizefits an avatar-sized icon, such as a 40 pt group avatar, concentric with the capsule. - Fixes:
- A page taking over the shared native composer now sends its own text. A new page no longer shows the previous page's draft.
- A page coming back into view now shows its composer again.
- After a hot restart, a composer the previous isolate left on screen is removed.
0.3.0 #
- Native sheets can present native sheets. A sheet opened from inside a sheet's engine is a real UIKit sheet over it. Sessions are keyed per presenting engine, so a nested sheet no longer replaces its parent's session (which left the parent unable to close or report its result). Dismissing a sheet finishes the sheets above it too.
- Content-sized native sheets:
NativeSheetDetent.content()now works natively. Wrap the sheet's content inNativeSheetContent; the sheet lays out off screen, reports its height and slides up at it, then follows the content as it changes. - Faster sheets: the next engine for an entrypoint starts as soon as one is used, so a sheet opened from a sheet, or the next one, is warm too.
- Content-sized sheets, sizing: content taller than the sheet is laid out at the sheet's height, so its scroll views end at the sheet's bottom instead of below it; content the sheet is still growing to keeps its height rather than being squeezed for a frame.
- Stacked sheets: a Flutter sheet under another sheet steps back behind it (slightly smaller, its top peeking above, its grabber hidden) and returns when the sheet above closes.
- Navigation bar: the scroll edge effect shows only once the page's content scrolls under the bar, natively and in the replica, so content at rest under the bar stays crisp.
- Fixes: a detent index outside
detentsno longer crashes; a content-sized Flutter sheet follows content that grows after its first layout; the first drag on a content-sized native sheet scrolls its content instead of being lost.
0.2.2 #
- Sheets: a Flutter sheet given a
backgroundColorstays solid at every detent: content no longer shows the page behind through it, nor a lighter band under the bar's edge fade. ACupertinoDynamicColorfollows the theme's brightness while the sheet is open. Sheets without a colour keep iOS 26's translucency below the large detent. - Sheets: while the page behind recedes (the large detent), the status bar turns light over it, as in UIKit; adding that never rebuilds the sheet mid-drag.
- Fallbacks:
VeneerFallbackStyle.shadowsreplaces the default shadow under replica surfaces, so bars, buttons and the tab bar can use a design system's elevation. - Fixes: after a hot restart, chrome an earlier isolate left hidden (a Flutter sheet was up) shows again.
0.2.1 #
- Sheets:
NativeSheetDetent.content()sizes a Flutter-drawn sheet to its content and follows it as it changes; sheets shorter than large float inset from the screen edges. Sheet content gets aMaterial(text fields and list tiles work as-is) and the sheet's surface as its scaffold background. - Sheets, safe area and keyboard: a sheet rising for the keyboard grows at least as fast as the keyboard, so its content is never squeezed between the two; the keyboard inset reaching the content no longer counts the sheet's floating inset twice; dragging the sheet puts the keyboard away.
- Fixes: bars, composers and the tab bar come back when a popup or sheet on an outer
navigator closes (a page in a tab's navigator); after a hot restart the native overlay
reports its insets again, so pages under a
NativeNavigationBardon't slide under the bar.
0.2.0 #
- Navigation bar: titles can be leading-aligned (
NativeBarTitle.alignment) and styled with your app's fonts and colours (style,subtitleStyle), natively too. The title capsule takes anaccessoryglyph. - Bar buttons:
badge,prominenttinted glass, text-only buttons, andgroupto split trailing buttons into separate capsules. - Menus:
NativeMenuItem.divider()for sections. - Prompt composer:
stopAction,sendEnabled,sendBusyandeditable; attachments takeloading. - Sheets:
payload/NativeSheet.payload()pass data into pre-warmed engines. Sheet engines no longer restyle the app's status bar, and each is torn down when its sheet is dismissed (their overlays no longer outlive them). - Fallbacks:
VeneerFallbackStyleis aThemeExtension, so replicas can follow your design system. The replica bar spans its parent and places titles like UIKit, the menu is opaque with section bands, the tab bar sizes to its tabs and clears Android's gesture handle, and the Flutter sheet has a rim at its edge. - Fixes: a bar or composer on a page inside a nested navigator (a tab's) now hides when an
outer route covers it; a
NativeNavigationBaroutside aNativeChromeScopegives its content the right top inset; a composer host rebuilt mid-page keeps its height.
0.1.1 #
- Composer: if the software keyboard goes away while the text view keeps focus (for example at the end of an interactive dismissal), the composer now drops focus and collapses to its capsule, instead of staying expanded with no keyboard and bringing the keyboard back on the next tap.
- Composer: the collapsed capsule always shows its text from the start.
- Docs: a demo of the example app in the README.
0.1.0 #
First public release.
- Tab bar (
NativeChromeScope): a realUITabBarControllerwith the split layout's trailing button, badges, selected icons and a bottom scroll edge effect. Slides away while another route covers its page. - Navigation bar (
NativeNavigationBar): a realUINavigationBarwith glass buttons, a title capsule, grouped trailing buttons, native menus, animated item changes and iOS 26's scroll edge effect over Flutter content. - Composer (
NativeComposer): a messaging composer that morphs from a capsule into a card above the keyboard inside the keyboard's animation, with interactive keyboard dismissal. - Prompt composer (
NativePromptComposer): an assistant-style composer with inline actions, a primary action that becomes send, multi-line growth, attachments and glass side actions that split off for a live session. - Sheets (
showNativeSheet): real UIKit sheets hosting Flutter content in a pre-warmed engine, with detents, grabber and Veneer's bars and composers inside; a Flutter sheet with the same behaviour elsewhere. - Menus (
NativeMenuItem): nativeUIMenus from bar buttons, composer buttons and glass shapes. - Glass (
GlassShape,GlassGroup): Liquid Glass positioned by Flutter layout every frame through a synchronousdart:fficall, merging within groups and clipped by Flutter clips. - Icons (
NativeIcon): SF Symbols,IconDatafrom any icon font (including variable fonts), a native SVG renderer and raster images. - Flutter replicas of every component for Android and iOS 15–25, and
VeneerFallbackScope/Veneer.debugForceFallbackto force them.
