Section 2 · Layout and Style
StyleSheet in a World Without the Cascade
18 min3 min readCode playground
Lesson at a glance
Style Trailhead with StyleSheet, style arrays and design tokens, using unitless points and longhand properties in a system with no cascade, selectors or media queries.
- 1Style objects and StyleSheet.create
- 2Numbers without units
- 3Style arrays: order is the only rule
- 4Tokens instead of a cascade
- 5Why no cascade is a feature
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
The first time you style a React Native screen, you reach for things that aren’t there: a class name, a :hover, a media query, font-family on body so everything inherits it. React Native has none of them. What it has instead is smaller and more predictable: plain objects, applied directly to the component that needs them, in an order you control.
Style objects and StyleSheet.create
A style is a JavaScript object with camelCase keys. You can pass it inline, but most code collects styles with StyleSheet.create, which gives you autocompletion, type checking and a single place to read a component’s styles:
import { StyleSheet, Text, View } from 'react-native';
export function HikeCard({ name, distanceKm }: { name: string; distanceKm: number }) {
return (
<View style={styles.card}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.meta}>{distanceKm} km</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#ffffff',
borderRadius: 12,
paddingVertical: 12,
paddingHorizontal: 16,
borderWidth: 1,
borderColor: '#e2e8e4',
},
title: { fontSize: 18, fontWeight: '600', color: '#1d2a22' },
meta: { fontSize: 14, color: '#5c6b62', marginTop: 4 },
});Defining styles below the component is a convention, and a good one: the JSX comes first when you open the file.
Is StyleSheet.create faster than inline objects? Not in any way you’ll measure today. The real costs of inline objects are readability and new object identities on every render, which matter only when you pass styles into memoized children. A sensible default: static styles go in StyleSheet.create, and the few values that depend on props or state go inline or in the style array next to them.
Numbers without units
borderRadius: 12 means 12 density-independent points, not physical pixels. A 12-point corner looks the same size on a cheap Android phone and on an iPhone with three times the pixel density; the platform multiplies for you. Percentages work as strings (width: '50%'), and that’s it: no px, rem, em or vh.
Shorthands that take several values don’t exist either. Instead of margin: '8px 16px' you write marginVertical: 8, marginHorizontal: 16. The same pattern covers padding (paddingTop, paddingHorizontal), borders (borderTopWidth, borderBottomColor) and corners (borderTopLeftRadius). Single-value shorthands like margin: 8 and borderRadius: 12 work as you’d expect.
Modern React Native also supports several properties web developers miss: gap, rowGap and columnGap for spacing children, boxShadow with a CSS-like string, and filter. Older tutorials use separate shadowColor/shadowOffset props for iOS and elevation for Android, which you’ll still see in existing codebases.
Style arrays: order is the only rule
The style prop accepts an array. React Native merges it left to right, later keys win, and falsy entries (false, null, undefined) are skipped. That turns conditional styling into one line:
<View style={[styles.card, isFavorite && styles.favorite, isSelected && styles.selected]} />There’s no specificity to reason about, no !important, and no style arriving from a parent three files away. If a card looks wrong, the reason is in that array.
Use the same rule to make your own components adjustable. Accept a style prop and put it last:
import type { StyleProp, ViewStyle } from 'react-native';
type Props = { name: string; distanceKm: number; style?: StyleProp<ViewStyle> };
export function HikeCard({ name, distanceKm, style }: Props) {
return (
<View style={[styles.card, style]}>
{/* …title and meta as before */}
</View>
);
}
// A caller nudges one instance without forking the component:
<HikeCard name="Ridge Loop" distanceKm={8.4} style={{ marginBottom: 24 }} />StyleProp<ViewStyle> is the type that accepts an object, an array, or a falsy value, exactly like the built-in components.
Tokens instead of a cascade
Without inheritance, consistency comes from two places: components (one HikeCard, one PrimaryButton, one AppText) and a small tokens file they all import.
export const colors = {
bg: '#f6f8f6',
surface: '#ffffff',
text: '#1d2a22',
textMuted: '#5c6b62',
primary: '#2f6f4e',
border: '#e2e8e4',
};
export const space = { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 };
export const radius = { sm: 8, md: 12, lg: 20 };Now the card reads borderRadius: radius.md and paddingHorizontal: space.lg, and a designer’s change lands in one file.
Why no cascade is a feature
On a large web app, the hardest styling bugs come from rules you didn’t write reaching elements you didn’t expect. React Native removes that whole category. A component’s look is its own style plus whatever its caller passes in, and nothing else. It’s more typing at first, and much less debugging later. Teams that want class-like ergonomics add a library such as NativeWind (Tailwind classes compiled to style objects), but underneath it’s still these same objects.
Next you’ll lay those styled boxes out with flexbox, where React Native’s defaults differ from the web in ways that explain most “why is this squashed?” moments.
Key takeaways
- Styles are JavaScript objects with camelCase keys and unitless numbers measured in density-independent points.
- CSS shorthands with several values, such as
margin '8px 16px', don’t exist; usemarginVerticalandmarginHorizontal. - In a style array later entries win and falsy entries are skipped, which makes conditional styles a one-liner.
- Reuse happens through components and a tokens object, not through selectors or a global stylesheet.
- Accept a
styleprop and append it last so callers can adjust a component without forking it.