Diseño responsivo y adaptativo
Puntos de ruptura para teléfono/tablet/plegable sin suposiciones web CSS.
Busca en todas las páginas de la documentación
Puntos de ruptura para teléfono/tablet/plegable sin suposiciones web CSS.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import React, { useMemo } from "react";
import { useWindowDimensions, View, StyleSheet } from "react-native";
export const BREAKPOINTS = { phone: 0, tablet: 768, wide: 1024 } as const;
export type LayoutClass = keyof typeof BREAKPOINTS;
export function useLayoutClass(): LayoutClass {
const { width } = useWindowDimensions();
if (width >= BREAKPOINTS.wide) return "wide";
if (width >= BREAKPOINTS.tablet) return "tablet";
return "phone";
}
export function AdaptiveColumns({ children }: { children: React.ReactNode }) {
const { width } = useWindowDimensions();
const layout = useLayoutClass();
const columns = layout === "phone" ? 1 : layout === "tablet" ? 2 : 3;
const gap = layout === "phone" ? 12 : 16;
const itemWidth = useMemo(
() => (width - gap * (columns + 1)) / columns,
[width, columns, gap]
);
return (
<View style={[styles.grid, { gap, padding: gap }]}>
{React.Children.map(children, (child, index) => (
<View key={index} style={{ width: itemWidth }}>
{child}
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
grid: { flexDirection: "row", flexWrap: "wrap" },
});Cuándo usarlo: Pantallas que deben cambiar el número de columnas, patrón de navegación o estructura maestro-detalle en teléfonos, tablets, dispositivos plegables y rotación - sin asumir consultas de medios CSS del navegador.
import { ScrollView, StyleSheet, Text, View, useWindowDimensions } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
const TABLET_MIN = 768;
type Message = { id: string; subject: string; preview: string };
const MESSAGES: Message[] = [
{ id: "1", subject: "Sprint review", preview: "Demo adaptive layout on iPad." },
{ id: "2", subject: "Foldable QA", preview: "Test narrow window after fold." },
{ id: "3", subject: "Breakpoint tokens", preview: "Document 768 in the design system." },
];
export default function InboxAdaptiveScreen() {
const { width, height } = useWindowDimensions();
const minDim = Math.min(width, height);
const isTablet = minDim >= TABLET_MIN;
const isWide = width >= 1024;
const isLandscape = width > height;
const listWidth = isWide ? 360 : isTablet ? (isLandscape ? 320 : 280) : undefined;
return (
<SafeAreaView style={styles.safe} edges={["top", "left", "right"]}>
<View style={[styles.shell, isTablet && styles.shellTablet]}>
{/* List pane - full width on phone, fixed sidebar on tablet */}
<View style={[styles.list, isTablet && { width: listWidth }]}>
<Text style={styles.heading}>Inbox</Text>
<Text style={styles.meta}>
{width}×{height} · {isTablet ? "tablet layout" : "phone layout"}
</Text>
{MESSAGES.map((msg) => (
<View key={msg.id} style={styles.row}>
<Text style={styles.subject}>{msg.subject}</Text>
<Text style={styles.preview} numberOfLines={1}>
{msg.preview}
</Text>
</View>
))}
</View>
{/* Detail pane - visible only on tablet+; phone navigates via stack */}
{isTablet ? (
<ScrollView style={styles.detail} contentContainerStyle={styles.detailInner}>
<Text style={styles.detailTitle}>Select a message</Text>
<Text style={styles.detailBody}>
Master-detail split view. On phone, push this screen from the list via Expo Router.
</Text>
</ScrollView>
) : null}
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: { flex: 1, backgroundColor: "#fff" },
shell: { flex: 1 },
shellTablet: { flexDirection: "row" },
list: { flex: 1, borderColor: "#e2e8f0" },
heading: { fontSize: 28, fontWeight: "700", padding: 16, paddingBottom: 4 },
meta: { fontSize: 13, color: "#64748b", paddingHorizontal: 16, paddingBottom: 12 },
row: {
paddingHorizontal: 16,
paddingVertical: 12,
borderTopWidth: StyleSheet.hairlineWidth,
borderColor: "#f1f5f9",
gap: 2,
},
subject: { fontSize: 16, fontWeight: "600", color: "#0f172a" },
preview: { fontSize: 14, color: "#64748b" },
detail: { flex: 1, backgroundColor: "#f8fafc" },
detailInner: { padding: 24 },
detailTitle: { fontSize: 22, fontWeight: "600" },
detailBody: { fontSize: 16, lineHeight: 24, color: "#475569", marginTop: 8 },
});Lo que esto demuestra:
useWindowDimensions() impulsando el re-diseño en rotación, pliegue y redimensionamiento de pantalla dividida.min(width, height) para detección de tablet segura para plegables (no solo ancho bruto).flexDirection: "row" maestro-detalle en tablet.@media o consultas de contenedor. Lees width y height de useWindowDimensions() y ramificas el diseño en JSX u objetos de estilo derivados.useWindowDimensions se suscribe a eventos de cambio de dimensión. La rotación, los cambios de estado de pliegue y la pantalla dividida de Android activan re-renderizados con nuevos valores. Una sola llamada a Dimensions.get("window") no se actualiza.Para la mecánica de hooks, fontScale y PixelRatio, consulta Dimensiones y diseño responsivo.
// Basado en ancho (más común)
export const BP = {
phone: 0,
tablet: 768, // iPad portrait, many Android tablets
desktop: 1024, // iPad landscape, large tablets, foldable inner display
} as const;
// Alternativa inspirada en Material
export const MATERIAL = { compact: 0, medium: 600, expanded: 840 } as const;| Umbral | Dispositivos típicos | Uso común |
|---|---|---|
< 768 | Teléfonos, pantalla dividida estrecha | Una sola columna, navegación por pila |
768-1023 | Tablet en orientación vertical, pantalla interna pequeña plegable | Dos columnas, maestro-detalle dividido |
>= 1024 | Tablet en orientación horizontal, clase de escritorio | Tres columnas, raíl lateral persistente |
import { Platform, useWindowDimensions } from "react-native";
function useDeviceLayout() {
const { width, height } = useWindowDimensions();
const minDim = Math.min(width, height);
const maxDim = Math.max(width, height);
return {
width,
height,
isLandscape: width > height,
isTablet: minDim >= 768,
isWide: width >= 1024,
isIPad: Platform.OS === "ios" && Platform.isPad,
// Foldable: inner display may be square - minDim catches "tablet-class"
isTabletClass: minDim >= 600,
};
}width >= 768 - simple, funciona para la mayoría de tablets en orientación vertical.min(width, height) >= 768 - mejor para dispositivos plegables y relaciones de aspecto cuadradas.Platform.isPad - solo iOS; empareja con comprobaciones de ancho para Android.| Patrón | Teléfono | Tablet / ancho |
|---|---|---|
| Maestro-detalle | Empuja la pantalla de detalle a la pila | Lista y detalle lado a lado |
| Navegación | Pestañas inferiores | Raíl lateral o cajón permanente |
| Cuadrícula | 1-2 columnas | 2-4 columnas |
| Modal | Hoja de pantalla completa | Diálogo centrado maxWidth: 560 |
| Tipografía | fontSize: 16 | fontSize: 17-18, lineLength más ancho |
// Límite de ancho modal en pantallas grandes
const { width } = useWindowDimensions();
const modalWidth = Math.min(width - 48, 560);Los puntos de ruptura eligen qué árbol renderizar; flexbox distribuye espacio dentro de cada panel:
const isTablet = width >= 768;
return (
<View style={{ flex: 1, flexDirection: isTablet ? "row" : "column" }}>
<View style={isTablet ? { width: 320 } : { flex: 1 }}>{/* list */}</View>
{isTablet && <View style={{ flex: 1 }}>{/* detail */}</View>}
</View>
);Consulta Análisis profundo de Flexbox para recetas de flex, gap y row.
import { useWindowDimensions, ScaledSize } from "react-native";
type Breakpoint = "phone" | "tablet" | "wide";
function getBreakpoint({ width }: ScaledSize): Breakpoint {
if (width >= 1024) return "wide";
if (width >= 768) return "tablet";
return "phone";
}
function useResponsiveValue<T>(map: Record<Breakpoint, T>): T {
const window = useWindowDimensions();
return map[getBreakpoint(window)];
}
// Usage
const padding = useResponsiveValue({ phone: 16, tablet: 24, wide: 32 });
const columns = useResponsiveValue({ phone: 1, tablet: 2, wide: 3 });import type { ScaledSize } from "react-native";
interface ResponsiveProps {
phone: React.ReactNode;
tablet?: React.ReactNode;
wide?: React.ReactNode;
}
function Responsive({ phone, tablet, wide }: ResponsiveProps) {
const window = useWindowDimensions();
const bp = getBreakpoint(window);
if (bp === "wide" && wide) return <>{wide}</>;
if (bp === "tablet" && tablet) return <>{tablet}</>;
return <>{phone}</>;
}BREAKPOINTS y getBreakpoint de un módulo - evita números mágicos en las pantallas.LayoutClass como una unión para que las declaraciones switch verifiquen la exhaustividad.Dimensions.get() sin suscripción - Diseño congelado después de la rotación. Solución: Usa useWindowDimensions() en cualquier componente que se ramifique según el tamaño.
width: 375 codificado desde un mock de diseño - Los iPhones van de 320-430+ dp; Android varía más. Solución: Deriva anchos de useWindowDimensions() o usa flex y porcentajes.
Comprobación de tablet en width solo en orientación horizontal - Un teléfono en orientación horizontal puede exceder 768 dp falsamente. Solución: Usa min(width, height) >= 768 para detección de clase de dispositivo.
Ignorar pantalla dividida de Android - El ancho de la ventana se reduce a teléfono-estrecho en una tablet. Solución: Prueba pantalla dividida; prefiere diseños flex que se degraden a una sola columna con elegancia.
Solo espaciado responsivo, sin cambio estructural - Empaquetar un diseño de tablet en un teléfono con relleno más pequeño sigue viéndose mal. Solución: Adapta la navegación y la estructura del panel, no solo paddingHorizontal.
Punto de ruptura en caché en useRef - Diseño obsoleto después de la rotación. Solución: Deriva el punto de ruptura de useWindowDimensions() en cada renderizado.
Olvidar el área segura con espaciado responsivo - El espaciado más ancho de la tablet no reemplaza el manejo de inserción. Solución: Combina Áreas seguras y muescas con tokens de punto de ruptura.
| Alternativa | Usa cuando | No uses cuando |
|---|---|---|
useWindowDimensions + constantes | Predeterminado - cero dependencias, control total | Lecturas de dimensión fuera de React (usa Dimensions + listener) |
Helper useResponsiveValue | Muchos tokens varían por punto de ruptura | Una rama isTablet en una sola pantalla |
Platform.isPad | Solo popover vs sheet de iOS | Detección de tablet multiplataforma |
Archivos de plataforma (*.tablet.tsx) | Interfaces de tablet radicalmente diferentes | Ajustes de espaciado menores (if/else es más simple) |
react-native-responsive-screen | El equipo quiere widthPercentageToDP | Prefieres hooks y puntos de ruptura explícitos |
| Consultas de medios CSS (Expo web) | Objetivos responsivos solo web | Diseños de producción nativos de iOS/Android |
El diseño RN es flexbox de Yoga, no un motor CSS del navegador. Simula consultas de medios con useWindowDimensions() y constantes de punto de ruptura. Los objetivos web de Expo pueden usar CSS, pero las aplicaciones nativas usan ramificación basada en hooks.
768 dp es el más común (ancho de iPad en orientación vertical). Material usa 600 para ancho medio. 1024 es adecuado para diseños de tablet grande en orientación horizontal y clase de escritorio. Elige un conjunto, documéntalo y úsalo de forma coherente.
useWindowDimensions - vuelve a renderizar al cambiar. Dimensions.get("window") es una instantánea única buena para registrar, incorrecta para interfaz responsiva. Consulta Dimensiones y diseño responsivo.
En tablet (min(width,height) >= 768), renderiza lista y detalle en flexDirection: "row". En teléfono, muestra solo la lista e impulsa la pantalla de detalles a través de la pila de Expo Router / React Navigation. Comparte los mismos hooks de datos en ambos diseños.
min(width, height) es más seguro para dispositivos plegables, pantallas cuadradas y cambios de orientación. El width >= 768 bruto funciona cuando explícitamente quieres comportamiento de tablet en orientación horizontal (por ejemplo, dividir solo en orientación horizontal).
Un teléfono en orientación horizontal puede exceder 768 dp de ancho. Usa Math.min(width, height) >= 768 para clase de dispositivo, o requiere umbrales de ancho y minDim.
Plegar/desplegar y pantalla dividida cambian useWindowDimensions abruptamente. Evita detección de modelo de dispositivo; reacciona al tamaño de la ventana. Prueba anchos estrechos (320-400 dp) incluso en hardware plegable.
Sí - mantén preocupaciones ortogonales en un módulo de tema:
const theme = {
spacing: useResponsiveValue({ phone: 16, tablet: 24, wide: 32 }),
colors: palette[useColorScheme() ?? "light"],
};Consulta Modo oscuro y esquemas de color.
Patrón común: 1 columna teléfono, 2 tablet, 3 ancho. Calcula el ancho del elemento: (width - gap * (columns + 1)) / columns. Usa flexWrap: "wrap" en el contenedor.
Sí. Usa useWindowDimensions en archivos de diseño y pantalla. Algunos equipos usan grupos de rutas paralelas para tablet ((tablet)/) - ramificación basada en hooks en un archivo es más simple para diferencias moderadas.
const modalWidth = Math.min(useWindowDimensions().width - 48, 560);
<View style={{ width: modalWidth, alignSelf: "center" }} />useWindowDimensions también devuelve fontScale. El texto del sistema ampliado puede romper filas de altura fija. Permite ajuste y flexShrink: 1. Prueba con el texto de accesibilidad grande habilitado.
Solo cuando la interfaz de tablet es radicalmente diferente (árbol de navegación distinto). Para alternadores maestro-detalle y números de columnas, una rama isTablet en un archivo es más fácil de mantener.
Los puntos de ruptura seleccionan la estructura (un vs dos paneles). Flex distribuye el espacio dentro de cada panel (flex: 1, gap, row vs column). Ambas capas trabajan juntas - consulta Análisis profundo de Flexbox.
useWindowDimensions, PixelRatio y mecánica de hooksVersiones de pila: 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