Size, Spacing and Borders
Density-independent pixels, padding and margin, percentage sizes, aspectRatio, borderRadius, and how shadows actually work.
{ paddingHorizontal: 16, width: '100%', aspectRatio: 16 / 9, borderRadius: 12 }Every unitless number in a style is a density-independent pixel, written dp. The system multiplies it by the screen's pixel density, so 16dp is physically the same size on a cheap phone and on a flagship. PixelRatio.get() returns that density: 2 on an iPhone SE or 11, 3 on an iPhone X or a Pixel 2, 1.5 on an hdpi Android device.
Spacing
Padding and margin exist, with the usual per-side keys plus paddingHorizontal, paddingVertical and the writing-direction pair paddingStart and paddingEnd. paddingHorizontal: 16 is shorthand for paddingLeft: 16, paddingRight: 16 and nothing more. Two differences from CSS matter: there is no margin collapsing, so adjacent margins add up, and boxSizing defaults to border-box rather than content-box, so padding and border are counted inside width instead of being added to it. React Native does expose a boxSizing style prop, so boxSizing: 'content-box' switches a view to the CSS default when you need it.
Size
width and height take a number of dp or a percentage string measured against the parent. The docs are explicit that only points and percentages work; em, rem, vh and vw do not exist, and neither does calc(). aspectRatio fills in whichever dimension you left undefined, so width: '100%' with aspectRatio: 16 / 9 gives a responsive video box with no height arithmetic.
Borders and shadows
borderRadius has per-corner longhands including logical ones such as borderStartStartRadius. Shadows are the ugly part: iOS reads shadowColor, shadowOffset, shadowOpacity and shadowRadius, while Android reads elevation. There is also a boxShadow prop, but it needs the New Architecture, and the docs limit outset shadows to Android 9 and above.
Example
import { Platform, StyleSheet, Text, View } from 'react-native';
export default function BoxModelDemo() {
return (
<View style={styles.screen}>
<View style={styles.card}>
{/* Height comes from aspectRatio, so no number has to be guessed */}
<View style={styles.cover} />
<Text style={styles.caption}>16:9 cover, 12dp radius</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, backgroundColor: '#f2f2f7' },
card: {
// The box is border-box: this 12dp padding is inside the card's width.
padding: 12,
borderRadius: 16,
backgroundColor: '#ffffff',
// One shadow, two platform spellings. Neither works on the other OS.
...Platform.select({
ios: {
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 }, // an object, not a number
shadowOpacity: 0.15,
shadowRadius: 6,
},
android: { elevation: 3 },
default: {},
}),
},
cover: {
width: '100%', // percentages are measured against the parent
aspectRatio: 16 / 9, // fills in the undefined dimension
borderRadius: 12,
backgroundColor: '#d1d1d6',
},
caption: { marginTop: 8, fontSize: 13, color: '#636366' },
});When to use it
- Give a media thumbnail a 16:9 shape without computing a pixel height.
- Add a card shadow that appears on both iOS and Android.
- Round the corners of a group and clip its children to that radius.
- Replace per-child margins with padding plus gap so spacing stays consistent.
More examples
Longhand padding, logical sides, and a hairline divider
Two settings rows of exactly the same height, separated by a one-pixel line, inside a rounded white group. The two row styles are written both ways to show that the shorthand expands to four keys and nothing else. overflow: 'hidden' on the group is what makes the children respect its corner radius.
import { StyleSheet, Text, View } from 'react-native';
export default function SettingsRows() {
return (
<View style={styles.group}>
<View style={styles.rowShorthand}>
<Text>Notifications</Text>
</View>
<View style={styles.divider} />
<View style={styles.rowLonghand}>
<Text>Sounds</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
group: { borderRadius: 12, backgroundColor: '#ffffff', overflow: 'hidden' },
// These two rows are identical; the second spells out the first.
rowShorthand: { paddingHorizontal: 16, paddingVertical: 12 },
rowLonghand: {
paddingLeft: 16,
paddingRight: 16,
paddingTop: 12,
paddingBottom: 12,
},
// paddingStart/paddingEnd flip with the writing direction; left/right do not.
divider: { height: StyleSheet.hairlineWidth, backgroundColor: '#c6c6c8' },
});A square avatar grid without a single fixed height
Three squares filling the row with 8dp between them, staying square on a narrow phone and on a tablet. A hardcoded height would be wrong on both. Combining flex: 1 with aspectRatio is the standard way to size tiles, map previews and media thumbnails.
import { StyleSheet, View } from 'react-native';
export default function AvatarGrid() {
return (
<View style={styles.row}>
<View style={styles.cell} />
<View style={styles.cell} />
<View style={styles.cell} />
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', gap: 8, padding: 8 },
cell: {
flex: 1, // each cell takes a third of the row
aspectRatio: 1, // height follows the measured width: a square
borderRadius: 8,
borderWidth: 1,
borderColor: '#c7c7cc',
backgroundColor: '#e5e5ea',
},
});
Discussion