Componentes base: el “alfabeto” de la UI
En React Native, la interfaz se construye combinando componentes base. Piensa en ellos como piezas de LEGO: cada una tiene una responsabilidad clara y, al componerlas, obtienes pantallas completas. En este capítulo trabajaremos con View, Text, Image, ScrollView, FlatList, Pressable y TextInput, y aprenderás a darles forma con StyleSheet y Flexbox.
View: contenedor y estructura
View es el contenedor principal para agrupar elementos y aplicar layout (Flexbox), fondos, bordes y espaciados.
import { View, Text } from 'react-native';
export function Example() {
return (
<View>
<Text>Hola</Text>
</View>
);
}Text: tipografía y contenido
Text renderiza texto y permite estilos como tamaño, peso, color, alineación y espaciado entre líneas.
<Text style={{ fontSize: 18, fontWeight: '600' }}>Título</Text>
<Text style={{ color: '#6B7280', lineHeight: 20 }}>Descripción breve.</Text>Image: imágenes locales y remotas
Image requiere definir tamaño (o un contenedor que lo determine). Para imágenes remotas se usa source={{ uri }}; para locales, require().
import { Image } from 'react-native';
<Image
source={{ uri: 'https://picsum.photos/300/200' }}
style={{ width: 300, height: 200, borderRadius: 12 }}
resizeMode="cover"
/>Propiedades útiles: resizeMode (cover, contain, stretch, center) y borderRadius para esquinas redondeadas.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
ScrollView vs FlatList: desplazamiento y rendimiento
ScrollView renderiza todo su contenido de una vez; es ideal para pantallas con contenido limitado. FlatList virtualiza filas y es preferible para listas largas.
import { ScrollView, View, Text } from 'react-native';
export function Screen() {
return (
<ScrollView contentContainerStyle={{ padding: 16 }}>
<Text>Sección 1</Text>
<View style={{ height: 600 }} />
<Text>Sección 2</Text>
</ScrollView>
);
}import { FlatList, Text, View } from 'react-native';
const data = Array.from({ length: 100 }, (_, i) => ({ id: String(i), name: `Item ${i}` }));
export function ListScreen() {
return (
<FlatList
data={data}
keyExtractor={(item) => item.id}
contentContainerStyle={{ padding: 16 }}
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
renderItem={({ item }) => (
<View style={{ padding: 12, backgroundColor: '#fff', borderRadius: 12 }}>
<Text>{item.name}</Text>
</View>
)}
/>
);
}Pressable: interacción y estados
Pressable permite responder a toques y ajustar estilos según el estado (presionado, enfocado, etc.). Es una base excelente para botones personalizados.
import { Pressable, Text } from 'react-native';
export function PrimaryButton({ title, onPress }) {
return (
<Pressable
onPress={onPress}
style={({ pressed }) => [
{
backgroundColor: '#111827',
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 12,
opacity: pressed ? 0.85 : 1,
},
]}
>
<Text style={{ color: '#fff', fontWeight: '600', textAlign: 'center' }}>{title}</Text>
</Pressable>
);
}TextInput: formularios y entrada de datos
TextInput se controla normalmente con estado. Ajusta teclado, autocorrección, placeholder y seguridad para contraseñas.
import { useState } from 'react';
import { TextInput, View, Text } from 'react-native';
export function LoginForm() {
const [email, setEmail] = useState('');
return (
<View>
<Text style={{ marginBottom: 8 }}>Email</Text>
<TextInput
value={email}
onChangeText={setEmail}
placeholder="tu@email.com"
keyboardType="email-address"
autoCapitalize="none"
style={{
borderWidth: 1,
borderColor: '#E5E7EB',
borderRadius: 12,
paddingHorizontal: 12,
paddingVertical: 10,
}}
/>
</View>
);
}Estilos con StyleSheet: consistencia y mantenimiento
Aunque puedes usar estilos inline, StyleSheet ayuda a centralizar estilos, mejorar legibilidad y reutilización. Un patrón común es definir estilos por “bloques” (contenedor, texto, tarjeta, etc.).
import { StyleSheet, View, Text } from 'react-native';
export function Card() {
return (
<View style={styles.card}>
<Text style={styles.title}>Producto</Text>
<Text style={styles.subtitle}>Descripción corta</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 16,
borderWidth: 1,
borderColor: '#F3F4F6',
},
title: {
fontSize: 18,
fontWeight: '700',
color: '#111827',
},
subtitle: {
marginTop: 6,
fontSize: 14,
color: '#6B7280',
lineHeight: 20,
},
});Guía rápida de espaciados
padding: espacio interno del componente.margin: espacio externo respecto a otros componentes.- Usa escalas consistentes: por ejemplo 4, 8, 12, 16, 24, 32.
- Prefiere
paddingHorizontal/paddingVerticalymarginHorizontal/marginVerticalpara mantener simetría.
Tipografía práctica
- Define jerarquías: título (18–24), subtítulo (14–16), cuerpo (14–16), caption (12–13).
- Controla legibilidad con
lineHeight(aprox. 1.3–1.5 delfontSize). - Evita usar demasiados pesos; normalmente 400/500/700 es suficiente.
Flexbox en React Native: layouts responsivos
React Native usa Flexbox por defecto con flexDirection: 'column'. Esto significa que los elementos se apilan verticalmente si no indicas lo contrario.
Conceptos clave
| Propiedad | Qué controla | Ejemplo típico |
|---|---|---|
flexDirection | Eje principal (fila o columna) | 'row' para barras horizontales |
justifyContent | Distribución en el eje principal | 'space-between' para separar extremos |
alignItems | Alineación en el eje cruzado | 'center' para centrar verticalmente en filas |
flex | Cómo crece/ocupa espacio | flex: 1 para llenar |
gap | Espacio entre hijos (si está disponible) | gap: 12 |
flexWrap | Permite saltos de línea en filas | 'wrap' para chips/tags |
Ejemplo: encabezado con avatar y acciones
import { View, Text, Image, Pressable } from 'react-native';
export function Header() {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}>
<Image
source={{ uri: 'https://picsum.photos/80' }}
style={{ width: 44, height: 44, borderRadius: 22, marginRight: 12 }}
/>
<View style={{ flex: 1 }}>
<Text style={{ fontSize: 16, fontWeight: '700' }}>Hola, Ana</Text>
<Text style={{ color: '#6B7280' }}>Bienvenida de vuelta</Text>
</View>
<Pressable style={{ padding: 10, borderRadius: 12, backgroundColor: '#F3F4F6' }}>
<Text>⚙️</Text>
</Pressable>
</View>
);
}Observa el uso de flex: 1 en el bloque central para que empuje el botón de la derecha hacia el extremo.
Ejemplo: grid simple con filas y wrap
import { View, Text } from 'react-native';
const tags = ['React', 'Native', 'UI', 'Flexbox', 'Listas', 'Estilos'];
export function TagCloud() {
return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
{tags.map((t) => (
<View key={t} style={{
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 999,
backgroundColor: '#EEF2FF',
marginRight: 8,
marginBottom: 8,
}}>
<Text style={{ color: '#3730A3', fontWeight: '600' }}>{t}</Text>
</View>
))}
</View>
);
}Manejo de imágenes: patrones comunes
Imagen “hero” responsiva con relación de aspecto
Un patrón útil es fijar la relación de aspecto y dejar que el ancho lo determine el contenedor.
import { View, Image } from 'react-native';
export function HeroImage() {
return (
<View style={{ width: '100%', aspectRatio: 16 / 9, borderRadius: 16, overflow: 'hidden' }}>
<Image
source={{ uri: 'https://picsum.photos/1200/675' }}
style={{ width: '100%', height: '100%' }}
resizeMode="cover"
/>
</View>
);
}overflow: 'hidden' es clave para que el borderRadius recorte la imagen.
Avatar circular
<Image
source={{ uri: 'https://picsum.photos/200' }}
style={{ width: 64, height: 64, borderRadius: 32 }}
/>Patrones de composición: de piezas a pantallas
Para construir UI escalable, evita pantallas monolíticas. Divide en componentes reutilizables y organiza la pantalla por secciones. Un enfoque práctico es: (1) componentes de base (botones, inputs, separadores), (2) componentes de dominio (tarjetas de producto, item de lista), (3) secciones (header, listado, resumen), (4) pantalla.
Patrón 1: Componentes reutilizables (UI kit mínimo)
Crea componentes pequeños con API clara. Ejemplo: botón primario y campo de texto.
import { StyleSheet, Pressable, Text, TextInput, View } from 'react-native';
export function AppButton({ title, onPress, variant = 'primary' }) {
return (
<Pressable
onPress={onPress}
style={({ pressed }) => [
styles.btn,
variant === 'primary' ? styles.btnPrimary : styles.btnGhost,
pressed && { opacity: 0.85 },
]}
>
<Text style={[styles.btnText, variant === 'primary' ? styles.btnTextPrimary : styles.btnTextGhost]}>
{title}
</Text>
</Pressable>
);
}
export function AppInput({ label, value, onChangeText, placeholder }) {
return (
<View style={{ gap: 8 }}>
<Text style={{ fontWeight: '600', color: '#111827' }}>{label}</Text>
<TextInput
value={value}
onChangeText={onChangeText}
placeholder={placeholder}
style={styles.input}
/>
</View>
);
}
const styles = StyleSheet.create({
btn: { paddingVertical: 12, paddingHorizontal: 16, borderRadius: 12, alignItems: 'center' },
btnPrimary: { backgroundColor: '#111827' },
btnGhost: { backgroundColor: '#F3F4F6' },
btnText: { fontWeight: '700' },
btnTextPrimary: { color: '#FFFFFF' },
btnTextGhost: { color: '#111827' },
input: {
borderWidth: 1,
borderColor: '#E5E7EB',
borderRadius: 12,
paddingHorizontal: 12,
paddingVertical: 10,
backgroundColor: '#FFFFFF',
},
});Patrón 2: UI por secciones (Screen Sections)
En lugar de renderizar todo en una sola función, crea secciones con responsabilidad clara: ProfileHeader, StatsRow, RecentActivityList. Esto facilita iterar sobre el diseño y testear visualmente cada bloque.
function ProfileHeader() { /* ... */ }
function StatsRow() { /* ... */ }
function RecentActivityList() { /* ... */ }
export function ProfileScreen() {
return (
<ScrollView contentContainerStyle={{ padding: 16, gap: 16 }}>
<ProfileHeader />
<StatsRow />
<RecentActivityList />
</ScrollView>
);
}Patrón 3: Item de lista como componente
Con FlatList, extrae el item a un componente para mantener la pantalla limpia y reutilizarlo en otras listas.
import { View, Text, Image, Pressable } from 'react-native';
export function ProductRow({ item, onPress }) {
return (
<Pressable onPress={() => onPress(item)} style={{ flexDirection: 'row', gap: 12, padding: 12, borderRadius: 16, backgroundColor: '#fff', borderWidth: 1, borderColor: '#F3F4F6' }}>
<Image source={{ uri: item.image }} style={{ width: 64, height: 64, borderRadius: 12 }} />
<View style={{ flex: 1, justifyContent: 'center' }}>
<Text style={{ fontWeight: '700', color: '#111827' }}>{item.title}</Text>
<Text style={{ color: '#6B7280', marginTop: 4 }} numberOfLines={2}>{item.subtitle}</Text>
</View>
<View style={{ justifyContent: 'center' }}>
<Text style={{ fontWeight: '700' }}>${item.price}</Text>
</View>
</Pressable>
);
}Práctica guiada: de diseño estático a pantalla real
Objetivo: transformar un diseño estático (tipo “Home de tienda”) en una pantalla funcional con secciones, estilos consistentes, un buscador y una lista de productos. Trabajaremos con: header, barra de búsqueda, categorías horizontales y listado vertical.
Paso 1: Define el layout general con ScrollView
Usa ScrollView para permitir desplazamiento de toda la pantalla y un contentContainerStyle con padding y separación.
import { ScrollView, StyleSheet } from 'react-native';
export function ShopHomeScreen() {
return (
<ScrollView contentContainerStyle={styles.container}>
<HeaderSection />
<SearchSection />
<CategoriesSection />
<ProductsSection />
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
padding: 16,
gap: 16,
backgroundColor: '#FFFFFF',
},
});Paso 2: Crea la sección Header (View + Text + Image + Pressable)
Incluye saludo, subtítulo y un avatar. Usa Flexbox en fila y flex: 1 para que el texto ocupe el espacio disponible.
import { View, Text, Image, Pressable, StyleSheet } from 'react-native';
function HeaderSection() {
return (
<View style={headerStyles.row}>
<View style={{ flex: 1 }}>
<Text style={headerStyles.title}>Explora</Text>
<Text style={headerStyles.subtitle}>Encuentra productos para hoy</Text>
</View>
<Pressable style={headerStyles.avatarWrap}>
<Image source={{ uri: 'https://picsum.photos/100' }} style={headerStyles.avatar} />
</Pressable>
</View>
);
}
const headerStyles = StyleSheet.create({
row: { flexDirection: 'row', alignItems: 'center', gap: 12 },
title: { fontSize: 24, fontWeight: '800', color: '#111827' },
subtitle: { marginTop: 4, color: '#6B7280' },
avatarWrap: { width: 44, height: 44, borderRadius: 22, overflow: 'hidden' },
avatar: { width: '100%', height: '100%' },
});Paso 3: Sección de búsqueda (TextInput + icono “falso”)
Construye un campo de búsqueda con un contenedor tipo “pill”. El icono puede ser un Text o una View decorativa; lo importante es el layout.
import { useState } from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';
function SearchSection() {
const [query, setQuery] = useState('');
return (
<View style={searchStyles.box}>
<Text style={searchStyles.icon}>🔎</Text>
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Buscar productos"
style={searchStyles.input}
autoCapitalize="none"
/>
</View>
);
}
const searchStyles = StyleSheet.create({
box: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
paddingHorizontal: 12,
paddingVertical: 10,
borderRadius: 14,
backgroundColor: '#F3F4F6',
},
icon: { fontSize: 16 },
input: { flex: 1, fontSize: 16 },
});Paso 4: Categorías horizontales (FlatList horizontal + Pressable)
Usa FlatList horizontal para categorías. Mantén un estado selected para resaltar la categoría activa.
import { useMemo, useState } from 'react';
import { View, Text, FlatList, Pressable, StyleSheet } from 'react-native';
function CategoriesSection() {
const categories = useMemo(() => ['Todo', 'Novedades', 'Hogar', 'Tecnología', 'Ropa'], []);
const [selected, setSelected] = useState('Todo');
return (
<View style={{ gap: 10 }}>
<Text style={catStyles.heading}>Categorías</Text>
<FlatList
data={categories}
keyExtractor={(c) => c}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ gap: 10 }}
renderItem={({ item }) => {
const active = item === selected;
return (
<Pressable
onPress={() => setSelected(item)}
style={[catStyles.pill, active ? catStyles.pillActive : catStyles.pillIdle]}
>
<Text style={[catStyles.pillText, active ? catStyles.pillTextActive : catStyles.pillTextIdle]}>
{item}
</Text>
</Pressable>
);
}}
/>
</View>
);
}
const catStyles = StyleSheet.create({
heading: { fontSize: 16, fontWeight: '800', color: '#111827' },
pill: { paddingHorizontal: 14, paddingVertical: 10, borderRadius: 999 },
pillActive: { backgroundColor: '#111827' },
pillIdle: { backgroundColor: '#F3F4F6' },
pillText: { fontWeight: '700' },
pillTextActive: { color: '#FFFFFF' },
pillTextIdle: { color: '#111827' },
});Paso 5: Listado de productos (FlatList vertical dentro de una sección)
En una pantalla con ScrollView, una lista grande puede generar conflictos de scroll. Para esta práctica, mantén el listado corto o renderiza productos como “bloques” dentro del ScrollView. Alternativa profesional: usar solo FlatList como contenedor principal y renderizar secciones con ListHeaderComponent. Aquí aplicaremos ese patrón para escalar mejor.
import { useMemo } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
import { ProductRow } from './ProductRow';
export function ShopHomeScreen() {
const products = useMemo(
() => [
{ id: '1', title: 'Auriculares', subtitle: 'Sonido nítido y cancelación de ruido.', price: 59, image: 'https://picsum.photos/200?1' },
{ id: '2', title: 'Lámpara', subtitle: 'Luz cálida para tu escritorio.', price: 24, image: 'https://picsum.photos/200?2' },
{ id: '3', title: 'Mochila', subtitle: 'Resistente, ideal para el día a día.', price: 39, image: 'https://picsum.photos/200?3' },
],
[]
);
return (
<FlatList
data={products}
keyExtractor={(p) => p.id}
contentContainerStyle={styles.container}
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
ListHeaderComponent={
<View style={{ gap: 16 }}>
<HeaderSection />
<SearchSection />
<CategoriesSection />
<Text style={styles.heading}>Recomendados</Text>
</View>
}
renderItem={({ item }) => (
<ProductRow item={item} onPress={() => {}} />
)}
/>
);
}
const styles = StyleSheet.create({
container: { padding: 16, backgroundColor: '#FFFFFF' },
heading: { fontSize: 16, fontWeight: '800', color: '#111827' },
});Paso 6: Ajustes finos de layout (espaciado, alineación, densidad visual)
- Si la UI se ve “apretada”, aumenta
gapo separadores (ItemSeparatorComponent). - Si se ve “vacía”, reduce padding o usa tarjetas con borde sutil (
borderColorclaro). - Para alinear textos y precios, usa
justifyContent: 'center'en columnas pequeñas yflex: 1en el bloque principal. - Para consistencia, centraliza colores y tamaños en un archivo de constantes (por ejemplo
theme.js) y reutilízalos enStyleSheet.
Paso 7: Checklist de calidad visual
- ¿Los textos tienen jerarquía clara (título vs subtítulo vs cuerpo)?
- ¿Los botones tienen estado presionado (feedback)?
- ¿Las imágenes tienen tamaño consistente y bordes redondeados sin deformarse (
resizeMode)? - ¿La lista mantiene rendimiento (usar
FlatListpara colecciones)? - ¿Los espaciados siguen una escala (8/12/16)?