Patterns from production · Layout that holds
Wide tables become labelled cards
Nothing is left out, nothing scrolls sideways, and every value says what it is.
Wide tables become labelled cards
Try the controls to see the rule.
The rule
On a phone, a table of three columns or more shows each row as a card: its first cell is the title and every other cell is named by its column. Only tables whose cells carry their column’s name (data-label) change; any other table stays a table and scrolls inside itself.
Tokens used
space.md12pxcolor.text.primarytheme.text.primarycolor.bg.surfacetheme.surfacesize.tap44pxmotion.duration.base260ms
Native spec · generated from this pattern and the shared token registry
struct OvLabelledCards: 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: space.md=12px, color.text.primary=theme.text.primary, color.bg.surface=theme.surface, size.tap=44px, motion.duration.base=260ms@Composable
fun OvLabelledCards(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: space.md=12px, color.text.primary=theme.text.primary, color.bg.surface=theme.surface, size.tap=44px, motion.duration.base=260ms