Inmersión en Flexbox
El caballo de batalla del diseño mobile - dirección, flex, alineación y recetas comunes.
Busca en todas las páginas de la documentación
El caballo de batalla del diseño mobile - dirección, flex, alineación y recetas comunes.
Tarjeta de referencia rápida - lista para copiar y pegar.
import { StyleSheet, Text, View } from "react-native";
export function ScreenShell() {
return (
<View style={styles.screen}>
<View style={styles.header}>
<Text style={styles.title}>Inbox</Text>
</View>
<View style={styles.content}>
<Text>El contenido desplazable o de lista va aquí.</Text>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>3 sin leer</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: "#fff" },
header: {
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: "#e2e8f0",
},
title: { fontSize: 20, fontWeight: "700" },
content: { flex: 1, padding: 16 }, // crece - empuja el pie hacia abajo
footer: {
padding: 16,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#e2e8f0",
},
footerText: { textAlign: "center", color: "#64748b" },
});Cuándo usarlo: Cualquier diseño de pantalla en React Native - filas, columnas, barras de herramientas, tarjetas y el shell clásico encabezado/contenido/pie todos se ejecutan a través del flexbox Yoga.
import { ScrollView, StyleSheet, Text, View } from "react-native";
interface Task {
id: string;
title: string;
done: boolean;
}
const TASKS: Task[] = [
{ id: "1", title: "Revisar PR de flexbox", done: false },
{ id: "2", title: "Enviar tokens de modo oscuro", done: true },
{ id: "3", title: "Probar diseño de pantalla plegable", done: false },
];
export default function TaskBoard() {
return (
<View style={styles.screen}>
{/* Fila de barra de herramientas */}
<View style={styles.toolbar}>
<Text style={styles.heading}>Tareas</Text>
<View style={styles.badge}>
<Text style={styles.badgeText}>{TASKS.filter((t) => !t.done).length}</Text>
</View>
</View>
{/* Hijo flex desplazable */}
<ScrollView contentContainerStyle={styles.list}>
{TASKS.map((task) => (
<View key={task.id} style={styles.row}>
<View style={[styles.dot, task.done && styles.dotDone]} />
<Text
style={[styles.rowTitle, task.done && styles.rowTitleDone]}
numberOfLines={1}
>
{task.title}
</Text>
<Text style={styles.rowMeta}>{task.done ? "Hecho" : "Abierto"}</Text>
</View>
))}
</ScrollView>
{/* Barra de acciones inferior */}
<View style={styles.actionBar}>
<View style={styles.actionPrimary}>
<Text style={styles.actionPrimaryText}>Añadir tarea</Text>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: "#f8fafc" },
toolbar: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 16,
paddingVertical: 12,
gap: 8,
},
heading: { fontSize: 28, fontWeight: "700", color: "#0f172a" },
badge: {
minWidth: 28,
height: 28,
borderRadius: 14,
backgroundColor: "#2563eb",
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 8,
},
badgeText: { color: "#fff", fontWeight: "700", fontSize: 13 },
list: { padding: 16, gap: 10 },
row: {
flexDirection: "row",
alignItems: "center",
gap: 12,
backgroundColor: "#fff",
borderRadius: 12,
padding: 14,
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: "#94a3b8",
flexShrink: 0,
},
dotDone: { backgroundColor: "#22c55e" },
rowTitle: { flex: 1, fontSize: 16, fontWeight: "600", color: "#0f172a" },
rowTitleDone: { color: "#94a3b8", textDecorationLine: "line-through" },
rowMeta: { fontSize: 13, color: "#64748b", flexShrink: 0 },
actionBar: {
padding: 16,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#cbd5e1",
backgroundColor: "#fff",
},
actionPrimary: {
backgroundColor: "#2563eb",
borderRadius: 12,
paddingVertical: 14,
alignItems: "center",
},
actionPrimaryText: { color: "#fff", fontWeight: "600", fontSize: 16 },
});Lo que esto demuestra:
flex: 1 en la raíz de la pantalla para que el diseño llene el viewport del dispositivo.flexDirection: "row" para barra de herramientas y filas de lista - explícito, nunca asumido.justifyContent: "space-between" para fijar el título a la izquierda y la insignia a la derecha.flex: 1 en el Text del título para que se trunce mientras los metadatos se mantienen visibles.flexShrink: 0 en el punto y los metadatos para que los iconos no se aplasien.gap para espaciado consistente entre hijos de fila sin matemáticas de margin.ScrollView como hijo flex que se desplaza mientras la barra de acciones permanece fija.View es un contenedor flex. El display predeterminado es flex; no hay división block/inline como en la web.flexDirection predeterminado es column - los hijos se apilan verticalmente a menos que establezcas row.alignItems predeterminado es stretch - los hijos se expanden al ancho del eje transversal del contenedor (en una columna, eso significa ancho completo).flexShrink predeterminado es 0 en hijos (difiere de muchos valores predeterminados del navegador) - los elementos mantienen su tamaño intrínseco a menos que permitas encogimiento.flex: 1 es una abreviatura de flexGrow: 1, flexShrink: 1, flexBasis: 0 - el hijo reclama todo el espacio restante a lo largo del eje principal.flexDirection | Eje principal (justifyContent) | Eje transversal (alignItems) |
|---|---|---|
column (predeterminado) | Vertical ↕ | Horizontal ↔ |
row | Horizontal ↔ | Vertical ↕ |
column-reverse | Vertical (invertido) | Horizontal |
row-reverse | Horizontal (invertido) | Vertical |
// Columna (predeterminado): justifyContent = vertical, alignItems = horizontal
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Centrado</Text>
</View>
// Fila: justifyContent = horizontal, alignItems = vertical
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
<Icon />
<Text style={{ flex: 1 }}>Label llena el espacio del medio</Text>
<Chevron />
</View>| Propiedad | Efecto |
|---|---|
flex: 1 | Crecer para llenar el espacio restante; encoger si es necesario |
flexGrow: 1 | Tomar espacio extra cuando los hermanos no |
flexShrink: 1 | Permitir encogimiento por debajo del tamaño intrínseco cuando hay congestión |
flexShrink: 0 | Nunca encoger - proteger iconos, insignias, avatares |
flexBasis: "auto" | Comenzar desde el tamaño del contenido, luego crecer/encoger |
flexBasis: 0 | Ignorar el tamaño del contenido al distribuir espacio |
alignSelf | Anular el alignItems del padre para un hijo |
1. Encabezado / contenido desplazable / pie
<View style={{ flex: 1 }}>
<Header />
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16 }} />
<Footer />
</View>2. Acciones de barra de herramientas espaciadas horizontalmente
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<BackButton />
<Title />
<MenuButton />
</View>3. Segmentos de ancho igual (pestañas)
<View style={{ flexDirection: "row" }}>
{tabs.map((tab) => (
<Pressable key={tab} style={{ flex: 1, alignItems: "center" }}>
<Text>{tab}</Text>
</Pressable>
))}
</View>4. Acciones de tarjeta alineadas en la parte inferior
<View style={{ flex: 1, justifyContent: "flex-end", padding: 16 }}>
<PrimaryButton />
</View>5. Fila de chip con ajuste
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
{tags.map((tag) => (
<Chip key={tag} label={tag} />
))}
</View>import type { FlexStyle, ViewStyle } from "react-native";
type Justify = FlexStyle["justifyContent"]; // "flex-start" | "center" | "space-between" | ...
interface RowProps {
children: React.ReactNode;
justify?: Justify;
style?: ViewStyle;
}
function Row({ children, justify = "flex-start", style }: RowProps) {
return (
<View style={[{ flexDirection: "row", alignItems: "center", justifyContent: justify, gap: 8 }, style]}>
{children}
</View>
);
}FlexStyle cubre claves específicas de flex; ViewStyle es el tipo de estilo de vista completo.[styles.base, isActive && styles.active].false, null, undefined) se ignoran de forma segura.Asumir dirección de fila - Pegar fragmentos web sin flexDirection: "row" apila elementos verticalmente. Solución: Establece flexDirection: "row" explícitamente para diseños horizontales.
Falta de flex: 1 en la raíz de la pantalla - El contenido no llena el viewport; los pies flotan a mitad de pantalla. Solución: Aplica flex: 1 a la View más externa (dentro de tu envoltorio de área segura).
Usar height: "100%" en lugar de flex: 1 - La altura de porcentaje requiere un padre con altura explícita; falla de forma impredecible en árboles anidados. Solución: Prefiere flex: 1 para diseños de llenar espacio restante.
Texto truncado en filas sin flex: 1 - Los títulos largos empujan hermanos fuera de la pantalla en lugar de hacer elipsis. Solución: Dale al Text flex: 1 (y numberOfLines) dentro de una fila; establece flexShrink: 0 en iconos finales.
justifyContent: "center" sin flex: 1 - El centrado solo funciona dentro de la altura del contenido del contenedor, no de la pantalla completa. Solución: Dale al contenedor flex: 1 para que abarca el espacio disponible.
alignItems: "center" encogiendo hijos de fila - Los hijos encojen al ancho del contenido; un hijo width: "100%" puede no estirarse. Solución: Usa alignSelf: "stretch" en el hijo que debe ser ancho completo, o usa alignItems: "stretch" en el padre.
Padding en ScrollView en lugar de contentContainerStyle - Los indicadores de desplazamiento y el rebote se comportan de manera extraña en iOS. Solución: Pon padding y gap en contentContainerStyle; mantén style={{ flex: 1 }} en la ScrollView en sí.
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Flexbox Yoga (View) | Predeterminado para todo diseño RN - filas, columnas, llenar espacio | Necesitas superposiciones de posicionamiento absoluto (usa position: "absolute") |
FlatList / SectionList | Listas virtualizadas largas con filas homogéneas | Contenido estático corto - ScrollView + map es más simple |
react-native-reanimated animaciones de diseño | Cambios de flex animados, diseños que entran/salen | Pantallas estáticas sin requisitos de movimiento |
Anchos de porcentaje (width: "50%") | Divisiones simples de dos columnas | Grillas de varias columnas precisas - calcula dp desde useWindowDimensions |
| CSS Grid (solo objetivos web) | Expo web con grid responsivo | Diseños iOS/Android de producción - flex es la opción portátil |
column - los hijos se apilan de arriba a abajo. Esto es lo opuesto a muchas barras de navegación web que fluyen implícitamente en una fila. Siempre establece flexDirection: "row" cuando desees diseño horizontal.
Es una abreviatura de flexGrow: 1, flexShrink: 1, flexBasis: 0. El hijo crece para consumir todo el espacio restante a lo largo del eje principal del padre y puede encoger si el espacio es limitado. Pon flex: 1 en la raíz de la pantalla y en áreas de contenido entre encabezados/pies fijos.
justifyContent - alineación a lo largo del eje principal (vertical en una columna, horizontal en una fila).alignItems - alineación a lo largo del eje transversal (horizontal en una columna, vertical en una fila).Centra un único hijo en una pantalla completa: flex: 1, justifyContent: "center", alignItems: "center".
<View style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }} />
<Footer />
</View>El ScrollView con flex: 1 toma todo el espacio por encima del pie. El pie se renderiza a su altura natural debajo.
numberOfLines necesita un ancho limitado. En una fila, dale al Text flex: 1 para que compita por espacio, y flexShrink: 0 en iconos finales para que mantengan su tamaño.
Evita que un hijo se encoja por debajo de su tamaño intrínseco cuando la fila está llena. Úsalo en avatares, iconos, insignias y botones de ancho fijo para que no se aplasien.
Sí - gap, rowGap y columnGap se admiten en React Native 0.71+. Añaden espacio entre hijos flex sin margin en elementos primero/último. Funciona en contentContainerStyle de View y ScrollView.
justifyContent: "space-between" - primero al inicio, último al final, espacio igual entre.justifyContent: "space-around" - espacio igual alrededor de cada elemento.justifyContent: "space-evenly" - espacios perfectamente uniformes incluyendo bordes.Para barra de herramientas atrás/título/menú, space-between es la más común.
Sí - flex es recursivo. Una pantalla de columna puede contener barras de herramientas de fila, elementos de lista de fila y tarjetas de columna. Cada View establece su propio contexto flex con ejes principal/transversal independientes.
justifyContent: "center" centra dentro de la altura propia del contenedor. Si el contenedor envuelve su contenido (sin flex: 1), el centrado no tiene espacio extra para trabajar. Añade flex: 1 al contenedor.
flexWrap: "wrap" permite que hijos de fila fluyan a la siguiente línea cuando exceden el ancho del contenedor. Combina con gap para grillas de chips y nubes de etiquetas. Establece anchos explícitos en hijos o usa base de porcentaje para columnas uniformes.
Anula el alignItems del padre para un único hijo. Ejemplo: el padre tiene alignItems: "center" pero un hijo necesita ancho completo - establece alignSelf: "stretch" en ese hijo.
Prefiere gap para espaciado uniforme entre hermanos. Usa margin cuando un elemento necesita espaciado asimétrico (p. ej., empuja un botón a la derecha con marginLeft: "auto" en una fila - aunque justifyContent: "space-between" es más limpio).
Dale al ScrollView style={{ flex: 1 }} para que llene espacio entre hermanos. Pon padding y gap en contentContainerStyle. Añade contentContainerStyle={{ flexGrow: 1 }} cuando necesites que el contenido de desplazamiento llene el viewport (p. ej., centra el texto de estado vacío).
Flex distribuye espacio dentro de un contenedor; los puntos de quiebre deciden qué árbol flex renderizar. Ver Diseño Responsivo y Adaptativo y Dimensiones y Diseño Responsivo.
StyleSheet.create, estilos en línea y el modelo mental de estilo RNView y reglas de anidamiento de textouseWindowDimensions para matemáticas de ancho de columnaVersiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0 y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026