On a CraftHybridController (a UITabBarController), every screen's content box ends ~138pt above the bottom of the device. The app's configured backgroundColor fills what is left, so the floating tab bar appears to sit on a grey container running full width to the screen edge, and the last row of content is clipped at a hard horizontal edge rather than scrolling under the bar.
The line
packages/ios/templates/CraftNativeScreen.swift:6079
rootBottomToSafeArea = rootStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)The root stack is pinned to the safe-area bottom. Inside a tab bar controller that inset includes the tab bar, so the content box stops above it. Nothing is wrong with the tab bar itself — Craft never configures a UITabBarAppearance, and the band is not the bar's background.
How it was established
Measured on an iPhone 17 Pro simulator (iOS 26.5), app at @craft-native/ios 0.0.377, against a real app (HQ.training) rather than a fixture.
- The band and the content background were pixel-identical,
(242, 242, 247). Not conclusive on its own:#f2f2f7is also iOS's grouped-background grey, so either side could have been drawing it. - The app's own screen wrapper was repainted
#ff0000and rebuilt. The content area went red; the band stayed(242, 242, 247). So the band is not the app's background showing through. - The boundary sits at y=2208 of a 2622px capture — 736pt of 874, which is the safe-area bottom.
(242, 242, 247)is exactlycraft.config.json's"backgroundColor": "#f2f2f7"for that app, i.e. Craft's own root background.
So: content is bottom-pinned to the safe area, and Craft's root background fills the remainder.
What it should do
Content should reach the bottom of the view and scroll under the floating bar, with the bar's inset handed to scrollable content as a bottom content inset rather than shrinking the layout box. That is what the consuming app's own guidance already assumes — clearance belongs in contentContainerStyle, and the host owns safe-area insets — but an app cannot achieve it, because NativeNavigationOptions exposes no way to opt out: there is no translucent, contentInset, extendBehind or equivalent. tabBarHidden is the only related option, and hiding the bar is not the ask.
Care needed
That constraint governs every Craft screen, not only tabbed ones. Pinning the root to view.bottomAnchor unconditionally would push content under the home indicator on screens with no tab bar. The change wants to be conditional on a tab bar actually being present (and ideally on the content being scrollable), and verified across a few screen shapes — a plain screen, a tabbed screen, a sheet, and a screen with the tab accessory — not just one.
Also worth knowing
The native testing kit cannot catch regressions here. @stacksjs/stx's native testing kit models craft.navigation as open/back/push only, with no replace, no popToRoot and no way to assert setOptions effects — so screen presentation of this kind is verifiable only on a device. Separately, craft.navigation.replace(screen, params?) takes a screen name rather than a path; passing a path navigates nowhere, silently. Both are separate reports, noted here only because they shaped how this one had to be diagnosed.