EduFlow

Section 2 · Layout and Style

Flexbox with Mobile Defaults

20 min3 min readFill in the blanks

Lesson at a glance

Lay out Trailhead’s list rows and screens with flexbox, knowing the mobile defaults (column direction, no shrinking, stretch) that explain most squashed or overflowing layouts.

  1. 1Same flexbox, different defaults
  2. 2flex: 1, the property you’ll type most
  3. 3Building the hike row
  4. 4Absolute positioning and wrapping

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Here’s a layout bug every web developer hits in their first week of React Native: a row with an icon, a title and a chevron looks perfect with “Ridge Loop” and breaks with “Old Mill Creek Trail to Eagle Point Lookout”. The chevron vanishes off the right edge. Nothing is wrong with your flexbox knowledge; React Native’s flexbox simply starts from different defaults.

Same flexbox, different defaults

React Native lays out every View with flexbox (via Yoga, a layout engine that implements the spec). There’s no display: block or inline; every View is a flex container. Four defaults differ from the web:

Property Web default React Native default
flexDirection row column
flexShrink 1 0
alignContent normal flex-start
flex a shorthand a single number

The first two explain most surprises. Children stack vertically unless you say flexDirection: 'row', and they don’t shrink when space runs out, which is why that long title pushed the chevron away.

In a column, the main axis runs top to bottom and justifyContent works vertically while alignItems works horizontally. In a row, the main axis runs left to right and the roles swap. flexDirection: 'column' (default) main: justifyContent cross: alignItems flexDirection: 'row' flex: 1 main: justifyContent
Main axis and cross axis in a column and a row

The rules you know still hold: justifyContent works along the main axis, alignItems across it. In a column (the default), justifyContent: 'center' centers vertically and alignItems: 'center' centers horizontally. Switch to a row and the two swap.

alignItems defaults to stretch, so a child in a column fills the full width unless you give it a width or change alignItems. That’s why a Text with a background color in a plain column stretches edge to edge.

flex: 1, the property you’ll type most

In React Native, flex takes a single number. flex: 1 means “grow to fill the remaining space along the main axis, and you may shrink”. Two places need it almost always:

  1. The root View of every screen, so it fills the screen instead of hugging its content.
  2. Every parent of a ScrollView or FlatList, because a scroll container scrolls within the height it’s given. If no ancestor bounds that height, it grows to its full content and the bottom gets cut off without ever scrolling.

When a screen renders as a thin strip at the top, or a list won’t scroll, walk up the tree and look for the missing flex: 1.

Building the hike row

Here’s the row that broke, fixed:

src/components/HikeRow.tsx
import { Image, StyleSheet, Text, View } from 'react-native';

export function HikeRow({ hike }: { hike: { name: string; distanceKm: number; date: string; thumbUrl: string } }) {
  return (
    <View style={styles.row}>
      <Image source={{ uri: hike.thumbUrl }} style={styles.thumb} />
      <View style={styles.body}>
        <Text style={styles.title} numberOfLines={1}>{hike.name}</Text>
        <Text style={styles.meta}>{hike.distanceKm} km · {hike.date}</Text>
      </View>
      <Text style={styles.chevron}>›</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 },
  thumb: { width: 56, height: 56, borderRadius: 8 },
  body: { flex: 1 },
  title: { fontSize: 17, fontWeight: '600' },
  meta: { fontSize: 14, color: '#5c6b62', marginTop: 2 },
  chevron: { fontSize: 24, color: '#9aa79f' },
});

Read it axis by axis. The row’s main axis is horizontal; alignItems: 'center' centers the three children vertically against the tallest one (the thumbnail). The thumbnail and chevron have fixed sizes. The body has flex: 1, so it takes whatever width is left, and numberOfLines={1} truncates a long name with an ellipsis instead of pushing the chevron off screen.

Two more tools round out everyday layout. alignSelf lets one child break from its parent’s alignItems: a “Log a hike” button in a stretched column can use alignSelf: 'flex-end' to sit on the right at its natural width. And aspectRatio sizes a box from one dimension, which is ideal for photos: give the trail photo width: '100%' and aspectRatio: 4 / 3, and its height follows the screen width on every phone without you calculating anything.

When a layout misbehaves, the fastest debugging trick is still the oldest one: give the suspect Views a temporary bright background color. You’ll see at once which box is too small, which one stretched, and which one never got its flex: 1.

Absolute positioning and wrapping

Every View is position: 'relative' by default, so position: 'absolute' places a child relative to its parent. That’s how you put a “New” badge on the corner of a thumbnail:

TSX
<View>
  <Image source={{ uri: hike.thumbUrl }} style={styles.thumb} />
  <View style={{ position: 'absolute', top: -4, right: -4, backgroundColor: '#e8590c', borderRadius: 8, paddingHorizontal: 6 }}>
    <Text style={{ color: 'white', fontSize: 11, fontWeight: '700' }}>NEW</Text>
  </View>
</View>

For a row of tag chips (“steep”, “lake”, “dog-friendly”) that should flow onto a second line when they run out of room, combine flexDirection: 'row', flexWrap: 'wrap' and gap: 8. Absolute positioning takes the child out of the flex flow entirely, so use it for overlays only, never to fake a layout that flex can express.

Next you’ll make these layouts hold up across phone sizes, notches, rounded corners and the two platforms’ differences.

Key takeaways

  • Every View is a flex container, and the main axis runs top to bottom by default (flexDirection: 'column').
  • justifyContent positions children along the main axis; alignItems positions them across it and defaults to stretch.
  • Children don’t shrink by default in React Native, so a long title pushes its siblings off screen until you give it flex: 1.
  • flex: 1 means take the remaining space; a screen’s root View and every parent of a ScrollView need it.
  • Use position: 'absolute' for overlays like badges, and flexWrap: 'wrap' with gap for chip rows.

Further reading

Check your understanding

3 questions

Question 1 of 3

A row has a 56-point thumbnail, a title and a chevron. Long hike names push the chevron off the right edge. What’s the fix?

Question 2 of 3

In a View with default styles, you set justifyContent: 'center'. Where do the children go?

Question 3 of 3

Trailhead’s detail screen is a ScrollView inside a View, and it won’t scroll; the content is simply cut off. What’s the likely cause?

Practice

Fill in the blanks

Style the hike row

Complete Trailhead’s list styles so that:

  1. the screen fills the whole display,
  2. each row lays out the thumbnail, text and chevron side by side, vertically centered,
  3. the text column takes the leftover width so long names don’t push the chevron away,
  4. difficulty chips flow onto a second line when they run out of room,
  5. the “NEW” badge sits on the thumbnail’s corner without affecting layout.
const styles = StyleSheet.create({ screen: { : 1, backgroundColor: '#f6f8f6' }, row: { flexDirection: '', alignItems: '', gap: 12, padding: 12, }, thumb: { width: 56, height: 56, borderRadius: 8 }, body: { flex: }, chips: { flexDirection: 'row', flexWrap: '', gap: 8 }, badge: { position: '', top: -4, right: -4 }, });