What is missing
There is no way to give a native screen's navigation bar an opaque background. CraftNativeScreenController sets prefersLargeTitles and nothing else, so UIKit's default appearance applies and the bar keeps its material.
On iOS 26 that material is transparent enough that scrolled content is legible through it. For a plain list that is fine and intended. For a screen whose own header sticks under the bar — a week strip, a filter row, a segmented control — it is not: the sticky header is opaque, so you get a solid band directly below a cloudy one, with body text showing through the top of it and a visible seam between the two.
backgroundColor in setOptions does not help. It sets view.backgroundColor, which is behind the scrolling content; the bar blurs the content, not the screen colour.
What would fix it
A per-screen option that fills the bar with the screen's own colour:
if let solid = options["navigationBarSolid"] as? Bool {
navigationBarSolid = solid
applyNavigationBarFill()
}
private func applyNavigationBarFill() {
guard navigationBarSolid else {
navigationItem.standardAppearance = nil
navigationItem.scrollEdgeAppearance = nil
navigationItem.compactAppearance = nil
navigationItem.compactScrollEdgeAppearance = nil
return
}
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = screenBackgroundColor ?? config.resolvedBackgroundColor
appearance.shadowColor = .clear
navigationItem.standardAppearance = appearance
navigationItem.scrollEdgeAppearance = appearance
navigationItem.compactAppearance = appearance
navigationItem.compactScrollEdgeAppearance = appearance
}Three things worth keeping if you take this shape:
resolvedBackgroundColorrather than a hex. It is already a dynamicUIColor, so the bar repaints on a Dark Mode switch without anything being set again, and it matches whatever the screen is actually drawn on.- All four appearances. Otherwise the bar changes as you scroll, which is the seam reappearing in a different place.
shadowColor = .clear. The hairline is part of what makes it read as two surfaces.
Opt-in, defaulting to today's behaviour, so no existing app changes.
Built and ran it against an all-native five-tab app on iPhone 17 Pro (iOS 26), craft-native@0.0.187: with it off, a card is legible through the bar at the top of the screen; with it on, the content is clipped cleanly at the bar's edge and the bar and the sticky header below it read as one surface, in both light and dark. Happy to open a PR.