Why React Native Styling Feels Different
React Native styling is inspired by CSS, but it is not CSS running in a browser. Styles are JavaScript objects that are interpreted by the native platform. This has a few practical consequences: you typically use camelCase property names (for example, backgroundColor instead of background-color), you work with numbers for most sizes (interpreted as density-independent pixels), and you don’t have access to the full web CSS feature set (for example, no cascading selectors, no :hover, and no external stylesheets in the same way).
The two pillars you will use most often are:
StyleSheet: a helper API to organize and validate style objects, and to keep your styling consistent and maintainable.- Flexbox layout: the default layout system in React Native. Almost every screen is built by combining containers and children using flex properties.
Using StyleSheet: Structure, Reuse, and Maintainability
Inline styles vs StyleSheet styles
You can apply styles inline directly on a component, but this becomes hard to read and hard to reuse as your UI grows. Inline styles are useful for quick experiments or for small, truly one-off tweaks.
// Inline style (works, but can get messy quickly)
<View style={{ padding: 16, backgroundColor: '#fff' }} />With StyleSheet.create, you define a named set of styles once and reference them by key. This keeps your render code cleaner and encourages reuse.
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
card: {
padding: 16,
backgroundColor: '#fff',
},
});
export function Example() {
return <View style={styles.card} />;
}What StyleSheet.create gives you
StyleSheet.create does not magically change what styles can do, but it provides practical benefits:
- Listen to the audio with the screen off.
- Earn a certificate upon completion.
- Over 5000 courses for you to explore!
Download the app
- Organization: styles live in one place, with meaningful names.
- Validation and consistency: it helps catch typos in style keys during development.
- Performance-friendly patterns: you avoid recreating new style objects on every render when you keep styles outside the component.
A good baseline pattern is to define styles outside your component and keep them stable. When you need dynamic styling, combine static styles with conditional overrides.
Combining styles (arrays) and overriding
React Native allows style to be an array. Later entries override earlier ones. This is the main technique for conditional styling without duplicating entire style objects.
import { StyleSheet, Text } from 'react-native';
const styles = StyleSheet.create({
label: {
fontSize: 16,
color: '#222',
},
labelMuted: {
color: '#777',
},
labelError: {
color: '#b00020',
},
});
export function StatusLabel({ muted, error, children }) {
return (
<Text
style={[
styles.label,
muted && styles.labelMuted,
error && styles.labelError,
]}
>
{children}
</Text>
);
}Notice the common pattern: condition && styles.someStyle. When the condition is false, React Native ignores the false value in the array.
Dynamic values: when to use inline objects
Sometimes a style depends on a runtime value (for example, a progress bar width). In those cases, it’s fine to use a small inline object combined with your base StyleSheet style.
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
barTrack: {
height: 10,
borderRadius: 5,
backgroundColor: '#eee',
overflow: 'hidden',
},
barFill: {
height: '100%',
backgroundColor: '#4a90e2',
},
});
export function ProgressBar({ progress }) {
const clamped = Math.max(0, Math.min(1, progress));
return (
<View style={styles.barTrack}>
<View style={[styles.barFill, { width: `${clamped * 100}%` }]} />
</View>
);
}Here, the base styling stays in StyleSheet, while the dynamic width is computed inline.
Common style properties you’ll use frequently
While React Native supports many style properties, a practical core set covers most UI work:
- Spacing:
margin,padding, and directional variants likemarginTop,paddingHorizontal. - Size:
width,height,minWidth,maxWidth. - Typography (Text):
fontSize,fontWeight,lineHeight,color. - Background and borders:
backgroundColor,borderWidth,borderColor,borderRadius. - Shadows: platform-specific (iOS uses
shadowColor,shadowOpacity,shadowRadius,shadowOffset; Android mainly useselevation).
Spacing and layout are usually the first place to look when something “doesn’t align.” Flexbox is the second.
Flexbox Fundamentals in React Native
Flexbox is the default layout model for React Native. Every View is a flex container by default, and its children are flex items. The key idea is that you describe how items should be arranged along a main axis and a cross axis, and the layout engine calculates positions and sizes.
Main axis vs cross axis
The main axis is controlled by flexDirection:
flexDirection: 'column'(default): main axis is vertical (top to bottom), cross axis is horizontal.flexDirection: 'row': main axis is horizontal (left to right), cross axis is vertical.
Many layout confusions come from forgetting that the meaning of “justify” and “align” depends on the current axis.
justifyContent and alignItems
Two of the most important container properties are:
justifyContent: positions children along the main axis.alignItems: positions children along the cross axis.
Example: a horizontal row with items centered vertically and spaced out horizontally.
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
},
box: {
width: 40,
height: 40,
backgroundColor: '#4a90e2',
},
});
export function RowExample() {
return (
<View style={styles.row}>
<View style={styles.box} />
<View style={styles.box} />
<View style={styles.box} />
</View>
);
}If you change flexDirection to 'column', justifyContent will now control vertical spacing, and alignItems will control horizontal alignment.
flex: sizing and space distribution
The flex property is the most common way to make components expand to fill available space. Think of it as a “share” of remaining space along the main axis.
flex: 1means “take up remaining space.”flex: 2means “take twice as much remaining space as a sibling withflex: 1.”
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
},
left: {
flex: 1,
backgroundColor: '#f2f2f2',
},
right: {
flex: 2,
backgroundColor: '#d9eaff',
},
});
export function SplitPane() {
return (
<View style={styles.container}>
<View style={styles.left} />
<View style={styles.right} />
</View>
);
}This creates a 1/3 and 2/3 split across the width of the screen (because the main axis is horizontal).
alignSelf: overriding alignment for one item
Sometimes one child needs a different cross-axis alignment than the rest. Use alignSelf on the child.
import { StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
gap: 8,
},
badge: {
paddingHorizontal: 10,
paddingVertical: 6,
backgroundColor: '#222',
borderRadius: 999,
},
badgeText: {
color: '#fff',
fontSize: 12,
},
tall: {
height: 60,
backgroundColor: '#eee',
flex: 1,
},
topAligned: {
alignSelf: 'flex-start',
},
});
export function AlignSelfExample() {
return (
<View style={styles.container}>
<View style={[styles.badge, styles.topAligned]}>
<Text style={styles.badgeText}>NEW</Text>
</View>
<View style={styles.tall} />
</View>
);
}The container keeps alignItems: 'center', but the badge opts into flex-start alignment.
gap vs margins
Newer React Native versions support gap for flex layouts, which is often cleaner than adding margins to children. If you need compatibility with older versions, use margins (for example, marginRight on all but the last item). When using margins, be careful not to accidentally add extra spacing at the edges of the container.
Step-by-Step: Build a Card Layout with Flexbox
This walkthrough builds a common UI pattern: a “profile card” with an avatar, text content, and a right-aligned action area. The goal is to practice: row layout, alignment, spacing, and flexible text.
Step 1: Create the basic structure
Start with a container View and three sections: left (avatar), middle (text), right (action).
import { StyleSheet, Text, View } from 'react-native';
export function ProfileCard() {
return (
<View style={styles.card}>
<View style={styles.avatar} />
<View style={styles.content}>
<Text style={styles.name}>Alex Johnson</Text>
<Text style={styles.subtitle}>Mobile Developer</Text>
</View>
<View style={styles.action}>
<Text style={styles.actionText}>...</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
card: {},
avatar: {},
content: {},
name: {},
subtitle: {},
action: {},
actionText: {},
});Step 2: Make the card a row and align items
We want avatar, content, and action in a horizontal line, vertically centered.
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#eee',
},
avatar: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#d9eaff',
},
content: {},
name: {},
subtitle: {},
action: {},
actionText: {},
});Step 3: Add spacing and let the content expand
The middle section should take remaining space, pushing the action to the far right. This is a classic use of flex: 1.
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#eee',
gap: 12,
},
avatar: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#d9eaff',
},
content: {
flex: 1,
},
action: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#f2f2f2',
alignItems: 'center',
justifyContent: 'center',
},
actionText: {
fontSize: 18,
color: '#333',
},
name: {},
subtitle: {},
});Because content has flex: 1, it expands and the action stays at the end of the row.
Step 4: Style the text and handle long names
Real data can be long. In a row layout, long text can push other elements off-screen unless you constrain it. A common approach is to allow the text to truncate with numberOfLines and to ensure the content area can shrink when needed.
import { StyleSheet, Text, View } from 'react-native';
export function ProfileCard() {
return (
<View style={styles.card}>
<View style={styles.avatar} />
<View style={styles.content}>
<Text style={styles.name} numberOfLines={1}>
Alexandra Johnson-Smith the Third
</Text>
<Text style={styles.subtitle} numberOfLines={1}>
Mobile Developer
</Text>
</View>
<View style={styles.action}>
<Text style={styles.actionText}>...</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
borderWidth: 1,
borderColor: '#eee',
gap: 12,
},
avatar: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#d9eaff',
},
content: {
flex: 1,
minWidth: 0,
},
name: {
fontSize: 16,
fontWeight: '600',
color: '#111',
},
subtitle: {
marginTop: 2,
fontSize: 13,
color: '#666',
},
action: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#f2f2f2',
alignItems: 'center',
justifyContent: 'center',
},
actionText: {
fontSize: 18,
color: '#333',
},
});The minWidth: 0 on the flex child is a practical fix that helps text truncation behave correctly in row layouts. Without it, some layouts may refuse to shrink the content area, causing overflow.
Flexbox Wrapping, Rows, and Grids
Flexbox can also create simple grids by allowing items to wrap. This is useful for tag chips, photo thumbnails, or button groups.
Using flexWrap
Set flexDirection: 'row' and flexWrap: 'wrap' on the container. Then give children a fixed width or a percentage-based width.
import { StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
wrap: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
padding: 16,
},
chip: {
paddingHorizontal: 12,
paddingVertical: 8,
backgroundColor: '#f2f2f2',
borderRadius: 999,
},
chipText: {
fontSize: 13,
color: '#333',
},
});
export function ChipWrap() {
const tags = ['React Native', 'Flexbox', 'StyleSheet', 'UI', 'Layout', 'Design'];
return (
<View style={styles.wrap}>
{tags.map((t) => (
<View key={t} style={styles.chip}>
<Text style={styles.chipText}>{t}</Text>
</View>
))}
</View>
);
}When the row runs out of horizontal space, items wrap to the next line. If you need equal-width columns, you can use percentage widths (for example, width: '48%') and rely on justifyContent: 'space-between' or gap to manage spacing.
Positioning: Relative, Absolute, and Overlays
Most layouts should be built with flexbox, but overlays (badges, floating buttons, corner icons) often use absolute positioning.
Absolute positioning basics
By default, elements are positioned relatively in the normal layout flow. With position: 'absolute', you remove the element from the flow and place it using top, right, bottom, and left.
import { StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
photo: {
width: 160,
height: 120,
borderRadius: 12,
backgroundColor: '#d9eaff',
position: 'relative',
overflow: 'hidden',
},
badge: {
position: 'absolute',
top: 8,
right: 8,
backgroundColor: '#111',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 999,
},
badgeText: {
color: '#fff',
fontSize: 12,
fontWeight: '600',
},
});
export function PhotoWithBadge() {
return (
<View style={styles.photo}>
<View style={styles.badge}>
<Text style={styles.badgeText}>LIVE</Text>
</View>
</View>
);
}Setting position: 'relative' on the parent is often a good habit to make it clear the absolute child is anchored to that container.
Platform Notes: Shadows and Rounded Corners
Some styling details behave differently across iOS and Android. Two common examples are shadows and clipping.
Shadows
On iOS, you typically use shadow properties. On Android, you usually use elevation. A cross-platform “card” style often combines both.
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
// iOS shadow
shadowColor: '#000',
shadowOpacity: 0.08,
shadowRadius: 12,
shadowOffset: { width: 0, height: 6 },
// Android shadow
elevation: 3,
},
});Overflow and borderRadius
If you want children to be clipped to rounded corners, you often need overflow: 'hidden' on the rounded container. Be aware that clipping can interact with shadows (clipping may hide the shadow), so you may need a wrapper: an outer view for shadow and an inner view for clipping.
import { StyleSheet, View } from 'react-native';
const styles = StyleSheet.create({
shadowWrapper: {
borderRadius: 12,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOpacity: 0.08,
shadowRadius: 12,
shadowOffset: { width: 0, height: 6 },
elevation: 3,
},
clippedInner: {
borderRadius: 12,
overflow: 'hidden',
},
});
export function ShadowAndClip({ children }) {
return (
<View style={styles.shadowWrapper}>
<View style={styles.clippedInner}>{children}</View>
</View>
);
}Practical Debugging: Finding Layout Problems Fast
When a layout doesn’t look right, you can debug systematically instead of guessing.
Technique 1: Add temporary borders
Add a visible border to the container and children to see their real boundaries.
const debug = {
borderWidth: 1,
borderColor: 'red',
};
// Usage:
// <View style={[styles.card, debug]}>
// <View style={[styles.avatar, debug]} />This quickly reveals whether spacing issues come from padding, margins, or unexpected sizing.
Technique 2: Check the axis first
Before adjusting justifyContent or alignItems, confirm the container’s flexDirection. Many “why won’t it center?” issues are simply using the wrong property for the current axis.
Technique 3: Verify which element should flex
If something should push another element away, the pushing element usually needs flex: 1. In a header row, for example, the title area often gets flex: 1 so the right-side icons remain visible.
Style Organization Patterns That Scale
Group styles by component responsibility
Keep styles close to the component they style, but avoid mixing unrelated styles in a single file. A common approach is one styles object per component file. If you have shared primitives (buttons, text variants, spacing), extract them into a shared module.
Create small “tokens” for consistency
Even without a full design system, you can reduce inconsistency by defining shared constants for spacing and colors. This makes it easier to adjust the look later.
// tokens.js
export const colors = {
text: '#111',
muted: '#666',
border: '#eee',
surface: '#fff',
primary: '#4a90e2',
};
export const spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
};// usage in a component
import { StyleSheet } from 'react-native';
import { colors, spacing } from './tokens';
const styles = StyleSheet.create({
card: {
padding: spacing.md,
backgroundColor: colors.surface,
borderColor: colors.border,
borderWidth: 1,
borderRadius: 12,
},
title: {
color: colors.text,
fontSize: 16,
fontWeight: '600',
},
});Avoid “magic numbers” by tying them to layout intent
Instead of random values, choose spacing that matches a small scale (4, 8, 16, 24). For alignment, prefer flex properties over manual offsets. If you find yourself using many top/left nudges, it’s often a sign the flex layout needs adjustment.