ReviewOS

craft-native/craft

Tab bar: the selected-item highlight is a hardcoded Capsule, with no way to change its shape

#386
Closed glennmichael123 opened this 9 days ago ·
9 days ago

What

The floating tab bar draws its selection highlight as a Capsule(), hardcoded in the generated <App>.swift:

.background {
    if isSelected {
        Capsule()
            .fill(Color.primary.opacity(0.09))
            .matchedGeometryEffect(id: "selection", in: selection)
    }
}
.contentShape(Capsule())

There is no way to ask for anything else. A tab in the config is { id, title, symbol, badge } — no styling — and the mobile config has no tab-bar appearance options at all. The only lever is tint.

Why it matters

A fully-rounded highlight is a strong stylistic statement, and it is the one piece of app chrome that is always on screen. On an app whose own vocabulary is softer corners (10px cards, 6-10px controls), the capsule reads as borrowed — it does not match anything else the app draws.

It is also not a bug in the sense of being wrong: it matches iOS 26's Liquid Glass tab bar, where the selected indicator is a capsule. That is a reasonable default. The gap is that it is the ONLY option.

Shape of the ask

A config knob for the selection shape, or just its corner radius. Something like:

tabBar: {
  selection: { cornerRadius: 6 },   // or: shape: 'capsule' | 'rounded'
}

Default to the current capsule so nothing changes for anyone who does not set it.

Verified by patching

Swapping the two lines locally produces exactly what was wanted:

RoundedRectangle(cornerRadius: 6, style: .continuous)
    .fill(Color.primary.opacity(0.09))
    .matchedGeometryEffect(id: "selection", in: selection)

matchedGeometryEffect keeps working — the highlight still slides between tabs, and the animation is unchanged. So the shape is genuinely the only thing coupled here; nothing else in the bar depends on it being a capsule.

contentShape(Capsule()) would want to follow the same shape, or the tappable region keeps the capsule's clipped corners while the drawn highlight does not.

Why this cannot be fixed downstream

storage/framework/mobile/ios/Sources/<App>.swift is generated and gitignored; buddy build:ios rewrites it. The patch above survives exactly as long as nobody rebuilds, which is why it is offered as evidence rather than as a workaround.

Related, same shape of gap: #371 (<Switch> inert), #375 (navigation.open takes no props).

Seen on craft-native@0.0.187, iOS 26.5 simulator, renderer: 'native' with five tabs.

9 days ago

Resolved on main in 868d78a. NativeTabBarOptions.selection.cornerRadius configures the selected highlight and matching hit shape; omitting it preserves the existing capsule. TypeScript, generator, and simulator unit coverage included.

Sign in to comment on this issue.