Layout Responsivo e Adaptativo
Pontos de interrupção para telefone/tablet/dobrável sem suposições de CSS da web.
Busque em todas as páginas da documentação
Pontos de interrupção para telefone/tablet/dobrável sem suposições de CSS da web.
Cartão de receita de referência rápida - pronto para copiar e colar.
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" },
});Quando usar isso: Telas que precisam mudar a contagem de colunas, o padrão de navegação ou a estrutura mestre-detalhe em telefones, tablets, dobráveis e rotação - sem supor media queries CSS do 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: "Revisão da Sprint", preview: "Demonstrar layout adaptativo no iPad." },
{ id: "2", subject: "QA de Dobrável", preview: "Testar janela estreita após dobrar." },
{ id: "3", subject: "Tokens de Ponto de Interrupção", preview: "Documentar 768 no sistema de design." },
];
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]}>
{/* Painel de lista - largura total no telefone, barra lateral fixa no tablet */}
<View style={[styles.list, isTablet && { width: listWidth }]}>
<Text style={styles.heading}>Caixa de Entrada</Text>
<Text style={styles.meta}>
{width}×{height} · {isTablet ? "layout de tablet" : "layout de telefone"}
</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>
{/* Painel de detalhes - visível apenas em tablets+; telefone navega via pilha */}
{isTablet ? (
<ScrollView style={styles.detail} contentContainerStyle={styles.detailInner}>
<Text style={styles.detailTitle}>Selecione uma mensagem</Text>
<Text style={styles.detailBody}>
Visualização dividida mestre-detalhe. No telefone, empurre esta tela a partir da lista 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 },
});O que isso demonstra:
useWindowDimensions() controlando o relayout na rotação, dobra e redimensionamento de tela dividida.min(width, height) para detecção de tablet segura para dobráveis (não apenas largura bruta).flexDirection: "row" no tablet.@media ou media queries de contêiner. Você lê width e height de useWindowDimensions() e ramifica o layout em JSX ou objetos de estilo derivados.useWindowDimensions se inscreve em eventos de mudança de dimensão. Rotação, mudanças de estado de dobra e tela dividida do Android acionam renderizações com novos valores. Dimensions.get("window") de uso único não atualiza.Para mecânica de hook, fontScale e PixelRatio, veja Dimensões e Layout Responsivo.
// Baseado na largura (mais comum)
export const BP = {
phone: 0,
tablet: 768, // iPad retrato, muitos tablets Android
desktop: 1024, // iPad paisagem, tablets grandes, display interno dobrável
} as const;
// Alternativa inspirada no Material
export const MATERIAL = { compact: 0, medium: 600, expanded: 840 } as const;| Limite | Dispositivos Típicos | Uso Comum |
|---|---|---|
< 768 | Telefones, tela dividida estreita | Coluna única, navegação em pilha |
768–1023 | Tablet retrato, dobrável pequeno interno | Duas colunas, mestre-detalhe dividido |
>= 1024 | Tablet paisagem, classe desktop | Três colunas, trilha 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,
// Dobrável: display interno pode ser quadrado - minDim pega "classe tablet"
isTabletClass: minDim >= 600,
};
}width >= 768 - simples, funciona para a maioria dos tablets em retrato.min(width, height) >= 768 - melhor para dobráveis e proporções quadradas.Platform.isPad - apenas iOS; combine com verificações de largura para Android.| Padrão | Telefone | Tablet / amplo |
|---|---|---|
| Mestre-detalhe | Empurrar pilha para tela de detalhes | Lista + detalhes lado a lado |
| Navegação | Abas inferiores | Trilha lateral ou gaveta permanente |
| Grade | 1–2 colunas | 2–4 colunas |
| Modal | Folha de tela inteira | Diálogo centralizado maxWidth: 560 |
| Tipografia | fontSize: 16 | fontSize: 17–18, lineLength mais amplo |
// Limite de largura do modal em telas grandes
const { width } = useWindowDimensions();
const modalWidth = Math.min(width - 48, 560);Pontos de interrupção escolhem qual árvore renderizar; flexbox distribui espaço dentro de cada painel:
const isTablet = width >= 768;
return (
<View style={{ flex: 1, flexDirection: isTablet ? "row" : "column" }}>
<View style={isTablet ? { width: 320 } : { flex: 1 }}>{/* lista */}</View>
{isTablet && <View style={{ flex: 1 }}>{/* detalhes */}</View>}
</View>
);Veja Mergulho Profundo em Flexbox para flex, gap e receitas de linha.
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)];
}
// Uso
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 e getBreakpoint de um módulo - evite números mágicos em telas.LayoutClass como uma união para que as instruções switch verifiquem exaustividade.Dimensions.get() sem assinatura - Layout congelado após rotação. Correção: Use useWindowDimensions() em qualquer componente que ramifique por tamanho.
width: 375 codificado em um mock de design - iPhones variam de 320–430+ dp; Android varia mais. Correção: Derive larguras de useWindowDimensions() ou use flex e porcentagens.
Verificação de tablet apenas em width em paisagem - Um telefone em paisagem pode exceder 768 dp falsamente. Correção: Use min(width, height) >= 768 para detecção de classe de dispositivo.
Ignorando tela dividida do Android - A largura da janela encolhe para a largura de um telefone em um tablet. Correção: Teste a tela dividida; prefira layouts flexíveis que degradam graciosamente para coluna única.
Apenas espaçamento responsivo, sem mudança estrutural - Amontoar um layout de tablet em um telefone com preenchimento menor ainda parece errado. Correção: Adapte a navegação e a estrutura do painel, não apenas paddingHorizontal.
Cache de ponto de interrupção em useRef - Layout desatualizado após rotação. Correção: Derive o ponto de interrupção de useWindowDimensions() a cada renderização.
Esquecendo a área segura com preenchimento responsivo - Preenchimento de tablet mais amplo não substitui o tratamento de insets. Correção: Combine Áreas Seguras e Entalhes com tokens de ponto de interrupção.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
useWindowDimensions + constantes | Padrão - zero dependências, controle total | Leituras de dimensão fora do React (use Dimensions + listener) |
Helper useResponsiveValue | Muitos tokens variam por ponto de interrupção | Um branch isTablet em uma única tela |
Platform.isPad | Detecção de popover vs folha apenas para iOS | Detecção de tablet multiplataforma |
Arquivos de plataforma (*.tablet.tsx) | UIs de tablet radicalmente diferentes | Pequenos ajustes de espaçamento (if/else é mais simples) |
react-native-responsive-screen | Equipe quer widthPercentageToDP | Você prefere hooks e pontos de interrupção explícitos |
| Media queries CSS (Expo web) | Alvos responsivos apenas para web | Layouts de produção nativos iOS/Android |
O layout RN é Yoga flexbox, não um motor CSS de navegador. Simule media queries com useWindowDimensions() e constantes de ponto de interrupção. O Expo web pode usar CSS, mas aplicativos nativos usam ramificação baseada em hook.
768 dp é o mais comum (largura do iPad em retrato). O Material usa 600 para largura média. 1024 serve para tablets grandes em paisagem e layouts de classe desktop. Escolha um conjunto, documente-o e use-o consistentemente.
useWindowDimensions - ele renderiza novamente na mudança. Dimensions.get("window") é um snapshot único, bom para log, errado para UI responsiva. Veja Dimensões e Layout Responsivo.
No tablet (min(width,height) >= 768), renderize lista e detalhes em flexDirection: "row". No telefone, mostre apenas a lista e empurre a tela de detalhes via Expo Router / pilha React Navigation. Compartilhe os mesmos hooks de dados em ambos os layouts.
min(width, height) é mais seguro para dobráveis, displays quadrados e mudanças de orientação. width >= 768 bruto funciona quando você explicitamente deseja comportamento de tablet em paisagem (por exemplo, dividir apenas em paisagem).
Um telefone em paisagem pode exceder 768 dp de largura. Use Math.min(width, height) >= 768 para classe de dispositivo, ou exija limites de largura e minDim.
Dobrar/desdobrar e tela dividida mudam useWindowDimensions abruptamente. Evite detecção de modelo de dispositivo; reaja ao tamanho da janela. Teste larguras estreitas (320–400 dp) mesmo em hardware dobrável.
Sim - mantenha preocupações ortogonais em um módulo de tema:
const theme = {
spacing: useResponsiveValue({ phone: 16, tablet: 24, wide: 32 }),
colors: palette[useColorScheme() ?? "light"],
};Padrão comum: 1 coluna telefone, 2 tablet, 3 amplo. Calcule a largura do item: (width - gap * (columns + 1)) / columns. Use flexWrap: "wrap" no contêiner.
Sim. Use useWindowDimensions em arquivos de layout e tela. Algumas equipes usam grupos de rotas paralelos para tablet ((tablet)/) - ramificação baseada em hook em um arquivo é mais simples para diferenças moderadas.
const modalWidth = Math.min(useWindowDimensions().width - 48, 560);
<View style={{ width: modalWidth, alignSelf: "center" }} />useWindowDimensions também retorna fontScale. Texto ampliado do sistema pode quebrar linhas de altura fixa. Permita quebra de linha e flexShrink: 1. Teste com texto de acessibilidade grande ativado.
Apenas quando a UI do tablet for radicalmente diferente (árvore de navegação distinta). Para alternância mestre-detalhe e contagem de colunas, um branch isTablet em um arquivo é mais fácil de manter.
Pontos de interrupção selecionam a estrutura (um ou dois painéis). Flex distribui o espaço dentro de cada painel (flex: 1, gap, linha vs coluna). Ambas as camadas trabalham juntas - veja Mergulho Profundo em Flexbox.
useWindowDimensions, PixelRatio e mecânica de hookVersões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026