Module 4: StyleSheet Deep Dive

StyleSheet Fundamentals

Master the styling system that makes React Native apps beautiful

Table of Contents

🎯 Learning Objectives

  • Understand why React Native has its own styling system
  • Use StyleSheet.create() to define optimized styles
  • Translate your CSS knowledge to React Native style properties
  • Choose the right units and values for mobile layouts
  • Combine multiple styles effectively with style arrays
  • Avoid common styling mistakes web developers make

Introduction

If you've been building web applications with CSS, you've developed strong intuitions about how styling works. The good news? About 80% of that knowledge transfers directly to React Native. The challenge? That other 20% can trip you up in surprising ways if you're not prepared for it.

In Module 3, you learned about React Native's core components — View, Text, Image, and others. You saw style props sprinkled throughout our examples, but we never took a deep dive into how the styling system actually works. That changes now.

🎨 The Big Picture

React Native's StyleSheet API gives you a CSS-like syntax that compiles down to native platform styles. It's not CSS — it's a JavaScript object that describes how components should look, which then gets translated to iOS UIKit styles or Android View styles.

Think of StyleSheet as a dialect of CSS. If CSS is British English, React Native styles are American English — mostly the same, with some spelling differences and a few words that mean completely different things. Once you learn the differences, you'll move fluently between both.

What We'll Cover

This lesson establishes your styling foundation. We'll explore what StyleSheet actually does under the hood, how to use it effectively, and — critically — where it differs from web CSS. Future lessons in this module will build on these fundamentals to cover Flexbox layouts, responsive design patterns, platform-specific styling, and organizing styles at scale.

💡 Prerequisites Check

This lesson assumes you're comfortable with:

  • CSS fundamentals (selectors, properties, values)
  • React component basics (props, JSX)
  • JavaScript objects and their syntax
  • The core components from Module 3 (View, Text, etc.)

What is StyleSheet?

Let's start with a fundamental question: why does React Native have its own styling system at all? Why not just use CSS?

The answer lies in what React Native actually is. When you write a React Native app, you're not creating a web page that runs in a browser. You're creating a truly native application where your JavaScript code controls real native UI components. iOS buttons, Android text fields, native scroll views — these are the actual building blocks of your app.

⚠️ Key Insight

There's no browser in a React Native app. No DOM, no CSS engine, no media queries. Native platforms have their own styling systems, and React Native needs to bridge the gap between the styles you write and what those platforms understand.

StyleSheet is React Native's answer to this challenge. It's an API that lets you:

  1. Define styles using a familiar, CSS-like syntax
  2. Validate styles at creation time (catching typos early)
  3. Optimize performance by creating style references once
  4. Bridge platforms by translating to native style formats

The StyleSheet Object

StyleSheet is imported from react-native and provides several useful methods and properties:

import { StyleSheet } from 'react-native';

// The main method you'll use constantly
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
  },
});

// Utility for combining styles
StyleSheet.compose(style1, style2);

// Flattens an array of styles into one object
StyleSheet.flatten([style1, style2]);

// A constant representing an absolutely positioned element
// that fills its parent completely
StyleSheet.absoluteFill;

// Same as absoluteFill but as a style object you can spread
StyleSheet.absoluteFillObject;

// Returns a "hairline" width (thinnest possible line on device)
StyleSheet.hairlineWidth;

Of these, StyleSheet.create() is by far the most important. Let's explore it in depth.

How Native Platforms Handle Styles

To truly understand StyleSheet, it helps to know what happens when your styles reach the native side:

flowchart TD
    subgraph JS["JavaScript Thread"]
        A["StyleSheet.create()"] --> B["Style Object with IDs"]
    end
    
    subgraph Bridge["React Native Bridge"]
        B --> C["Serialized Style Data"]
    end
    
    subgraph Native["Native Thread"]
        C --> D{Platform?}
        D -->|iOS| E["UIKit Styles
NSLayoutConstraint
CALayer properties"] D -->|Android| F["Android View Styles
LayoutParams
Paint/Canvas"] end style A fill:#e1f5fe style E fill:#f3e5f5 style F fill:#e8f5e9

When you call StyleSheet.create(), React Native doesn't just store your style objects. In production builds, it can optimize these styles and send references (IDs) across the bridge instead of full style objects every time a component renders. This is a significant performance optimization that we'll discuss more shortly.

StyleSheet.create() Explained

StyleSheet.create() takes an object where each key becomes a named style, and each value is an object of style properties. Think of it like defining CSS classes, but in JavaScript:

import { StyleSheet, View, Text } from 'react-native';

const styles = StyleSheet.create({
  // Like a CSS class called ".container"
  container: {
    flex: 1,
    backgroundColor: '#ffffff',
    padding: 20,
  },
  
  // Like a CSS class called ".title"
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#1a1a1a',
    marginBottom: 12,
  },
  
  // Like a CSS class called ".subtitle"  
  subtitle: {
    fontSize: 16,
    color: '#666666',
    lineHeight: 24,
  },
});

// Usage - reference styles by their keys
function MyComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome</Text>
      <Text style={styles.subtitle}>
        Getting started with StyleSheet
      </Text>
    </View>
  );
}

Why Use StyleSheet.create()?

You might wonder: can't I just pass plain objects to the style prop? Technically, yes:

// This works, but it's not recommended
<View style={{ flex: 1, padding: 20 }}>
  <Text style={{ fontSize: 24, fontWeight: 'bold' }}>
    Hello
  </Text>
</View>

So why bother with StyleSheet.create()? Several important reasons:

✅ Benefits of StyleSheet.create()

  • Validation: Style property names are checked when styles are created. Typos like fontSise trigger warnings immediately.
  • Performance: Styles are created once and referenced by ID. Inline objects are recreated every render.
  • Organization: All component styles live in one place, making them easier to find and modify.
  • Reusability: Named styles can be applied to multiple elements consistently.
  • Debugging: Named styles show up clearly in React DevTools.

Style Validation in Action

One of the most helpful features of StyleSheet.create() is immediate feedback on invalid styles:

// This will show a warning in development
const styles = StyleSheet.create({
  broken: {
    display: 'inline-block',  // ❌ Not supported in RN
    fontSise: 16,             // ❌ Typo - should be fontSize
    margin: '20px',           // ❌ String units not allowed
  },
});

// Correct version
const styles = StyleSheet.create({
  working: {
    display: 'flex',          // ✅ Supported value
    fontSize: 16,             // ✅ Correct spelling
    margin: 20,               // ✅ Number (density-independent pixels)
  },
});

In development mode, React Native checks your styles against known valid properties and values. This catches many common mistakes before they become visual bugs.

Placement Conventions

Where should you put your StyleSheet.create() call? The community convention is at the bottom of your component file, after the component definition:

// MyComponent.tsx

import { StyleSheet, View, Text } from 'react-native';

// Component first
export function MyComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello</Text>
    </View>
  );
}

// Styles at the bottom
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    fontSize: 18,
  },
});

This pattern keeps your component's render logic at the top where it's easy to find, with styling details below. Since JavaScript hoists const declarations within modules, you can reference styles in your component even though it's defined after.

Web CSS vs React Native Styles

This is where your web CSS experience becomes both an asset and a potential source of confusion. Let's systematically compare the two systems so you know exactly what transfers and what doesn't.

Syntax Differences

The most visible difference is the syntax. CSS uses kebab-case property names and various value formats. React Native uses camelCase and JavaScript values:

Web CSS

.card {
  background-color: #ffffff;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  padding: 16px;
  margin-bottom: 12px;
  font-size: 14px;
  font-weight: bold;
  text-align: center;
}

React Native

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderRadius: 8,
    // Shadow is platform-specific!
    padding: 16,
    marginBottom: 12,
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
});

Notice the patterns:

  • background-colorbackgroundColor (camelCase)
  • 16px16 (unitless numbers)
  • bold'bold' (string values in quotes)
  • box-shadow → Different approach entirely (platform-specific)

What Transfers Directly

Many CSS concepts work almost identically in React Native. Here's what you can rely on:

✅ Works the Same (or Nearly So)

Concept CSS React Native
Colors #fff, rgb(), rgba() Same formats work!
Flexbox display: flex Default for all Views
Padding/Margin padding: 16px padding: 16
Border radius border-radius: 8px borderRadius: 8
Positioning position: absolute position: 'absolute'
Opacity opacity: 0.5 opacity: 0.5

What's Different

Now for the gotchas. These differences catch web developers most often:

⚠️ Key Differences

Concept Web CSS React Native
Default display block flex
Flex direction row (default) column (default)
Units px, em, rem, %, vh Numbers (dp) and % only
Shorthand margin: 10px 20px Must use individual props
Inheritance Many properties inherit Almost nothing inherits
Shadows box-shadow Platform-specific APIs

What Doesn't Exist

Some CSS features have no equivalent in React Native because they don't make sense in a native context:

❌ Not Available in React Native

  • CSS Selectors: No .class, #id, :hover, ::before
  • Media Queries: No @media (we have other solutions)
  • CSS Grid: Layout is Flexbox-only
  • CSS Variables: No var(--custom) (use JS constants)
  • Cascading: No stylesheet cascade or specificity
  • Animation: No @keyframes (use Animated/Reanimated)
  • Pseudo-elements: No ::before, ::after
  • Float: No float: left/right
  • Display types: No inline, block, inline-block, grid

Don't panic! Each of these missing features has a React Native alternative. We'll cover responsive design without media queries in Lesson 4 of this module, and animations are covered extensively in Module 9.

Mental Model Shift

The key mental shift is this: in CSS, you're describing how elements should look, and the browser's CSS engine handles the cascade, inheritance, and specificity rules. In React Native, you're directly telling each component exactly how to style itself. There's no cascade, no inheritance to rely on, no "styles flowing down."

flowchart TB
    subgraph CSS["Web CSS Model"]
        direction TB
        S1["Global Stylesheet"] --> C1["Cascade Rules"]
        C1 --> S2["Specificity Calculation"]
        S2 --> I1["Inheritance"]
        I1 --> F1["Final Computed Style"]
    end
    
    subgraph RN["React Native Model"]
        direction TB
        ST["StyleSheet.create()"] --> D["Direct Application"]
        D --> COMP["Component receives
exact style object"] end style CSS fill:#e3f2fd style RN fill:#e8f5e9

This directness is actually a feature, not a bug. It makes styles predictable — what you write is exactly what you get, with no surprises from inherited styles or specificity battles.

Core Style Properties

React Native supports a subset of CSS properties, organized into categories based on what they affect. Let's survey the most important ones you'll use daily.

Layout Properties

These control how components are sized and positioned. We'll dive deep into Flexbox in the next lesson, but here's an overview:

const layoutStyles = StyleSheet.create({
  container: {
    // Flexbox (covered in detail next lesson)
    flex: 1,
    flexDirection: 'column',    // 'row', 'column', 'row-reverse', 'column-reverse'
    justifyContent: 'center',   // main axis alignment
    alignItems: 'center',       // cross axis alignment
    flexWrap: 'wrap',           // 'nowrap', 'wrap', 'wrap-reverse'
    gap: 10,                    // spacing between flex children
    
    // Sizing
    width: 200,
    height: 100,
    minWidth: 50,
    maxWidth: 300,
    minHeight: 50,
    maxHeight: 400,
    
    // Aspect ratio (very useful for images/videos!)
    aspectRatio: 16 / 9,
    
    // Positioning
    position: 'relative',       // 'relative' or 'absolute'
    top: 10,
    right: 10,
    bottom: 10,
    left: 10,
    zIndex: 100,
  },
});

Spacing Properties

Padding and margin work like CSS, but without shorthand:

const spacingStyles = StyleSheet.create({
  card: {
    // Individual sides
    paddingTop: 20,
    paddingRight: 16,
    paddingBottom: 20,
    paddingLeft: 16,
    
    // Axis shortcuts (these DO exist!)
    paddingHorizontal: 16,  // = paddingLeft + paddingRight
    paddingVertical: 20,    // = paddingTop + paddingBottom
    
    // All sides at once
    padding: 16,            // applies to all four sides
    
    // Same pattern for margin
    margin: 10,
    marginHorizontal: 20,
    marginVertical: 10,
    marginTop: 8,
    // ... etc
  },
});

💡 RN-Specific Shortcuts

paddingHorizontal, paddingVertical, marginHorizontal, and marginVertical are React Native additions that don't exist in CSS. They're incredibly handy shortcuts!

Visual Properties

Colors, backgrounds, borders, and visual effects:

const visualStyles = StyleSheet.create({
  card: {
    // Background
    backgroundColor: '#ffffff',
    
    // Borders (no shorthand!)
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderStyle: 'solid',      // 'solid', 'dotted', 'dashed'
    
    // Individual border sides
    borderTopWidth: 2,
    borderTopColor: '#2196F3',
    
    // Border radius
    borderRadius: 8,
    borderTopLeftRadius: 16,
    borderTopRightRadius: 16,
    borderBottomLeftRadius: 0,
    borderBottomRightRadius: 0,
    
    // Opacity
    opacity: 0.9,
    
    // Overflow (important for borderRadius to work on children!)
    overflow: 'hidden',        // 'visible', 'hidden', 'scroll'
  },
});

Typography Properties

These only apply to Text components (remember from Module 3 — all text must be in <Text>):

const textStyles = StyleSheet.create({
  heading: {
    // Font
    fontSize: 24,
    fontWeight: 'bold',        // '100'-'900', 'normal', 'bold'
    fontStyle: 'italic',       // 'normal', 'italic'
    fontFamily: 'System',      // Platform font or custom font name
    
    // Color
    color: '#1a1a1a',
    
    // Alignment
    textAlign: 'center',       // 'left', 'center', 'right', 'justify'
    textAlignVertical: 'center', // Android only: 'auto', 'top', 'center', 'bottom'
    
    // Decoration
    textDecorationLine: 'underline', // 'none', 'underline', 'line-through', 'underline line-through'
    textDecorationStyle: 'solid',    // 'solid', 'double', 'dotted', 'dashed'
    textDecorationColor: '#2196F3',
    
    // Spacing
    letterSpacing: 1.5,
    lineHeight: 32,
    
    // Transform
    textTransform: 'uppercase', // 'none', 'uppercase', 'lowercase', 'capitalize'
    
    // Shadow (text shadow, not box shadow)
    textShadowColor: 'rgba(0, 0, 0, 0.25)',
    textShadowOffset: { width: 1, height: 1 },
    textShadowRadius: 2,
  },
});

Shadow Properties (Platform Differences)

This is one area where iOS and Android diverge significantly:

const shadowStyles = StyleSheet.create({
  cardWithShadow: {
    backgroundColor: '#ffffff', // Required for shadows to show!
    
    // iOS Shadow (these only work on iOS)
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    
    // Android Shadow (this only works on Android)
    elevation: 4,
  },
});

⚠️ Shadow Reality Check

You'll often need to specify both iOS shadow properties AND Android elevation to get consistent shadows across platforms. We'll cover platform-specific styling patterns in Lesson 5 of this module.

Image-Specific Properties

When styling Image components (remember our Image lesson in Module 3?):

const imageStyles = StyleSheet.create({
  photo: {
    width: 200,
    height: 200,
    
    // How the image fills its container
    resizeMode: 'cover',  // 'cover', 'contain', 'stretch', 'center', 'repeat'
    
    // Tint (colorize the image)
    tintColor: '#2196F3', // Useful for icons!
    
    // Border radius works on images
    borderRadius: 100,    // Makes circular images
  },
});

Units and Values

One of the biggest adjustments for web developers is React Native's approach to units. Let's break it down completely.

No px, em, rem, vh, vw

In CSS, you have many unit options. In React Native, you have essentially two: unitless numbers and percentages.

// ❌ This won't work - string units are invalid
const wrongStyles = {
  width: '200px',      // Error!
  fontSize: '1.5rem',  // Error!
  height: '100vh',     // Error!
};

// ✅ This is correct
const rightStyles = StyleSheet.create({
  container: {
    width: 200,        // Density-independent pixels
    fontSize: 24,      // Points
    height: '100%',    // Percentage (as string)
  },
});

Density-Independent Pixels (dp)

When you write width: 200 in React Native, you're specifying 200 density-independent pixels (also called "points" on iOS or "dp" on Android). This is a crucial concept:

📖 Density-Independent Pixels

A density-independent pixel is a virtual unit that scales based on screen density. On a standard 1x display, 1dp = 1 physical pixel. On a 2x Retina display, 1dp = 2 physical pixels. This means width: 100 looks the same physical size across all devices, regardless of their pixel density.

This automatic scaling is why you don't need px — React Native handles the conversion to actual pixels based on device density.

1x Display 100dp = 100px 100 physical pixels 2x Retina 100dp = 200px 200 physical pixels 3x Display 100dp = 300px 300 physical pixels Same visual size on all devices! React Native handles the pixel density conversion automatically

Percentages

Percentages work for sizing, but they must be strings:

const percentStyles = StyleSheet.create({
  halfWidth: {
    width: '50%',      // 50% of parent width
    height: '100%',    // Full parent height
  },
  
  positioned: {
    position: 'absolute',
    top: '10%',        // 10% from top of parent
    left: '5%',        // 5% from left of parent
  },
});

Percentages are relative to the parent container, not the screen. This differs from CSS's vh and vw units, which are relative to the viewport.

Getting Screen Dimensions

Since there's no vh or vw, how do you size things relative to the screen? Use the Dimensions API or the useWindowDimensions hook:

import { 
  StyleSheet, 
  Dimensions, 
  useWindowDimensions,
  View 
} from 'react-native';

// Method 1: Dimensions API (static, doesn't update on rotation)
const screenWidth = Dimensions.get('window').width;
const screenHeight = Dimensions.get('window').height;

const staticStyles = StyleSheet.create({
  halfScreen: {
    width: screenWidth / 2,
    height: screenHeight * 0.3,  // 30vh equivalent
  },
});

// Method 2: useWindowDimensions hook (reactive, updates on rotation)
function ResponsiveComponent() {
  const { width, height } = useWindowDimensions();
  
  return (
    <View 
      style={{ 
        width: width * 0.8,   // 80vw equivalent
        height: height * 0.5  // 50vh equivalent
      }} 
    />
  );
}

✅ Best Practice

Prefer useWindowDimensions() over Dimensions.get() in components. The hook automatically updates when the screen size changes (like device rotation), while the API call returns a static value.

Font Sizes

Font sizes are also unitless numbers, representing points:

const textStyles = StyleSheet.create({
  small: { fontSize: 12 },    // 12pt
  body: { fontSize: 16 },     // 16pt - good default
  large: { fontSize: 20 },    // 20pt
  title: { fontSize: 28 },    // 28pt
  hero: { fontSize: 48 },     // 48pt
});

Unlike web where you might use rem for scalable typography, React Native fonts are fixed sizes. For accessible, user-scalable text, you'll need to implement scaling yourself or use libraries — we'll cover this in the responsive design lesson.

Inline Styles vs StyleSheet

You have two main options for applying styles in React Native: inline style objects and StyleSheet.create(). Let's understand when to use each.

Inline Styles

Inline styles are JavaScript objects passed directly to the style prop:

// Inline style - object created every render
<View style={{ flex: 1, padding: 16 }}>
  <Text style={{ fontSize: 24, color: '#333' }}>
    Hello World
  </Text>
</View>

StyleSheet Styles

StyleSheet styles are defined once and referenced by name:

// StyleSheet - objects created once, reused
<View style={styles.container}>
  <Text style={styles.title}>Hello World</Text>
</View>

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  title: { fontSize: 24, color: '#333' },
});

When to Use Each

✅ Use StyleSheet.create() For:

  • Static styles that don't change based on props or state
  • Styles reused across multiple elements
  • The majority of your styling needs
  • Better performance and validation

💡 Use Inline Styles For:

  • Dynamic values that depend on props, state, or calculations
  • One-off overrides combined with StyleSheet styles
  • Styles computed from useWindowDimensions()
  • Animated values (which must be inline)

Real-World Pattern: Combining Both

In practice, you'll often combine StyleSheet styles with inline dynamic values:

import { StyleSheet, View, Text, useWindowDimensions } from 'react-native';

interface CardProps {
  title: string;
  isHighlighted: boolean;
  customColor?: string;
}

function Card({ title, isHighlighted, customColor }: CardProps) {
  const { width } = useWindowDimensions();
  const isWideScreen = width > 600;
  
  return (
    <View 
      style={[
        styles.card,
        // Dynamic: conditional style
        isHighlighted && styles.highlighted,
        // Dynamic: prop-based color
        customColor && { borderColor: customColor },
        // Dynamic: responsive width
        { width: isWideScreen ? 400 : width - 32 },
      ]}
    >
      <Text style={styles.title}>{title}</Text>
    </View>
  );
}

// Static styles in StyleSheet
const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    borderWidth: 2,
    borderColor: '#e0e0e0',
  },
  highlighted: {
    borderColor: '#2196F3',
    backgroundColor: '#e3f2fd',
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1a1a1a',
  },
});

This pattern gives you the best of both worlds: validated, optimized static styles from StyleSheet, plus the flexibility of inline styles for dynamic values.

Combining Multiple Styles

React Native's style prop accepts either a single style object or an array of styles. This is incredibly powerful for composition.

Style Arrays

When you pass an array, styles are merged left-to-right, with later styles overriding earlier ones (just like Object.assign or spread):

// Later styles override earlier ones
<View style={[styles.base, styles.override]}>
  {/* If both define backgroundColor, override wins */}
</View>

const styles = StyleSheet.create({
  base: {
    padding: 16,
    backgroundColor: '#ffffff',
    borderRadius: 8,
  },
  override: {
    backgroundColor: '#e3f2fd',  // This wins!
    borderRadius: 16,            // This wins!
    // padding stays 16 from base
  },
});

Conditional Styles

Arrays shine for conditional styling. Use JavaScript's && or ternary operators:

interface ButtonProps {
  label: string;
  variant: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
  size?: 'small' | 'large';
}

function Button({ label, variant, disabled, size }: ButtonProps) {
  return (
    <Pressable
      style={[
        styles.button,
        styles[variant],                        // Dynamic key lookup
        size === 'small' && styles.small,       // Conditional
        size === 'large' && styles.large,       // Conditional
        disabled && styles.disabled,            // Conditional
      ]}
      disabled={disabled}
    >
      <Text style={[
        styles.label,
        styles[`${variant}Label`],              // Dynamic key
        disabled && styles.disabledLabel,
      ]}>
        {label}
      </Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    alignItems: 'center',
  },
  primary: { backgroundColor: '#2196F3' },
  secondary: { backgroundColor: '#e0e0e0' },
  danger: { backgroundColor: '#f44336' },
  small: { paddingVertical: 8, paddingHorizontal: 16 },
  large: { paddingVertical: 16, paddingHorizontal: 32 },
  disabled: { opacity: 0.5 },
  
  label: { fontSize: 16, fontWeight: '600' },
  primaryLabel: { color: '#ffffff' },
  secondaryLabel: { color: '#333333' },
  dangerLabel: { color: '#ffffff' },
  disabledLabel: { color: '#999999' },
});

⚠️ Falsy Values in Arrays

React Native gracefully handles false, null, and undefined in style arrays — they're simply ignored. This is what makes condition && styles.something work safely.

StyleSheet.compose() and StyleSheet.flatten()

React Native provides two utility methods for combining styles:

import { StyleSheet } from 'react-native';

// compose: Combines two styles (returns a new style reference)
const combined = StyleSheet.compose(styles.base, styles.variant);
// Use: <View style={combined} />

// flatten: Merges an array into a single plain object
const flattened = StyleSheet.flatten([styles.a, styles.b, styles.c]);
// Returns: { ...a, ...b, ...c } as a plain object

// flatten is useful when you need to read computed values:
const finalStyles = StyleSheet.flatten([styles.base, { backgroundColor: 'red' }]);
console.log(finalStyles.backgroundColor); // 'red'

In practice, style arrays [style1, style2] are more commonly used than these utility methods, but flatten() is handy when you need to inspect the final computed style values.

Style Inheritance Rules

This is one of the most surprising differences from web CSS. In React Native, almost no styles inherit from parent to child.

The Web CSS Model

In web CSS, many properties cascade down to descendants:

<!-- Web HTML/CSS -->
<div style="color: blue; font-family: Arial; font-size: 16px;">
  This text is blue, Arial, 16px.
  <span>This span inherits all of that!</span>
  <p>So does this paragraph.</p>
</div>

The React Native Model

In React Native, the same structure behaves very differently:

// React Native - NO inheritance!
<View style={{ /* View styles don't affect Text children */ }}>
  <Text style={styles.parentText}>
    This text has explicit styles.
    <Text>This nested Text inherits NOTHING by default!</Text>
  </Text>
</View>

// You must explicitly style every Text
const styles = StyleSheet.create({
  parentText: {
    color: 'blue',
    fontSize: 16,
    fontFamily: 'System',
  },
});

The One Exception: Nested Text

There is exactly one case where inheritance works: Text nested directly inside Text:

// Text nested in Text DOES inherit text styles
<Text style={styles.paragraph}>
  This is normal text.
  <Text style={styles.bold}> This inherits paragraph styles AND adds bold.</Text>
  <Text style={styles.link}> This inherits AND changes color.</Text>
</Text>

const styles = StyleSheet.create({
  paragraph: {
    fontSize: 16,
    color: '#333333',
    lineHeight: 24,
  },
  bold: {
    fontWeight: 'bold',
    // Inherits fontSize, color, lineHeight from parent Text
  },
  link: {
    color: '#2196F3',       // Overrides inherited color
    textDecorationLine: 'underline',
    // Inherits fontSize, lineHeight from parent Text
  },
});
flowchart TD
    subgraph NoInherit["No Inheritance"]
        V["View (styled)"] --> T1["Text"]
        T1 -.->|"❌ No inheritance"| NOTE1["View styles don't
cascade to Text"] end subgraph Inherit["Text-to-Text Inheritance"] T2["Text (parent styles)"] --> T3["Text (child)"] T3 -.->|"✅ Inherits text styles"| NOTE2["fontSize, color, etc.
cascade to nested Text"] end style NoInherit fill:#ffebee style Inherit fill:#e8f5e9

Practical Implication: Default Styles

Since nothing inherits, you'll often create base text styles and apply them everywhere:

// Common pattern: create reusable text style presets
const styles = StyleSheet.create({
  // Base text style - apply to all Text components
  text: {
    fontSize: 16,
    color: '#1a1a1a',
    fontFamily: 'System',
  },
  
  // Variants build on or override base
  heading: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#1a1a1a',
    fontFamily: 'System',
  },
  
  caption: {
    fontSize: 12,
    color: '#666666',
    fontFamily: 'System',
  },
});

// Usage: always include a text style
<Text style={styles.text}>Body text</Text>
<Text style={styles.heading}>Page Title</Text>
<Text style={styles.caption}>Small print</Text>

In Lesson 7, we'll explore patterns for organizing and scaling these base styles across large applications.

Common Pitfalls

Let's look at the mistakes web developers most commonly make when starting with React Native styling.

Pitfall 1: Using String Units

// ❌ WRONG - string units don't work
const badStyles = {
  width: '200px',
  fontSize: '16px',
  margin: '10rem',
};

// ✅ CORRECT - use numbers
const goodStyles = StyleSheet.create({
  container: {
    width: 200,
    fontSize: 16,
    margin: 10,
  },
});

Pitfall 2: Expecting CSS Shorthand

// ❌ WRONG - shorthand doesn't exist
const badStyles = {
  margin: '10 20',              // Nope
  padding: '10px 20px 10px',    // Nope
  border: '1px solid black',    // Nope
  background: 'linear-gradient(...)',  // Nope
};

// ✅ CORRECT - use individual properties
const goodStyles = StyleSheet.create({
  box: {
    marginVertical: 10,
    marginHorizontal: 20,
    // Or individually:
    paddingTop: 10,
    paddingRight: 20,
    paddingBottom: 10,
    paddingLeft: 20,
    // Border must be separate
    borderWidth: 1,
    borderStyle: 'solid',
    borderColor: 'black',
  },
});

Pitfall 3: Forgetting flexDirection Default

// Web CSS: flex items default to row
// React Native: flex items default to COLUMN

// If you want horizontal layout, you MUST specify:
const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',  // Required for horizontal!
  },
});

Pitfall 4: Applying Text Styles to View

// ❌ WRONG - text styles on View don't work
<View style={{ fontSize: 16, color: 'blue' }}>
  <Text>This text won't be blue or 16pt!</Text>
</View>

// ✅ CORRECT - text styles go on Text
<View>
  <Text style={{ fontSize: 16, color: 'blue' }}>
    Now it works!
  </Text>
</View>

Pitfall 5: Expecting borderRadius to Clip Children

// ❌ PROBLEM - children overflow rounded corners
<View style={{ borderRadius: 20 }}>
  <Image source={...} style={{ width: '100%', height: 200 }} />
  {/* Image corners stick out! */}
</View>

// ✅ SOLUTION - add overflow: 'hidden'
<View style={{ borderRadius: 20, overflow: 'hidden' }}>
  <Image source={...} style={{ width: '100%', height: 200 }} />
  {/* Image properly clipped to rounded corners */}
</View>

Pitfall 6: Inline Object Recreation

// ❌ SUBOPTIMAL - new object every render
function MyComponent() {
  return (
    <View style={{ flex: 1, padding: 16 }}>
      {/* This object is recreated on every render */}
    </View>
  );
}

// ✅ BETTER - StyleSheet created once
function MyComponent() {
  return (
    <View style={styles.container}>
      {/* Reference to pre-created style */}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
});

✅ Summary of Pitfalls

  1. Use numbers, not string units (200 not '200px')
  2. No shorthand — specify each property individually
  3. Remember flexDirection: 'column' is the default
  4. Text styles only work on Text components
  5. Add overflow: 'hidden' for borderRadius clipping
  6. Use StyleSheet.create() for static styles

Hands-On Exercises

Let's put your new StyleSheet knowledge into practice. Complete these exercises to solidify your understanding.

Exercise 1: Convert CSS to React Native

Convert this CSS to a React Native StyleSheet:

.profile-card {
  background-color: #ffffff;
  border-radius: 12px;
  padding: 20px;
  margin: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.profile-name {
  font-size: 24px;
  font-weight: bold;
  color: #1a1a1a;
  margin-bottom: 8px;
}

.profile-bio {
  font-size: 14px;
  color: #666666;
  line-height: 20px;
}
Show Solution
import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  profileCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    margin: 16,
    // iOS shadow
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    // Android shadow
    elevation: 4,
  },
  profileName: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#1a1a1a',
    marginBottom: 8,
  },
  profileBio: {
    fontSize: 14,
    color: '#666666',
    lineHeight: 20,
  },
});

Key conversions: kebab-case → camelCase, removed px units, split box-shadow into platform-specific properties.

Exercise 2: Build a Button Component with Variants

Create a Button component that accepts variant ('primary' | 'outline'), size ('small' | 'medium' | 'large'), and disabled props. Use style arrays to combine the appropriate styles.

Show Solution
import { StyleSheet, Pressable, Text } from 'react-native';

interface ButtonProps {
  title: string;
  variant?: 'primary' | 'outline';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  onPress?: () => void;
}

export function Button({ 
  title, 
  variant = 'primary', 
  size = 'medium',
  disabled = false,
  onPress 
}: ButtonProps) {
  return (
    <Pressable
      style={[
        styles.base,
        styles[variant],
        styles[size],
        disabled && styles.disabled,
      ]}
      onPress={onPress}
      disabled={disabled}
    >
      <Text style={[
        styles.text,
        variant === 'outline' && styles.outlineText,
        disabled && styles.disabledText,
      ]}>
        {title}
      </Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  base: {
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  
  // Variants
  primary: {
    backgroundColor: '#2196F3',
  },
  outline: {
    backgroundColor: 'transparent',
    borderWidth: 2,
    borderColor: '#2196F3',
  },
  
  // Sizes
  small: {
    paddingVertical: 8,
    paddingHorizontal: 16,
  },
  medium: {
    paddingVertical: 12,
    paddingHorizontal: 24,
  },
  large: {
    paddingVertical: 16,
    paddingHorizontal: 32,
  },
  
  // States
  disabled: {
    opacity: 0.5,
  },
  
  // Text styles
  text: {
    fontSize: 16,
    fontWeight: '600',
    color: '#ffffff',
  },
  outlineText: {
    color: '#2196F3',
  },
  disabledText: {
    color: '#999999',
  },
});

Exercise 3: Identify the Bugs

Find and fix all the bugs in this code:

const styles = StyleSheet.create({
  container: {
    display: 'block',
    width: '100%',
    padding: '16px',
  },
  card: {
    background: '#fff',
    border-radius: 8,
    box-shadow: '0 2px 4px rgba(0,0,0,0.1)',
  },
  title: {
    font-size: '24px',
    font-weight: 'bold',
  },
});

function Card() {
  return (
    <View style={styles.card}>
      <Text>Hello World</Text>
    </View>
  );
}
Show Solution
const styles = StyleSheet.create({
  container: {
    // ❌ display: 'block' → ✅ removed (View uses flex by default)
    // ❌ width: '100%' → ✅ width: '100%' (percentages are strings, this is OK)
    width: '100%',
    // ❌ padding: '16px' → ✅ padding: 16
    padding: 16,
  },
  card: {
    // ❌ background → ✅ backgroundColor
    backgroundColor: '#fff',
    // ❌ border-radius → ✅ borderRadius (camelCase!)
    borderRadius: 8,
    // ❌ box-shadow doesn't exist → ✅ platform-specific shadow
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2,
  },
  title: {
    // ❌ font-size → ✅ fontSize
    // ❌ '24px' → ✅ 24
    fontSize: 24,
    // ❌ font-weight → ✅ fontWeight
    fontWeight: 'bold',
  },
});

function Card() {
  return (
    <View style={styles.card}>
      {/* Note: Text has no style, so default system font/size/color */}
      <Text style={styles.title}>Hello World</Text>
    </View>
  );
}

Bugs found: display: 'block' invalid, kebab-case properties, string units, background should be backgroundColor, box-shadow doesn't exist, Text component missing style.

Summary

You've now built a solid foundation in React Native styling. Let's recap the key takeaways:

🎯 Key Concepts

  • StyleSheet.create() validates and optimizes your styles — use it for all static styles
  • No CSS engine — React Native translates JS style objects to native platform styles
  • camelCase properties, unitless numbers, and no shorthand
  • Flexbox is default and flexDirection defaults to 'column'
  • Style arrays let you combine and conditionally apply styles
  • No inheritance except for nested Text components
  • Platform differences exist, especially for shadows

Coming Up Next

In the next lesson, we'll dive deep into Flexbox in React Native. You'll learn how the default flex layout system works, master alignment and distribution, and build common layout patterns used in real mobile apps. The Flexbox knowledge you have from web development will serve you well, but there are some important differences we'll cover.

Quick Reference

// StyleSheet fundamentals cheat sheet
import { StyleSheet, View, Text } from 'react-native';

// 1. Create styles (at bottom of file)
const styles = StyleSheet.create({
  container: {
    flex: 1,                    // Takes full available space
    backgroundColor: '#fff',    // camelCase!
    padding: 16,                // Unitless number (dp)
  },
  text: {
    fontSize: 16,               // On Text only
    color: '#333',              // On Text only
    fontWeight: 'bold',         // String values
  },
});

// 2. Apply styles
<View style={styles.container}>
  <Text style={styles.text}>Hello</Text>
</View>

// 3. Combine styles (array)
<View style={[styles.base, styles.variant, { padding: 20 }]} />

// 4. Conditional styles
<View style={[styles.box, isActive && styles.active]} />

// 5. Dynamic inline (for calculated values)
<View style={{ width: screenWidth * 0.8 }} />