Styling Basics
10 examples to get you started with Styling & Layout - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Styling & Layout - 7 basic and 3 intermediate.
React Native styling runs in a native layout engine, not a browser. The fastest way to try these snippets is an Expo project with the default TypeScript template.
npx create-expo-app@latest MyApp
cd MyApp
npx expo startReplace App.tsx with any example below. react-native-safe-area-context ships with Expo - wrap your root in SafeAreaProvider (the default template already does).
Tooling: These examples target Expo SDK 57, React Native 0.86, and React 19.2.3. Dimensions are in density-independent pixels (dp), not
remorem.
Define reusable style objects with StyleSheet.create instead of scattering literals through JSX.
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.card}>
<Text style={styles.title}>StyleSheet.create</Text>
<Text style={styles.body}>
Named keys keep styles organized and validated at dev time.
</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
margin: 16,
padding: 20,
backgroundColor: "#f0f4ff",
borderRadius: 12,
},
title: { fontSize: 18, fontWeight: "600", marginBottom: 8 },
body: { fontSize: 14, lineHeight: 20, color: "#374151" },
});backgroundColor, borderRadius) - hyphenated CSS names are invalidStyleSheet.create validates keys in development and registers styles once so the native side receives stable IDsvh/vw unitsstyles at module scope so they are not recreated on every renderRelated: Style Performance - why cached styles matter on mobile | Styling Best Practices - naming and file organization
Inline style objects work for one-offs, but a new object reference is created every render unless you memoize it.
import { View, Text, StyleSheet } from "react-native";
const PADDING = 16;
export default function App() {
const isHighlighted = true;
return (
<View style={styles.container}>
<View style={styles.box}>
<Text>Cached via StyleSheet</Text>
</View>
<View style={{ padding: PADDING, backgroundColor: "#fde68a" }}>
<Text>Inline literal - fine for rare one-offs</Text>
</View>
<View
style={[
styles.box,
isHighlighted && { borderWidth: 2, borderColor: "#2563eb" },
]}
>
<Text>Cached base + small inline override</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, gap: 12 },
box: { padding: 16, backgroundColor: "#bbf7d0", borderRadius: 8 },
});style={{ padding: 16 }} allocates a new object each render - harmless for a single static prop, costly when passed deep into listsStyleSheet.create for anything reused or composed across componentsuseMemo are acceptable - profile before optimizingPADDING at module scope avoid magic numbers without triggering re-creationRelated: Style Performance -
useMemofor dynamic styles and list pitfalls
Every View is a flex container. The default flexDirection is column, not row like many web layouts assume.
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.screen}>
<View style={styles.header}>
<Text style={styles.label}>Header (stacked first)</Text>
</View>
<View style={styles.main}>
<Text style={styles.label}>Main content fills middle</Text>
</View>
<View style={styles.footer}>
<Text style={styles.label}>Footer (stacked last)</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16 },
header: { padding: 12, backgroundColor: "#dbeafe", borderRadius: 8 },
main: { flex: 1, justifyContent: "center", alignItems: "center", marginVertical: 12 },
footer: { padding: 12, backgroundColor: "#f3f4f6", borderRadius: 8 },
label: { fontWeight: "600" },
});flex: 1 on main makes it consume remaining vertical space between header and footerjustifyContent aligns along the main axis (vertical here); alignItems along the cross axis (horizontal)flexDirection: "row" when you need side-by-side siblings - covered in depth in the flexbox guideRelated: Flexbox Deep Dive - direction, flex grow/shrink, and common recipes
Spacing in React Native uses the same box model as CSS, with shorthand and per-edge props - all numbers are dp.
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.screen}>
<View style={styles.card}>
<Text style={styles.title}>Spacing primitives</Text>
<View style={styles.row}>
<View style={styles.chip}>
<Text>A</Text>
</View>
<View style={styles.chip}>
<Text>B</Text>
</View>
<View style={styles.chip}>
<Text>C</Text>
</View>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, backgroundColor: "#f9fafb" },
card: {
padding: 20,
marginBottom: 16,
backgroundColor: "#fff",
borderRadius: 12,
},
title: { marginBottom: 12, fontWeight: "600" },
row: { flexDirection: "row", gap: 8 },
chip: {
paddingHorizontal: 12,
paddingVertical: 8,
backgroundColor: "#e0e7ff",
borderRadius: 16,
},
});padding affects inside the element; margin pushes away from siblings and parent edgespaddingHorizontal / paddingVertical (or marginTop, etc.) when edges need different valuesgap (RN 0.71+) adds space between flex children without margin on every child - prefer it in rows and gridsRelated: Typography Scale & Font Loading - line height and readable text spacing | Styling Best Practices - spacing scales and tokens
Pass an array to style to layer base, state, and override styles - later entries win on conflicting keys.
import { useState } from "react";
import { View, Text, Pressable, StyleSheet } from "react-native";
export default function App() {
const [active, setActive] = useState(false);
return (
<View style={styles.container}>
<Pressable onPress={() => setActive((v) => !v)}>
<View style={[styles.tab, active && styles.tabActive]}>
<Text style={[styles.tabText, active && styles.tabTextActive]}>
{active ? "Active" : "Inactive"}
</Text>
</View>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
tab: {
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 20,
backgroundColor: "#e5e7eb",
},
tabActive: { backgroundColor: "#2563eb" },
tabText: { color: "#374151", fontWeight: "500" },
tabTextActive: { color: "#fff" },
});style={[styles.base, condition && styles.modifier]} is the standard pattern for state-driven UIfalse, undefined) are ignored - no need to filter the array manually[styles.a, styles.b] means b overrides a on duplicate keysPressable also accepts a style function ({ pressed }) => [...] for touch feedback - pair with arrays for complex statesRelated: Flexbox Deep Dive - aligning composed layouts | Dark Mode & Color Schemes - theme-aware style arrays
Read the user's system appearance preference and swap background and text colors without a separate theme library.
import { View, Text, useColorScheme, StyleSheet } from "react-native";
const palette = {
light: { bg: "#ffffff", text: "#111827", card: "#f3f4f6" },
dark: { bg: "#111827", text: "#f9fafb", card: "#1f2937" },
};
export default function App() {
const scheme = useColorScheme() ?? "light";
const colors = palette[scheme];
return (
<View style={[styles.screen, { backgroundColor: colors.bg }]}>
<View style={[styles.card, { backgroundColor: colors.card }]}>
<Text style={[styles.title, { color: colors.text }]}>
System scheme: {scheme}
</Text>
<Text style={{ color: colors.text, opacity: 0.7 }}>
Toggle light/dark in device settings to see this update.
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, justifyContent: "center" },
card: { padding: 20, borderRadius: 12 },
title: { fontSize: 18, fontWeight: "600", marginBottom: 8 },
});useColorScheme() returns "light", "dark", or null - default to "light" when null (some Android configs)palette object is enough to start; extract tokens to a shared module as the app grows{ color: colors.text }) are fine here because the scheme changes rarelyRelated: Dark Mode & Color Schemes - dynamic palettes,
Appearance, and system sync
Keep screen content clear of notches, status bars, and home indicators by wrapping the root in SafeAreaView.
import { Text, StyleSheet } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function App() {
return (
<SafeAreaView style={styles.safe}>
<Text style={styles.title}>Safe Area Layout</Text>
<Text style={styles.body}>
Content respects system insets on iOS and edge-to-edge Android.
</Text>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: { flex: 1, padding: 20, backgroundColor: "#fff" },
title: { fontSize: 22, fontWeight: "bold", marginBottom: 8 },
body: { fontSize: 15, lineHeight: 22, color: "#4b5563" },
});react-native-safe-area-context, not react-native - the core SafeAreaView is iOS-only and limitedSafeAreaProvider - required for inset values to resolveflex: 1 on SafeAreaView lets children fill the inset-adjusted area, not the raw screenuseSafeAreaInsets() when only some edges need padding (e.g., a full-bleed header image)Related: Safe Areas & Notches - per-edge insets and RN 0.86 edge-to-edge Android
iOS and Android render shadows differently - Platform.select keeps one style object with platform branches.
import { View, Text, Platform, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.screen}>
<View style={styles.card}>
<Text style={styles.title}>Platform-aware card</Text>
<Text style={styles.body}>
iOS uses shadow props; Android uses elevation.
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: "center",
padding: 24,
backgroundColor: "#f3f4f6",
},
card: {
backgroundColor: "#fff",
borderRadius: 16,
padding: 20,
...Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.12,
shadowRadius: 8,
},
android: { elevation: 6 },
}),
},
title: { fontSize: 18, fontWeight: "600", marginBottom: 8 },
body: { fontSize: 14, color: "#6b7280", lineHeight: 20 },
});Platform.select returns the matching value for the current OS - spread it into StyleSheet objects cleanlyshadowColor, shadowOffset, shadowOpacity, shadowRadius together; Android uses elevationPlatform.OS works for small JSX branches; prefer Platform.select when only style values differRelated: Shadows, Elevation & Borders - hairlines, overflow, and elevation limits
Derive layout values from the current window width so rotation and tablets reflow without hard-coded pixel widths.
import { View, Text, useWindowDimensions, StyleSheet } from "react-native";
export default function App() {
const { width } = useWindowDimensions();
const columns = width >= 768 ? 3 : 2;
const gap = 12;
const horizontalPadding = 32;
const tileSize =
(width - horizontalPadding - gap * (columns - 1)) / columns;
return (
<View style={styles.container}>
<Text style={styles.heading}>
{columns} columns · {Math.round(width)}px wide
</Text>
<View style={[styles.grid, { gap }]}>
{Array.from({ length: 6 }, (_, i) => (
<View
key={i}
style={[styles.tile, { width: tileSize, height: tileSize }]}
>
<Text>Tile {i + 1}</Text>
</View>
))}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
heading: { fontSize: 16, fontWeight: "600", marginBottom: 16 },
grid: { flexDirection: "row", flexWrap: "wrap" },
tile: {
backgroundColor: "#e0e7ff",
borderRadius: 8,
justifyContent: "center",
alignItems: "center",
},
});useWindowDimensions re-renders on rotation and resize - unlike the one-time Dimensions.get("window") snapshotwidth minus padding and gaps - numeric dimensions avoid percentage surprises in flex gridsTABLET = 768) shared across screens for consistent adaptive behaviorRelated: Responsive & Adaptive Layout - tablets, foldables, and split view
Combine StyleSheet, useColorScheme, and a small token module into a reusable pattern production apps use before reaching for a styling library.
import { View, Text, useColorScheme, StyleSheet } from "react-native";
const spacing = { sm: 8, md: 16, lg: 24 } as const;
const themes = {
light: {
screen: "#f9fafb",
card: "#ffffff",
text: "#111827",
muted: "#6b7280",
border: "#e5e7eb",
},
dark: {
screen: "#111827",
card: "#1f2937",
text: "#f9fafb",
muted: "#9ca3af",
border: "#374151",
},
} as const;
export default function App() {
const scheme = useColorScheme() ?? "light";
const theme = themes[scheme];
return (
<View style={[styles.screen, { backgroundColor: theme.screen }]}>
<View
style={[
styles.card,
{ backgroundColor: theme.card, borderColor: theme.border },
]}
>
<Text style={[styles.label, { color: theme.muted }]}>Account</Text>
<Text style={[styles.value, { color: theme.text }]}>alex@example.com</Text>
<View style={[styles.divider, { backgroundColor: theme.border }]} />
<Text style={[styles.label, { color: theme.muted }]}>Plan</Text>
<Text style={[styles.value, { color: theme.text }]}>Pro · renews Aug 1</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, justifyContent: "center", padding: spacing.md },
card: {
borderRadius: 12,
borderWidth: 1,
padding: spacing.lg,
},
label: { fontSize: 13, marginBottom: spacing.sm },
value: { fontSize: 16, fontWeight: "600", marginBottom: spacing.md },
divider: { height: StyleSheet.hairlineWidth, marginBottom: spacing.md },
});spacing, themes) live beside or above components - static layout in StyleSheet, semantic colors from the theme objectStyleSheet.hairlineWidth is the thinnest visible line on the current device - use it for dividers instead of hard-coding 1as const so keys stay narrow and typos fail at compile timetheme.ts and align with your design system - see best practices before adopting styled-components or NativeWindRelated: Styling Best Practices - token naming and library trade-offs | Dark Mode & Color Schemes - syncing navigation and status bar with theme
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026