Styling with StyleSheet
By the end of this lesson you will be able to style any React Native component, combine and switch styles based on state, add shadows that work on both platforms, and support dark mode. You will also understand why React Native styling feels like CSS but behaves a little differently.
Styles are JavaScript objects
There are no CSS files in React Native. Each component takes a style prop, which is a JavaScript object. The property names are the CSS names written in camelCase: background-color becomes backgroundColor, font-size becomes fontSize.
Sizes have no units. You write padding: 16, not padding: "16px". The number means density-independent pixels, so the size looks similar on every phone. Percentages are written as strings, such as width: "50%".
Step 1: style a fee receipt with StyleSheet.create.
import { View, Text, StyleSheet } from "react-native";
export default function Receipt() {
return (
<View style={styles.card}>
<Text style={styles.title}>Fee receipt</Text>
<Text style={styles.amount}>₹12,500</Text>
<Text style={styles.note}>Paid by Rohan Gupta, Class 10B</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
margin: 16,
padding: 20,
borderRadius: 14,
backgroundColor: "#ffffff",
borderWidth: 1,
borderColor: "#dde3ea",
},
title: { fontSize: 14, color: "#5b6b7a", textTransform: "uppercase", letterSpacing: 1 },
amount: { fontSize: 32, fontWeight: "700", marginVertical: 8 },
note: { fontSize: 14, color: "#5b6b7a" },
});What you should see: a white card with a thin grey border. A small grey "FEE RECEIPT" label sits above a large bold amount, followed by a grey line of text.
StyleSheet.create is not required (a plain object works), but it is the standard habit. It keeps styles out of your render code, gives you editor autocomplete, and lets the tools warn you about invalid properties.
How React Native styles differ from CSS
It helps to think of CSS as a family rule ("everyone in this house wears blue") and React Native styles as a uniform handed to each person individually.
| CSS on the web | React Native |
|---|---|
Selectors like .card p | No selectors; each component gets its own style |
| Styles cascade to children | Only nested Text inherits text styles |
16px, 1rem, 2em | Plain numbers, or percentage strings |
display: block by default | Every View is a flex container by default |
:hover, :active | No pseudo-classes; use state or Pressable's pressed |
| Media queries | useWindowDimensions() in JavaScript |
Useful shortcuts that do exist: marginHorizontal and marginVertical (also for padding), StyleSheet.absoluteFill for a view that covers its parent, and StyleSheet.hairlineWidth for the thinnest possible line on the device.
Combining and switching styles
You can pass an array of styles. React Native merges them from left to right, so later styles win. Falsy values such as false or null are ignored, which makes conditional styles easy.
Step 2: a status badge whose colour depends on the order status.
import { Text, StyleSheet } from "react-native";
export function StatusBadge({ status }) {
return (
<Text
style={[
styles.badge,
status === "delivered" && styles.delivered,
status === "cancelled" && styles.cancelled,
]}
>
{status}
</Text>
);
}
const styles = StyleSheet.create({
badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 999, backgroundColor: "#fff3cd", color: "#7a5b00", alignSelf: "flex-start" },
delivered: { backgroundColor: "#d7f5e3", color: "#1d6b3c" },
cancelled: { backgroundColor: "#fde2e2", color: "#9b1c1c" },
});What you should see: <StatusBadge status="pending" /> is a yellow pill, "delivered" is green and "cancelled" is red. The base style gives the shape; the later style only changes the colours.
Try the merge rule yourself
React Native's merge works like spreading objects one after another. This browser example copies that rule with a tiny flatten helper, so you can see which value wins.
import { useState } from "react";
import { createRoot } from "react-dom/client";
// Same idea as React Native's style arrays: later objects win, falsy items are skipped
function flatten(styles) {
return Object.assign({}, ...styles.filter(Boolean));
}
const base = { padding: 12, borderRadius: 8, background: "#eee", color: "#222" };
const selected = { background: "#087ea4", color: "white" };
const disabled = { opacity: 0.4 };
function Chip({ label, isSelected, isDisabled, onToggle }) {
return (
<button onClick={onToggle} style={flatten([base, isSelected && selected, isDisabled && disabled])}>
{label}
</button>
);
}
function Filters() {
const [veg, setVeg] = useState(false);
return (
<div style={{ display: "flex", gap: 8 }}>
<Chip label="Veg only" isSelected={veg} onToggle={() => setVeg(!veg)} />
<Chip label="Under ₹200" isDisabled />
</div>
);
}
console.log(JSON.stringify(flatten([base, selected])));
createRoot(document.getElementById("root")).render(<Filters />);{"padding":12,"borderRadius":8,"background":"#087ea4","color":"white"}Click "Veg only" and it turns blue. The padding and radius stay, because selected only replaces the colours.
Shadows on both platforms
Shadows were historically different on each platform. iOS used four shadow* properties, and Android used elevation. With the New Architecture, React Native also supports the web-style boxShadow on both platforms.
import { StyleSheet } from "react-native";
const styles = StyleSheet.create({
// Modern: one property, works on iOS and Android (New Architecture)
card: { backgroundColor: "white", borderRadius: 12, boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)" },
// Older code you will still meet
oldCard: {
backgroundColor: "white",
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.15,
shadowRadius: 8,
elevation: 4, // Android
},
});A shadow needs a background colour on the view, or it may not appear as you expect.
Dark mode and screen sizes
useColorScheme() returns "light" or "dark" from the phone's settings. Keep two colour sets and pick one:
import { useColorScheme, useWindowDimensions, View, Text } from "react-native";
const colors = {
light: { bg: "#ffffff", text: "#111111" },
dark: { bg: "#121212", text: "#f2f2f2" },
};
export default function Home() {
const scheme = useColorScheme();
const theme = colors[scheme === "dark" ? "dark" : "light"];
const { width } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={{ flex: 1, backgroundColor: theme.bg, padding: isTablet ? 48 : 16 }}>
<Text style={{ color: theme.text, fontSize: isTablet ? 28 : 20 }}>Welcome, Meera</Text>
</View>
);
}What you should see: switch your phone to dark mode and the screen turns dark with light text. On a tablet, the padding and font size are larger. useWindowDimensions updates when the phone rotates, so this replaces CSS media queries.
Common mistakes
- Adding units.
fontSize: "16px"is invalid. WritefontSize: 16. - Kebab-case names.
"background-color"does nothing. UsebackgroundColor. - Expecting cascading. Styles on a
Viewdo not reach theTextinside it. - Percentages as numbers.
width: 50means 50 points. For half the width, writewidth: "50%". - Hard-coded colours everywhere. Keep colours in one theme object so dark mode is a single switch.
Key points
- Styles are JavaScript objects with camelCase names and unitless numbers.
StyleSheet.createorganises styles and helps tools catch mistakes.- Style arrays merge left to right; falsy items are skipped, which makes conditional styles easy.
- There are no selectors, cascades or media queries; use state, nested
TextanduseWindowDimensions. boxShadowworks on both platforms with the New Architecture; older code usesshadow*andelevation.useColorScheme()gives you the system light or dark setting.
Exercise
Create a PriceTag component that takes price and onSale props. It shows the price in a rounded box. When onSale is true, the box turns red with white text and shows the word "SALE" before the price. Use one StyleSheet and a style array. Then make the box background dark grey in dark mode when it is not on sale.
Show solution
import { Text, StyleSheet, useColorScheme } from "react-native";
export function PriceTag({ price, onSale }) {
const isDark = useColorScheme() === "dark";
return (
<Text style={[styles.tag, isDark && styles.dark, onSale && styles.sale]}>
{onSale ? "SALE " : ""}₹{price}
</Text>
);
}
const styles = StyleSheet.create({
tag: { alignSelf: "flex-start", paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: "#eeeeee", color: "#111111", fontWeight: "600" },
dark: { backgroundColor: "#333333", color: "#f2f2f2" },
sale: { backgroundColor: "#c62828", color: "#ffffff" },
});The sale style comes last in the array, so it wins over both the light and dark colours.
Quick Quiz
1. How do you write a 16-pixel font size in React Native?
Sizes are unitless numbers in density-independent pixels, and names are camelCase.
2. In style={[styles.a, styles.b]}, both set color. Which one is used?
Style arrays merge from left to right, so later styles win.
3. What replaces CSS media queries in React Native?
You check the window width in JavaScript and choose styles accordingly.
4. Which hook tells you whether the phone is in dark mode?
useColorScheme() returns "light" or "dark" from the system settings.
Learn React Native with a mentor
Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
