Patterns from production · Elements

Tabs, not folds

Choose one navigation model for a set of peer views.

Tabs, not foldsLive demo

Try the controls to see the rule.

The rule

Use tabs only for settings and explainers whose parts are peers, each tab with its own address; never split content (lists, rails, sections) into tabs, because content in a tab is content people never find: shorten it with sideways rails, More (N) and folds that show their count. Use folds for details in the current view, and do not put tabs inside a fold for the same choice.

Tokens used

  • size.tap44px
  • motion.duration.fast180ms
  • motion.easing.standardcubic-bezier(.2,.7,.2,1)
  • space.md12px
  • motion.duration.base260ms

Native spec · generated from this pattern and the shared token registry

SwiftUI
struct OvTabsNotFolds: View {
    let status: String
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    var body: some View {
        HStack(spacing: OvScale.spaceMd) {
            Text(status).font(.body)
        }
        .frame(minHeight: OvScale.sizeTap)
        .animation(reduceMotion ? .none : .easeOut(duration: OvScale.motionDurationBase), value: status)
    }
}
// Tokens: size.tap=44px, motion.duration.fast=180ms, motion.easing.standard=cubic-bezier(.2,.7,.2,1), space.md=12px, motion.duration.base=260ms
Compose
@Composable
fun OvTabsNotFolds(status: String, reduceMotion: Boolean) {
    Row(
        modifier = Modifier.defaultMinSize(minHeight = OvScale.SizeTap),
        horizontalArrangement = Arrangement.spacedBy(OvScale.SpaceMd),
    ) {
        Text(status, style = MaterialTheme.typography.bodyMedium)
    }
    // Apply motion only when !reduceMotion.
}
// Tokens: size.tap=44px, motion.duration.fast=180ms, motion.easing.standard=cubic-bezier(.2,.7,.2,1), space.md=12px, motion.duration.base=260ms