Patterns from production · Elements
Tabs, not folds
Choose one navigation model for a set of peer views.
Tabs, not folds
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.tap44pxmotion.duration.fast180msmotion.easing.standardcubic-bezier(.2,.7,.2,1)space.md12pxmotion.duration.base260ms
Native spec · generated from this pattern and the shared token registry
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@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