A React Native pricing grid built from fixed widths and margins breaks on tablets; how would you rebuild it with flexWrap, gap, percentages and aspectRatio?
answer
- let the row decide how many fit
- flexWrap: 'wrap' plus gap
- flexBasis with flexGrow, not fixed widths
- alignContent packs lines at the start
- aspectRatio sizes the unset side
basics
~20 sMake the container a wrapping row with gap, give each card a preferred flexBasis plus flexGrow: 1 (and a maxWidth) so the number per line follows the width, and size artwork with width: '100%' and aspectRatio instead of fixed heights.
solid answer
~40 sFixed widths encode one screen, so replace them with rules Yoga can apply at any width. The container becomes `flexDirection: 'row'` with `flexWrap: 'wrap'` and a `gap`, so gutters appear only between cards and between lines. Each card gets a preferred `flexBasis`, say 260 points, plus `flexGrow: 1` and a `maxWidth`: one card per line on a phone, two or three on a tablet, and the leftover space shared instead of stranded. Percentage widths also work, but `'50%'` plus a gap is wider than the row, so the second card wraps. Artwork uses `width: '100%'` with `aspectRatio`, so its height follows the card's width. Wrapped lines pack at the top because `alignContent` defaults to `flex-start`, and cards in one line match heights through `alignItems: 'stretch'`.
code
tsx · 24 linesimport { StyleSheet, Text, View } from 'react-native';
type Plan = { id: string; name: string; price: string };
export function PlanGrid({ plans }: { plans: Plan[] }) {
return (
<View style={styles.grid}>
{plans.map(plan => (
<View key={plan.id} style={styles.card}>
<View style={styles.art} />
<Text style={styles.name}>{plan.name}</Text>
<Text>{plan.price}</Text>
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 16, padding: 16 },
card: { flexGrow: 1, flexBasis: 260, maxWidth: 420, padding: 16, borderWidth: 1 },
art: { width: '100%', aspectRatio: 16 / 9, marginBottom: 12, backgroundColor: '#dde3ea' },
name: { fontWeight: '600' },
});go deeper
Recall that flexWrap: 'wrap' is off by default and that gap puts space only between items.
Explain flexBasis plus flexGrow for grids, how aspectRatio fills the unset dimension, and why width: '50%' plus a gap wraps.
Rebuild the grid so the card count follows the width with no code, test rotation, long strings and large text, and know when explicit breakpoints are still warranted.
Set layout guidelines for a team so screens adapt from phone to tablet by rules rather than per-device constants, and decide when tablet-specific layouts justify their cost.
## Why the fixed grid breaks A pricing grid written as "two cards of `width: 170` with `marginRight: 12`" is a picture of one phone. On a tablet the two cards hug the left edge with a wide empty band beside them; on a small phone they overflow, because React Native items do not shrink by default. Hard-coded heights make things worse: the moment a plan name wraps to two lines, text spills out of its card. The fix is to describe **rules** that **Yoga**, React Native's layout engine, can apply at any width. ## The layout rules 1. **Wrapping row.** The container gets `flexDirection: 'row'` and `flexWrap: 'wrap'`. When the next card would not fit on the current line, it moves to a new line. 2. **Gutters with gap.** `gap: 16` (or `columnGap` and `rowGap` separately) adds space between cards and between lines, and never at the outer edges, so no card needs a special "last in row" margin. 3. **Preferred size, then share.** Each card gets `flexBasis: 260` and `flexGrow: 1`. Yoga fits as many 260-point cards plus gaps as the line allows, then grows them to use up the remaining width. A `maxWidth` keeps a lone card on the last line from stretching across a tablet. 4. **Proportional artwork.** An illustration gets `width: '100%'` and `aspectRatio: 16 / 9`, so its height is computed from its width at every size. 5. **No fixed heights on text containers.** Use `minHeight` where a design needs a floor, and let content decide the rest. ## How the defaults help | Default | Value | Effect on the grid | |---|---|---| | `alignItems` | `stretch` | Cards in the same line take the height of the tallest card | | `alignContent` | `flex-start` | Wrapped lines pack at the top of a taller container instead of spreading out | | `flexShrink` | `0` | Cards never squeeze below their basis; they wrap instead | | `flexWrap` | `nowrap` | Must be changed to `'wrap'` explicitly | Because `alignContent` is `flex-start`, a grid inside a tall screen does not suddenly space its lines apart. Change it only when a design asks for lines to spread or stretch. ## Percentages and their trap Percentage widths are resolved against the parent's size, so `width: '50%'` looks like the natural "two columns" rule. But the gap is **added** to the items' sizes when Yoga decides where a line breaks: two 50% cards plus a 16-point gap exceed the row, so the second card wraps and every card sits alone. Options: - Use a smaller percentage basis with growth, for example `flexBasis: '40%'` plus `flexGrow: 1`, so two fit and then share the leftover. - Prefer a point-based `flexBasis` with `flexGrow`, which adapts the column count to the width automatically. - Percentages also need a parent with a resolvable size along that axis; a percentage height inside a content-sized parent has nothing to resolve against. ## How aspectRatio resolves `aspectRatio` sets the **unset** dimension from the set one: - With a width set, height becomes width divided by the ratio. - With a `flexBasis` or flex growth on the main axis, it sizes the cross axis if that is unset. - It respects `minWidth`, `minHeight`, `maxWidth` and `maxHeight`. This replaces a common anti-pattern: computing an image height from the window width in JavaScript on every render. ## Tuning the gutters `gap` is shorthand for two separate values. `columnGap` sets the space between cards within a line, and `rowGap` sets the space between wrapped lines, so a grid can use a tight 12-point column gutter and a roomier 20-point row gutter. Gaps are never added before the first item or after the last, so outer spacing belongs in the container's `padding`. A `minWidth` on each card is a useful guard as well: it stops a card from ever shrinking narrower than its content can bear if someone later adds `flexShrink` to it. ## Verifying the rebuild - Rotate a tablet and confirm the card count per line changes without code. - Use the longest real plan names and prices, including translations. - Try the largest system text size and confirm no fixed height clips content. - Check the last line: one card alone should respect its `maxWidth`. Branching the layout on explicit window breakpoints is still possible, but the wrapping rules above cover most grids with no JavaScript at all.
- Why do cards in the same wrapped line end up the same height?The container's `alignItems` defaults to `stretch`, and in a row the cross axis is vertical, so each card stretches to the height of its line, which is set by the tallest card. Setting `alignItems: 'flex-start'` would let each card keep its content height instead.
- When is it still right to branch on the window width in code?When the structure itself changes, not just the count of cards: a side-by-side comparison table on tablets versus stacked cards on phones, or a different component entirely. Wrapping rules can reflow a grid, but they cannot swap one layout for another. Reading the window size is then done in code, since style objects have no media queries.
saying these in an interview costs you the question
- Two width: '50%' cards always fit on one line whatever the gap.
- Wrapped lines spread to fill the container because alignContent defaults to stretch.
- aspectRatio needs both width and height to be set.
- Style objects can use media queries to switch column counts.
- Fixed heights on cards are fine because text never grows on mobile.