React Native – Flexbox and Layout

June 22, 20264 min readUpdated 8/24/2026

Flexbox is the entire layout system. There is no grid, no float, no position: fixed, and no media queries. Everything on a React Native screen is positioned by flexbox, which is a much smaller thing to learn than CSS layout — once you know the handful of ways it differs.

Three defaults that are not the web's

These bite constantly when porting a design, and each is one word.

flexDirection defaults to 'column', not 'row'. Phones are tall; stacking is the common case. Every row in your app says so explicitly.

alignItems defaults to 'stretch'. Children fill the cross axis unless told otherwise — so a View in a column is full width by default, which is usually what you want and occasionally very confusing.

Everything is display: flex already. There is no display property to set, and no inline. A View is a flex container, always.

The row you will write a hundred times

  footer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },

Label on the left, value on the right, vertically centred. That single line is most of the UI in most apps — price rows, list headers, a title with a badge beside it.

The axes work exactly as on the web: justifyContent along the main axis, alignItems across it. Change flexDirection and the two swap meaning, which is the one genuinely confusing part of flexbox anywhere.

flex: 1

flex: 1 means "take the remaining space". It is how you make a screen fill the window, how a scroll area expands, and how one item in a row pushes another to the edge.

  pressable: { flex: 1 },
  pressed: { opacity: 0.8 },
  card: { padding: 0, overflow: 'hidden', flex: 1 },

Two of those three are flex: 1 for the same reason: the cards sit in a two-column grid, and without it a card with a short description would be shorter than its neighbour and the grid would go ragged.

⚠️ The classic mistake is expecting flex: 1 on a child to work when its parent has no height. Space can only be distributed if there is space; a column of unsized parents distributes nothing, and the child collapses to zero. When something has vanished, walk up the tree — the missing flex: 1 is almost always above the element you are looking at.

gap, and why you should use it

  chipRow: { flexDirection: 'row', flexWrap: 'wrap', gap: theme.spacing.sm },

gap spaces children without touching them. The alternative — a marginRight on every child and then a rule to strip it from the last one — is a recurring source of off-by-one bugs, and it stops working the moment the row wraps.

flexWrap: 'wrap' is what lets that row of topping chips flow onto as many lines as it needs. With gap, wrapped rows are spaced correctly in both directions for free.

Text has no overflow

There is no text-overflow: ellipsis, and no overflow: hidden to fall back on for text. Long text simply pushes the layout around. The fix is a prop on the Text itself:

          <View style={styles.footer}>
            {cheapest !== null ? (
              <Text variant="caption" tone="muted">
                from{' '}
                <Text variant="bodyStrong" tone="primary">
                  {formatMoney(cheapest)}
                </Text>
              </Text>
            ) : (
              <Text variant="caption" tone="subtle">
                Unavailable
              </Text>
            )}

numberOfLines={2} on the description above this truncates with an ellipsis and, more importantly, stops a wordy product from pushing the price row down and breaking the grid alignment. In a list of cards, that prop is the difference between a tidy grid and a ragged one.

Notice the nested Text too. On the web you would use a <span>; here a Text inside a Text flows inline with it, which is the only way to style part of a sentence differently. The {' '} is a deliberate space — JSX collapses whitespace across lines, so it has to be written.

Percentages, and the absence of media queries

Percentage strings work — width: '50%', maxHeight: '90%' — and are relative to the parent, as on the web. What does not exist is a media query. Responsive layout is done in JavaScript, with useWindowDimensions, and it re-runs when the device rotates. Lesson 17 covers it.

In practice most phone layouts need far less of this than a website. A single column that flexes covers the majority; tablets and landscape are where the dimension hook earns its keep.

Debugging a layout

The fastest trick in React Native, unchanged in a decade: give the suspect view a garish background colour and see what it actually occupies. Layout bugs here are almost always "this box is not the size I assumed", and colouring it in answers that in seconds where reading the styles does not.

The dev menu's element inspector does the same job more politely, and React DevTools shows the computed layout for a selected node.

What is next

Safe Areas, Notches and the Keyboard — the part of the screen you do not actually own.