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 cardsLive demo

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.md12px
  • color.text.primarytheme.text.primary
  • color.bg.surfacetheme.surface
  • size.tap44px
  • motion.duration.base260ms

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

SwiftUI
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
Compose
@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