View and Flexbox

The View Component and Flexbox Layout

A View is the fundamental container, with layout, style, touch handling and accessibility. It maps to an android.view.ViewGroup subclass on Android and a UIView on iOS, draws only its background, border and shadow, and cannot hold bare text.

Layout is flexbox, computed by Meta's C++ Yoga engine with the rules you know from CSS (Front-End Web Development). Only the defaults differ, which is why a first screen stacks vertically:

Flexbox defaults that differ from the web
Property React Native 36,878 default CSS default
flexDirection column row
alignContent flex-start stretch
flexShrink 0 1
flex one number only shorthand, up to three values

Every element is a flex container, lengths are unitless density-independent pixels (or percentage strings), styles never cascade from a parent View, and gap works as on the web. Each screen in this section is a component in src/demos/ that App.tsx renders inside the SafeAreaProvider of react-native-safe-area-context 2,760 (core SafeAreaView was deprecated in React Native 0.81). They share one wrapper and style sheet:

src/demos/ui.tsx: the shared screen wrapper and stylesTSX
import { ReactNode } from 'react';
import { StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export const Screen = ({ children }: { children: ReactNode }) => (
  <SafeAreaView style={ui.screen}>{children}</SafeAreaView>
);
export const ui = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 12, backgroundColor: '#eceff1' },
  card: { padding: 12, borderRadius: 8, backgroundColor: '#fff' },
  h1: { fontSize: 24, fontWeight: '700', color: '#212121' },
  label: { fontWeight: '600', color: '#37474f' },
  input: { borderWidth: 1, borderColor: '#90a4ae', borderRadius: 6, padding: 10, fontSize: 16 },
  error: { color: '#c62828' },
  button: { padding: 12, borderRadius: 6, alignItems: 'center', backgroundColor: '#1565c0' },
  buttonText: { color: '#fff', fontWeight: '700' },
});

The first screen lays out the six BookNest books as cards. Each card is a row; the text column takes the remaining width with flex: 1, and marginTop: 'auto' pushes the price to the bottom of the card, whose height comes from the cover's aspectRatio:

src/demos/BookCards.tsx: nested rows and columns (excerpt)TSX
      {books.map((book) => (
        <View key={book.id} style={[ui.card, { flexDirection: 'row', gap: 12 }]}>
          <View style={{ width: 56, aspectRatio: 2 / 3, backgroundColor: '#1565c0' }} />
          <View style={{ flex: 1 }}>
            <Text style={{ fontSize: 17, fontWeight: '600' }}>{book.title}</Text>
            <Text style={ui.label}>{book.author}</Text>
            <View style={{ flexDirection: 'row', justifyContent: 'space-between',
                           marginTop: 'auto' }}>
              <Text style={{ fontWeight: '700' }}>${book.price.toFixed(2)}</Text>
              <Text>{book.rating} ★</Text>
            </View>
          </View>
        </View>
      ))}

Like most listings here, it shows only what sits inside <Screen>; the result is the left screen of the figure in Long Text. Two pitfalls catch web developers: with flexShrink at 0, a long title can push its siblings off screen (flex: 1 on the column fixes it), and flex: 1 only fills space its parent has, so a blank screen usually has a parent without a height.